解读unity内置的软阴影处理方式

解读unity内置的软阴影处理方式:
参考网址:
https://blog.csdn.net/cgy56191948/article/details/105726682
https://blog.csdn.net/weixin_45776473/article/details/119582218
https://tajourney.games/5482/
上面的博客已经论述了,为何出现锯齿,并从连续性角度,论述了pcf解决方案,使得阴影能够软。
本文主要是针对算法的实现上,剖析其实现的细节。

* PCF tent shadowmap filtering based on a 3x3 kernel (optimized with 4 taps)
*/
half UnitySampleShadowmap_PCF3x3Tent(float4 coord, float3 receiverPlaneDepthBias)
{
    half shadow = 1;

#ifdef SHADOWMAPSAMPLER_AND_TEXELSIZE_DEFINED

    #ifndef SHADOWS_NATIVE
        // when we don't have hardware PCF sampling, fallback to a simple 3x3 sampling with averaged results.
        return UnitySampleShadowmap_PCF3x3NoHardwareSupport(coord, receiverPlaneDepthBias);
    #endif

    // tent base is 3x3 base thus covering from 9 to 12 texels, thus we need 4 bilinear PCF fetches
    float2 tentCenterInTexelSpace = coord.xy * _ShadowMapTexture_TexelSize.zw;
    float2 centerOfFetchesInTexelSpace = floor(tentCenterInTexelSpace + 0.5);
    float2 offsetFromTentCenterToCenterOfFetches = tentCenterInTexelSpace - centerOfFetchesInTexelSpace;

    // find the weight of each texel based
    float4 texelsWeightsU, texelsWeightsV;
    _UnityInternalGetWeightPerTexel_3TexelsWideTriangleFilter(offsetFromTentCenterToCenterOfFetches.x, texelsWeightsU);
    _UnityInternalGetWeightPerTexel_3TexelsWideTriangleFilter(offsetFromTentCenterToCenterOfFetches.y, texelsWeightsV);

    // each fetch will cover a group of 2x2 texels, the weight of each group is the sum of the weights of the texels
    float2 fetchesWeightsU = texelsWeightsU.xz + texelsWeightsU.yw;
    float2 fetchesWeightsV = texelsWeightsV.xz + texelsWeightsV.yw;

    // move the PCF bilinear fetches to respect texels weights
    float2 fetchesOffsetsU = texelsWeightsU.yw / fetchesWeightsU.xy + float2(-1.5,0.5);
    float2 fetchesOffsetsV = texelsWeightsV.yw / fetchesWeightsV.xy + float2(-1.5,0.5);
    fetchesOffsetsU *= _ShadowMapTexture_TexelSize.xx;
    fetchesOffsetsV *= _ShadowMapTexture_TexelSize.yy;

    // fetch !
    float2 bilinearFetchOrigin = centerOfFetchesInTexelSpace * _ShadowMapTexture_TexelSize.xy;
    shadow =  fetchesWeightsU.x * fetchesWeightsV.x * UNITY_SAMPLE_SHADOW(_ShadowMapTexture, UnityCombineShadowcoordComponents(bilinearFetchOrigin, float2(fetchesOffsetsU.x, fetchesOffsetsV.x), coord.z, receiverPlaneDepthBias));
    shadow += fetchesWeightsU.y * fetchesWeightsV.x * UNITY_SAMPLE_SHADOW(_ShadowMapTexture, UnityCombineShadowcoordComponents(bilinearFetchOrigin, float2(fetchesOffsetsU.y, fetchesOffsetsV.x), coord.z, receiverPlaneDepthBias));
    shadow += fetchesWeightsU.x * fetchesWeightsV.y * UNITY_SAMPLE_SHADOW(_ShadowMapTexture, UnityCombineShadowcoordComponents(bilinearFetchOrigin, float2(fetchesOffsetsU.x, fetchesOffsetsV.y), coord.z, receiverPlaneDepthBias));
    shadow += fetchesWeightsU.y * fetchesWeightsV.y * UNITY_SAMPLE_SHADOW(_ShadowMapTexture, UnityCombineShadowcoordComponents(bilinearFetchOrigin, float2(fetchesOffsetsU.y, fetchesOffsetsV.y), coord.z, receiverPlaneDepthBias));
#endif

    return shadow;
}

