解读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 fetchesfloat2 tentCenterInTexelSpace = coord.xy * _ShadowMapTexture_TexelSize.zw;float2 centerOfFetchesInTexelSpace = floor(tentCenterInTexelSpace + 0.5);float2 offsetFromTentCenterToCenterOfFetches = tentCenterInTexelSpace - centerOfFetchesInTexelSpace;// find the weight of each texel basedfloat4 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 texelsfloat2 fetchesWeightsU = texelsWeightsU.xz + texelsWeightsU.yw;float2 fetchesWeightsV = texelsWeightsV.xz + texelsWeightsV.yw;// move the PCF bilinear fetches to respect texels weightsfloat2 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));
#endifreturn 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 areasfloat 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 trianglecomputedAreaUncut.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 texelsfloat2 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 weightsfloat2 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 weightsfloat2 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
得到一个点的权重,然后分别做四次即可,得到四个方向上的阴影贡献值,即可得到最后软阴影效果了。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

css的复合选择器(有案例)

目录 复合选择器的描述 后代选择器&#xff08;常用重点&#xff09; 子选择器 并集选择器&#xff08;重点常用&#xff09; 伪类选择器 链接伪类选择器 focus 伪类选择器 知识总结&#xff1a; 案例实现&#xff1a; 复合选择器的描述 在 CSS 中&#xff0c;可以根…

日志门面slf4j和各日志框架

简介 简单日志门面(Simple Logging Facade For Java) SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架&#xff0c; 其主要意义在于提供接口&#xff0c;具体的实现可以交由其他日志框架&#xff0c;如log4j、logback、log4j2。 对于一般的Java项目而言&#xff…

SpringData JPA 搭建 xml的 配置方式

1.导入版本管理依赖 到父项目里 <dependencyManagement><dependencies><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-bom</artifactId><version>2021.1.10</version><scope>…

【C++11】lambda表达式及包装器

一.lambda表达式 1.可调用对象 可调用对象即可以像函数一样被调用的对象&#xff0c;有以下三种&#xff1a; 函数(指针)仿函数对象lambda表达式 tips&#xff1a;调用函数时&#xff0c;既可以用函数名&#xff0c;也可以用函数地址&#xff0c;因为函数名和函数地址是一回事…

Python从入门到精通五:Python数据容器

数据容器入门 为什么学习数据容器 思考一个问题&#xff1a;如果我想要在程序中&#xff0c;记录5名学生的信息&#xff0c;如姓名。 如何做呢&#xff1f; 学习数据容器&#xff0c;就是为了批量存储或批量使用多份数据 Python中的数据容器&#xff1a; 一种可以容纳多份…

Kalman滤波、扩展Kalman滤波、无迹Kalman滤波和异步滤波的原理及其Matlab代码

目录 引言Kalman滤波代码及其结果展示 扩展Kalman滤波代码及其结果展示 无迹Kalman滤波无迹变换无迹Kalman滤波代码及其结果展示 异步无迹Kalman滤波原理代码及其结果展示 引言 本文给出了Kalman Filter&#xff08;卡尔曼滤波&#xff09;、Extended Kalman Filter&#xff0…

leetcode 98. 验证二叉搜索树

leetcode 98. 验证二叉搜索树 题目 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是…

Softmax回归

一、Softmax回归关键思想 1、回归问题和分类问题的区别 Softmax回归虽然叫“回归”&#xff0c;但是它本质是一个分类问题。回归是估计一个连续值&#xff0c;而分类是预测一个离散类别。 2、Softmax回归模型 Softmax回归跟线性回归一样将输入特征与权重做线性叠加。与线性回归…

Linux安装Nginx并部署Vue项目

今天部署了一个Vue项目到阿里云的云服务器上&#xff0c;现记录该过程。 1. 修改Vue项目配置 我们去项目中发送axios请求的文件里更改一下后端的接口路由&#xff1a; 2. 执行命令打包 npm run build ### 或者 yarn build 打包成功之后&#xff0c;我们会看到一个dist包&a…

