TransformWorldToShadowCoord 的核心作用很简单:将你提供的世界坐标,转换到一个可以用于采样 Shadow Map 的坐标空间。它本质上是为你省去了手动编写矩阵乘法的繁琐步骤。
🔍 核心原理:一个"三步走"的幕后过程
函数内部主要执行了以下三个步骤:
-
获取矩阵 :获取从"世界空间"转换到"阴影空间"的核心矩阵
unity_WorldToShadow。-
方向光 :需处理阴影级联(Shadow Cascades),因此是一个包含最多4个矩阵的数组
unity_WorldToShadow[4]。 -
聚光灯/点光源 :通常只用一个矩阵
unity_WorldToLight。
-
-
执行变换 :执行矩阵乘法
mul(unity_WorldToShadow[selectedIndex], float4(worldPos, 1.0)),将世界坐标转换到阴影空间的齐次坐标。 -
处理齐次坐标 :对于聚光灯 或点光源 这类透视投影,需要执行透视除法(
shadowCoord.xyz / shadowCoord.w),将坐标映射到0-1范围以得到最终的Shadow Map UV坐标;而方向光 由于是正交投影(w分量为1),则无需此步骤。
此外,函数内部还会根据是否启用级联阴影映射(_MAIN_LIGHT_SHADOWS_CASCADE)进行相应处理。当启用时,它会选择正确的 unity_WorldToShadow 矩阵索引来计算坐标。
💡 一个更直观的理解
你可以将这个过程类比为:
-
TransformWorldToShadowCoord:是一个翻译器,它将物体的世界坐标,翻译成Shadow Map上每个像素点的"门牌号"。 -
Shadow Map:是由光源"拍摄"的深度信息图,记录了场景中每个点离光源的远近。
-
MainLightRealtimeShadow等采样函数 :拿着翻译出来的"门牌号"(shadowCoord),去Shadow Map这张"地图"上,找到对应的深度信息进行比较,最终判定当前像素点是否处于阴影中。
🔧 实际使用示例
一个典型的URP Shader中接收阴影的流程大致如下:
cs
// 1. 在着色器代码中包含必要的头文件并声明编译宏
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
// ... 顶点着色器中输出世界坐标
VertexPositionInputs vertexInput = GetVertexPositionInputs(IN.positionOS.xyz);
OUT.positionWS = vertexInput.positionWS;
// 2. 在片元着色器中,将世界坐标转换为阴影坐标
float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS);
// 3. 获取主光源,传入阴影坐标,得到带有阴影衰减值的光源信息
Light mainLight = GetMainLight(shadowCoord);
float3 attenuatedLightColor = mainLight.color * mainLight.shadowAttenuation;
⚠️ 注意事项
-
宏定义 :
_MAIN_LIGHT_SHADOWS和_MAIN_LIGHT_SHADOWS_CASCADE宏是正确启用功能的"开关",确保它们被正确定义非常重要。 -
性能考量 :在顶点着色器中计算
shadowCoord通常更高效(只计算一次),但在顶点动画较多时,为了保证阴影准确,可能需要在片元着色器中计算。 -
管线差异 :该函数主要适用于URP,Built-in管线通常使用
TRANSFER_SHADOW宏,而HDRP则使用EvalShadow_WorldToShadow函数。