传入的参数:float4 coord是(0,1)阴影贴图的纹理坐标值。
接下来说明三角形面积的计算方式:
在这里插入图片描述
如上图所示,红色的p点为阴影处的点。我们要对它进行软阴影处理。
情况1)三角形偏移为0时,三角形面积被分为4个部分的面积情况。
三角形的底为3,高为1.5,之所以使用这样的三角形,因为计算简单。
红色面积:1/8
橙色面积:1
绿色面积:1
蓝色面积:1/8
情况2)三角形偏移为-0.5时,三角形面积被分为4个部分的面积情况。
在这里插入图片描述
红色面积:0.5
橙色面积:1.25
绿色面积:0.5
蓝色面积:0
情况3)三角形偏移为0.5时,三角形面积被分为4个部分的面积情况
在这里插入图片描述
红色面积:0
橙色面积:0.5
绿色面积:1.25
蓝色面积:0.5
然后推广到更一般的情况,给定偏移offset,计算三角形被分割的四个部分的面积是多少,公式为:
在这里插入图片描述
对应的这段代码是:

// ------------------------------------------------------------------
//  PCF Filtering helpers
// ------------------------------------------------------------------

/**
* Assuming a isoceles rectangle triangle of height "triangleHeight" (as drawn below).
* This function return the area of the triangle above the first texel.
*
* |\      <-- 45 degree slop isosceles rectangle triangle
* | \
* ----    <-- length of this side is "triangleHeight"
* _ _ _ _ <-- texels
*/
float _UnityInternalGetAreaAboveFirstTexelUnderAIsocelesRectangleTriangle(float triangleHeight)
{
    return triangleHeight - 0.5;
}

/**
* Assuming a isoceles triangle of 1.5 texels height and 3 texels wide lying on 4 texels.
* This function return the area of the triangle above each of those texels.
*    |    <-- offset from -0.5 to 0.5, 0 meaning triangle is exactly in the center
*   / \   <-- 45 degree slop isosceles triangle (ie tent projected in 2D)
*  /   \
* _ _ _ _ <-- texels
* X Y Z W <-- result indices (in computedArea.xyzw and computedAreaUncut.xyzw)
*/
void _UnityInternalGetAreaPerTexel_3TexelsWideTriangleFilter(float offset, out float4 computedArea, out float4 computedAreaUncut)
{
    //Compute the exterior areas
    float offset01SquaredHalved = (offset + 0.5) * (offset + 0.5) * 0.5;
    computedAreaUncut.x = computedArea.x = offset01SquaredHalved - offset;
    computedAreaUncut.w = computedArea.w = offset01SquaredHalved;

    //Compute the middle areas
    //For Y : We find the area in Y of as if the left section of the isoceles triangle would
    //intersect the axis between Y and Z (ie where offset = 0).
    computedAreaUncut.y = _UnityInternalGetAreaAboveFirstTexelUnderAIsocelesRectangleTriangle(1.5 - offset);
    //This area is superior to the one we are looking for if (offset < 0) thus we need to
    //subtract the area of the triangle defined by (0,1.5-offset), (0,1.5+offset), (-offset,1.5).
    float clampedOffsetLeft = min(offset,0);
    float areaOfSmallLeftTriangle = clampedOffsetLeft * clampedOffsetLeft;
    computedArea.y = computedAreaUncut.y - areaOfSmallLeftTriangle;

    //We do the same for the Z but with the right part of the isoceles triangle
    computedAreaUncut.z = _UnityInternalGetAreaAboveFirstTexelUnderAIsocelesRectangleTriangle(1.5 + offset);
    float clampedOffsetRight = max(offset,0);
    float areaOfSmallRightTriangle = clampedOffsetRight * clampedOffsetRight;
    computedArea.z = computedAreaUncut.z - areaOfSmallRightTriangle;
}

这个公式后面会给出详细的证明。
然后计算四个划分的面积占总面积的多少,由于等腰直角三角形的底是3,高为1.5,所以面积是9/4,求权重比,对应的代码是:

/**
 * Assuming a isoceles triangle of 1.5 texels height and 3 texels wide lying on 4 texels.
 * This function return the weight of each texels area relative to the full triangle area.
 */