[MySQL]SQL优化之索引的使用规则

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 一、索引失效 &#x1f4d5;最左前缀法则 &#x1f4d5;范围查询> &#x1f4d5;索引列运算&#xff0c;索引失效 &#x1f4d5;前模糊匹配 &#x1f4d5;or连接的条件 &#x1f4d5;字符串类型不加 …

110. 平衡二叉树(Java)

给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a; 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;t…

如何通过SPI控制Peregrine的数控衰减器

概要 Peregrine的数控衰减器PE4312是6位射频数字步进衰减器(DSA,Digital Step Attenuator)工作频率覆盖1MHz~4GHz,插入损耗2dB左右,衰减步进0.5dB,最大衰减量为31.5dB,高达59dBm的IIP3提供了良好的动态性能,切换时间0.5微秒,供电电源2.3V~5.5V,逻辑控制兼容1.8V,20…

​如何使用https://www.krea.ai/来实现文生图,图生图,

网址&#xff1a;https://www.krea.ai/apps/image/realtime Krea.ai 是一个强大的人工智能艺术生成器&#xff0c;可用于创建各种创意内容。它可以用来生成文本描述的图像、将图像转换为其他图像&#xff0c;甚至写博客文章。 文本描述生成图像 要使用 Krea.ai 生成文本描述…

【conda】利用Conda创建虚拟环境,Pytorch各版本安装教程(Ubuntu)

TOC conda 系列&#xff1a; 1. conda指令教程 2. 利用Conda创建虚拟环境&#xff0c;安装Pytorch各版本教程(Ubuntu) 1. 利用Conda创建虚拟环境 nolonolo:~/sun/SplaTAM$ conda create -n splatam python3.10查看结果&#xff1a; (splatam) nolonolo:~/sun/SplaTAM$ cond…

Linux系统编程(一):基本概念

参考引用 Unix和Linux操作系统有什么区别&#xff1f;一文带你彻底搞懂posix Linux系统编程&#xff08;文章链接汇总&#xff09; 1. Unix 和 Linux 1.1 Unix Unix 操作系统诞生于 1969 年&#xff0c;贝尔实验室发布了一个用 C 语言编写的名为「Unix」的操作系统&#xff0…

【基于LSTM的电商评论情感分析:Flask与Sklearn的完美结合】

基于LSTM的电商评论情感分析&#xff1a;Flask与Sklearn的完美结合 引言数据集与爬取数据处理与可视化情感分析模型构建Flask应用搭建词云展示创新点结论 引言 在当今数字化时代&#xff0c;电商平台上涌现出大量的用户评论数据。了解和分析这些评论对于企业改进产品、服务以及…

以太坊:前世今生与未来

一、引言 以太坊&#xff0c;这个在区块链领域大放异彩的名字&#xff0c;似乎已经成为了去中心化应用&#xff08;DApps&#xff09;的代名词。从初期的萌芽到如今的繁荣发展&#xff0c;以太坊经历了一段曲折而精彩的旅程。让我们一起回顾一下以太坊的前世今生&#xff0c;以…

树实验代码

哈夫曼树 #include <stdio.h> #include <stdlib.h> #define MAXLEN 100typedef struct {int weight;int lchild, rchild, parent; } HTNode;typedef HTNode HT[MAXLEN]; int n;void CreatHFMT(HT T); void InitHFMT(HT T); void InputWeight(HT T); void SelectMi…

【Qt开发流程】之UI风格、预览及QPalette使用

概述 一个优秀的应用程序不仅要有实用的功能&#xff0c;还要有一个漂亮美腻的外观&#xff0c;这样才能使应用程序更加友善、操作性良好&#xff0c;更加符合人体工程学。作为一个跨平台的UI开发框架&#xff0c;Qt提供了强大而且灵活的界面外观设计机制&#xff0c;能够帮助…

利用Rclone将阿里云对象存储迁移至雨云对象存储的教程,对象存储数据迁移教程

使用Rclone将阿里云对象存储(OSS)的文件全部迁移至雨云对象存储(ROS)的教程&#xff0c;其他的对象存储也可以参照本教程。 Rclone简介 Rclone 是一个用于和同步云平台同步文件和目录命令行工具。采用 Go 语言开发。 它允许在文件系统和云存储服务之间或在多个云存储服务之间…