void _UnityInternalGetWeightPerTexel_3TexelsWideTriangleFilter(float offset, out float4 computedWeight)
{
    float4 dummy;
    _UnityInternalGetAreaPerTexel_3TexelsWideTriangleFilter(offset, computedWeight, dummy);
    computedWeight *= 0.44444;//0.44 == 1/(the triangle area)
}

此时得到了水平方向的四个权重,然后我们同样的方法得到垂直方向的四个权重,然后咋办?
在这里插入图片描述
我们可以把x和y两个看成连续的块,然后把z和w看成连续的块,然后对应代码:

    // each fetch will cover a group of 2x2 texels, the weight of each group is the sum of the weights of the texels
    float2 fetchesWeightsU = texelsWeightsU.xz + texelsWeightsU.yw;

于是fetchesWeightU.x = texelsWeightsU.x + texelsWeightsU.y
fetchesWeightU.y = texelsWeightsU.z + texelsWeightsU.w
然后,就是计算y占(x+y)的百分比,w占(z+w)的百分比

    // move the PCF bilinear fetches to respect texels weights
    float2 fetchesOffsetsU = texelsWeightsU.yw / fetchesWeightsU.xy + float2(-1.5,0.5);

fetchesOffsetsU.x = texelsWeightsU.y / (texelsWeightsU.x + texelsWeightsU.y)
fetchesOffsetsU.y = texelsWeightsU.w / (texelsWeightsU.z + texelsWeightsU.w)
这样就得到的fetchesOffsetsU,是大于等于0的偏移,所以为了得到相对的偏移得归一化到起点。
在这里插入图片描述
如上图粉色框框起来的x和y点,其水平起点为三角形的底/2,取负,所以是-1.5
在这里插入图片描述
而又因为是相邻每两个像素的归一化,所以绿色框住的起点是-1.5+2=0.5,于是得到上面的偏移:+float2(-1.5,0.5);
同样的垂直方向上也是类似的计算方式,最终我们得到了:

// move the PCF bilinear fetches to respect texels weights
    float2 fetchesOffsetsU = texelsWeightsU.yw / fetchesWeightsU.xy + float2(-1.5,0.5);
    float2 fetchesOffsetsV = texelsWeightsV.yw / fetchesWeightsV.xy + float2(-1.5,0.5);

然后我们将转换到(0,1)的范围,直接乘以:

    fetchesOffsetsU *= _ShadowMapTexture_TexelSize.xx;
    fetchesOffsetsV *= _ShadowMapTexture_TexelSize.yy;

这里的_ShadowMapTexture_TexelSize的x、y、z、w分量分别是阴影贴图的水平像素个数倒数,垂直像素个数倒数,水平像素个数,垂直像素个数。
然后就是去采样了:

    // fetch !
    float2 bilinearFetchOrigin = centerOfFetchesInTexelSpace * _ShadowMapTexture_TexelSize.xy;
    shadow =  fetchesWeightsU.x * fetchesWeightsV.x * UNITY_SAMPLE_SHADOW(_ShadowMapTexture, UnityCombineShadowcoordComponents(bilinearFetchOrigin, float2(fetchesOffsetsU.x, fetchesOffsetsV.x), coord.z, receiverPlaneDepthBias));
    shadow += fetchesWeightsU.y * fetchesWeightsV.x * UNITY_SAMPLE_SHADOW(_ShadowMapTexture, UnityCombineShadowcoordComponents(bilinearFetchOrigin, float2(fetchesOffsetsU.y, fetchesOffsetsV.x), coord.z, receiverPlaneDepthBias));
    shadow += fetchesWeightsU.x * fetchesWeightsV.y * UNITY_SAMPLE_SHADOW(_ShadowMapTexture, UnityCombineShadowcoordComponents(bilinearFetchOrigin, float2(fetchesOffsetsU.x, fetchesOffsetsV.y), coord.z, receiverPlaneDepthBias));
    shadow += fetchesWeightsU.y * fetchesWeightsV.y * UNITY_SAMPLE_SHADOW(_ShadowMapTexture, UnityCombineShadowcoordComponents(bilinearFetchOrigin, float2(fetchesOffsetsU.y, fetchesOffsetsV.y), coord.z, receiverPlaneDepthBias));
#endif

float2 bilinearFetchOrigin = centerOfFetchesInTexelSpace * _ShadowMapTexture_TexelSize.xy;
是p点,就是要处理的点。
然后就是:
UnityCombineShadowcoordComponents(bilinearFetchOrigin, float2(fetchesOffsetsU.x, fetchesOffsetsV.x), coord.z, receiverPlaneDepthBias)
这个函数

/**
* Combines the different components of a shadow coordinate and returns the final coordinate.
* See UnityGetReceiverPlaneDepthBias
*/
float3 UnityCombineShadowcoordComponents(float2 baseUV, float2 deltaUV, float depth, float3 receiverPlaneDepthBias)
{
    float3 uv = float3(baseUV + deltaUV, depth + receiverPlaneDepthBias.z);
    uv.z += dot(deltaUV, receiverPlaneDepthBias.xy);
    return uv;
}

baseUV + deltaUV,就是进行uv的偏移。然后z值,可以uv和阴影bias点乘,加到z上,最终得到一个三维的采样坐标。
最终:fetchesWeightsU.x * fetchesWeightsV.x * UNITY_SAMPLE_SHADOW
得到一个点的权重,然后分别做四次即可,得到四个方向上的阴影贡献值,即可得到最后软阴影效果了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/237461.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【基于LSTM的股票数据预测与分类】

基于LSTM的股票数据预测与分类 引言数据集与爬取数据处理与可视化股票预测与分类Flask页面搭建股票推荐功能创新点结论 引言 股票市场波动剧烈&#xff0c;对于投资者而言&#xff0c;精准的数据预测和分类是制定明智决策的基础。本文将介绍一种基于长短时记忆网络&#xff08…

福德植保无人机:绿色农业的新篇章

今天&#xff0c;我们荣幸地向您介绍福德植保无人机&#xff0c;一种改变传统农业种植方式&#xff0c;引领绿色农业的新科技产品。福德植保无人机以其高效、环保、安全的特点&#xff0c;正逐渐成为植保行业的新宠。福德植保无人机是一种搭载了高性能发动机和精确喷洒系统的飞…

TypeScript基础语法

目录 变量声明 条件控制 循环迭代 函数 类和接口 模块开发 变量声明 TypeScript在JavaScript的基础上加入了静态类型检查功能&#xff0c;因此每一个变量都有固定的数据类型。 条件控制 TypeScript与大多数开发语言类似&#xff0c;支持基于if-else和Switch的条件控制…

[原创]如何正确的部署R语言开发环境(含动图演示).

[简介] 常用网名: 猪头三 出生日期: 1981.XX.XX QQ联系: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、D…

目标检测——R-FCN算法解读

论文&#xff1a;R-FCN: Object Detection via Region-based Fully Convolutional Networks 作者&#xff1a;Jifeng Dai, Yi Li, Kaiming He and Jian Sun 链接&#xff1a;https://arxiv.org/pdf/1605.06409v2.pdf 代码&#xff1a;https://github.com/daijifeng001/r-fcn 文…

赛宁网安多领域亮相第三届网络空间内生安全发展大会

2023年12月8日&#xff0c;第三届网络空间内生安全发展大会在宁开幕。两院院士、杰出专家学者和知名企业家相聚南京&#xff0c;围绕数字经济新生态、网络安全新范式进行广泛研讨&#xff0c;为筑牢数字安全底座贡献智慧和力量。 大会围绕“一会、一赛、一展”举办了丰富多彩的…

2-1基础算法-枚举/模拟

文章目录 1.枚举2.模拟 1.枚举 [例1] 特别数的和 评测系统 #include <iostream> using namespace std; bool pa(int x) {while (x) {if (x % 10 2 || x % 10 1 || x % 10 0 || x % 10 9) {return true;}else {x x / 10;}}return false; } int main() {int sum0;i…

科技云报道:从数据到生成式AI,是该重新思考风险的时候了

科技云报道原创。 OpenAI“宫斗”大戏即将尘埃落定。 自首席执行官Sam Altman突然被董事会宣布遭解雇、董事长兼总裁Greg Brockman辞职&#xff1b;紧接着OpenAI员工以辞职威胁董事会要求Altman回归&#xff1b;再到OpenAI董事会更换成员、Altman回归OpenAI。 表面上看&…

数据库容灾的设计与实现(五)

六、容灾方案的应用评估 上文中设计了油田数据级容灾系统&#xff0c;完成了基于Oracle Data Guard数据级容灾架构的设计和实施&#xff0c;实现了Broker Failover的FSFO切换技术、触发器提供不间断服务器端服务、客户端使用TAF实现透明故障转移的&#xff0c;完成了数据级容灾…

Java最全面试题专题---2、Java集合容器(2)

Map接口 说一下 HashMap 的实现原理&#xff1f; HashMap概述&#xff1a; HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作&#xff0c;并允许使用null值和null键。此类不保证映射的顺序&#xff0c;特别是它不保证该顺序恒久不变。 HashMap的数据…

EasyExcel使用模板导出复杂Excel

1&#xff09;添加easyexlce的依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.0-beta1</version> </dependency>2&#xff09;添加excel模板文件 实现的效果&#xff1a;…

java工程(ajax/axios/postman)向请求头中添加消息

1、问题概述 在项目中我们经常会遇到需要向请求头中添加消息的场景&#xff0c;然后后端通过request.getRequest()或者RequestHeader获取请求头中的消息。 下面提供几种前端向请求头添加消息的方式 2、创建一个springmvc工程用于测试 2.1、创建工程并引入相关包信息 sprin…

kettle+report designer导出带样式的excel包含多个sheet页

场景介绍&#xff1a; 运用pentaho report designer报表设计器&#xff0c;查询数据库字典表生成带有样式的excel&#xff0c;通过kettle pentaho报表输出组件导出形成数据字典&#xff0c;最终形成的数据字典样式如下图&#xff1a; 案例适用范围&#xff1a; pentaho repor…

开源治理典型案例分享(汇编转)

当前&#xff0c;越来越多的企业申请通过信通院的开源治理成熟度评估和认证&#xff0c;获得增强级或先进级评估。这些企业包括中国工商银行股份有限公司、中国农业银行、上海浦东发展银行股份有限公司、中信银行股份有限公司、中国太平洋保险&#xff08;集团&#xff09;股份…

停车场物联网解决方案4G工业路由器应用

随着物联网技术的发展&#xff0c;停车场也实现了数字化、智能化。停车场管理系统是一个集计算机、网络通信、自动控制等技术为一体的综合性系统&#xff0c;它的实施&#xff0c;对加强停车场管理&#xff0c;提高工作效率&#xff0c;提升服务质量和现代化水平&#xff0c;进…

ubuntu18.04配置cuda+cudnn+tensorrt+anconda+pytorch-gpu+pycharm

一、显卡驱动安装 执行nvidia-smi查看安装情况 二、cuda安装 cuda官网下载cuda_11.6.2_510.47.03_linux.run&#xff0c;安装执行 sudo sh cuda_11.6.2_510.47.03_linux.run提升安装项&#xff0c;驱动不用安装&#xff0c;即第一项&#xff08;Driver&#xff09;&#xff…

TrustZone之完成器:外围设备和内存

到目前为止,在本指南中,我们集中讨论了处理器,但TrustZone远不止是一组处理器功能。要充分利用TrustZone功能,我们还需要系统其余部分的支持。以下是一个启用了TrustZone的系统示例: 本节探讨了该系统中的关键组件以及它们在TrustZone中的作用。 完成器:外围设备…

centos7部署docker

文章目录 &#xff08;1&#xff09;安装前准备&#xff08;2&#xff09;卸载旧版Docker&#xff08;3&#xff09;安装docker&#xff08;4&#xff09;配置镜像加速 &#xff08;1&#xff09;安装前准备 在开始安装之前&#xff0c;首先需要检查内核版本。使用 uname -r 命…

Python基础期末复习 新手 2

虽然age 10在__init__方法中定义了一个局部变量age&#xff0c;但这个局部变量并不会影响类属性age的值。类属性是在类级别上定义的&#xff0c;不属于任何一个实例。因此&#xff0c;在创建实例s1和s2时&#xff0c;它们的age属性值都为类属性的初始值0。 尽管对类的属性值进…

【JVM从入门到实战】(一) 字节码文件

一、什么是JVM JVM 全称是 Java Virtual Machine&#xff0c;中文译名 Java虚拟机。 JVM 本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件。 二、JVM的功能 解释和运行 对字节码文件中的指令&#xff0c;实时的解释成机器码&#xff0c;让计算机…