一段根据片元深度值重建对应的深度坐标值的代码及其解释
Table of Contents
请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com
// 从深度信息重建世界空间坐标
// 参数:
// screenPos - 屏幕空间坐标(通常来自顶点着色器的SV_Position)
// viewDir - 视图空间方向向量
// sceneDepth - 包含原始深度和线性深度值的结构体
float3 ReconstructWorldPosition(float4 screenPos, float3 viewDir, SceneDepth sceneDepth)
{
// 平台深度缓冲处理 ----------------------------------------
#if UNITY_REVERSED_Z // 处理Reversed-Z平台(如DirectX)
real rawDepth = sceneDepth.raw; // 直接使用原始深度值
#else // OpenGL等常规深度缓冲平台
// 将深度值从[0,1]映射到[Near,1],适配OpenGL的NDC范围
real rawDepth = lerp(UNITY_NEAR_CLIP_VALUE, 1, sceneDepth.raw);
#endif
// 正交投影处理 --------------------------------------------
#if defined(ORTHOGRAPHIC_SUPPORT)
// 将屏幕坐标转换为NDC空间(-1到1范围)
float4 viewPos = float4(
(screenPos.xy / screenPos.w) * 2.0 - 1.0, // 透视除法后映射到NDC
rawDepth, // 深度值
1.0
);
// 获取视图-世界变换矩阵
float4x4 viewToWorld = UNITY_MATRIX_I_VP;
// Unity版本兼容性修复(7.3.1后需要反转矩阵第二行)
#if UNITY_REVERSED_Z
// 修正矩阵:_12, _22, _32, _42分量取反
viewToWorld._12_22_32_42 = -viewToWorld._12_22_32_42;
#endif
// 矩阵变换得到世界坐标
float4 viewWorld = mul(viewToWorld, viewPos);
float3 viewWorldPos = viewWorld.xyz / viewWorld.w; // 透视除法
#endif
// 透视投影处理 --------------------------------------------
// 获取当前相机世界坐标
float3 camPos = GetCurrentViewPosition().xyz;
// 计算透视投影的世界坐标:
// sceneDepth.eye - 线性深度值(相机到表面的距离)
// viewDir/screenPos.w - 视图方向归一化(screenPos.w存储投影前的z分量)
float3 worldPos = sceneDepth.eye * (viewDir / screenPos.w) - camPos;
float3 perspWorldPos = -worldPos; // 方向修正(视图空间到世界空间转换)
// 结果混合 -----------------------------------------------
#if defined(ORTHOGRAPHIC_SUPPORT)
// 使用unity_OrthoParams.w(0=透视,1=正交)混合两种投影结果
return lerp(perspWorldPos, viewWorldPos, unity_OrthoParams.w);
#else
return perspWorldPos; // 仅返回透视投影结果
#endif
}
第一部分是关于处理深度值,这里有一个条件编译UNITY_REVERSED_Z
。Unity中不同的平台可能使用不同的深度缓冲范围,比如DirectX通常使用Reversed-Z,即近平面为1,远平面为0,而OpenGL则是0到1。所以这里如果是Reversed-Z,直接使用sceneDepth.raw;否则,用lerp
将原始深度值调整到UNITY_NEAR_CLIP_VALUE
到1之间,可能是为了适配OpenGL的NDC空间。
接下来被注释掉的那行代码是调用ComputeWorldSpacePosition
函数,可能是Unity内置的函数,但当前代码没有使用,而是自己实现了计算。
然后是条件编译ORTHOGRAPHIC_SUPPORT
,处理正交投影的情况。这里首先将屏幕坐标转换为NDC(-1到1),然后构造viewPos,使用UNITY_MATRIX_I_VP
矩阵(即视图投影矩阵的逆矩阵)将viewPos转换到世界空间。但注意到在UNITY_REVERSED_Z
的情况下,需要调整矩阵的第二行,可能是由于Unity的某个版本修正导致的矩阵问题,所以需要将第二行的符号取反。之后得到viewWorldPos,除以齐次坐标的w分量得到正确的三维坐标。
接下来处理透视投影的情况。获取相机位置camPos
,然后根据sceneDepth.eye
(可能是线性深度值)、viewDir
和screenPos.w
来计算worldPos。这里可能需要推导一下公式:可能是在视图空间中的位置计算,然后转换到世界空间?不过代码中的计算看起来是worldPos = sceneDepth.eye * (viewDir / screenPos.w) - camPos
; 然后perspWorldPos
是-worldPos
,这可能是因为视图方向的方向问题?
最后,根据是否支持正交投影,用lerp混合透视投影和正交投影的结果,混合系数是unity_OrthoParams.w
,这个参数可能表示当前是否处于正交投影模式(w分量可能为0或1)。
现在需要详细注释每个步骤。可能需要注意的点:
-
UNITY_REVERSED_Z
的处理:不同平台的深度值处理。 -
ORTHOGRAPHIC_SUPPORT
部分的正交投影坐标转换,特别是矩阵调整的原因。 -
透视投影部分的计算,可能需要解释
sceneDepth.eye
的含义,以及如何通过viewDir
和深度值重建世界坐标。 -
最后的
lerp
如何根据投影模式选择正确的坐标。
可能遇到的问题:对某些矩阵运算或者参数的具体含义不太确定,需要查阅Unity文档或者常见Shader编写方法。例如,sceneDepth
的结构可能包含raw深度和线性深度eye;screenPos可能是经过透视除法的坐标,或者需要自己处理;viewDir
可能是在视图空间中的方向向量。
此外,代码中的注释提到在UNITY_REVERSED_Z
的情况下,调整viewToWorld
矩阵的第二行,是因为Unity的一个修复导致的问题,所以需要翻转某些符号。这可能涉及到坐标系的变换,比如从右手系到左手系,或者矩阵求逆后的修正。
在透视投影部分,计算worldPos
的公式可能需要进一步推导。例如,假设sceneDepth.eye
是线性深度值(即从相机到物体的距离),viewDir
可能是从屏幕位置到某个方向的向量,而screenPos.w可能保存的是齐次坐标的w分量,即投影后的w,可能在透视投影中为视图空间中的z值。所以viewDir/screenPos.w
可能是在归一化某个方向向量,或者进行透视校正。
关键分析:
-
深度处理机制:
UNITY_REVERSED_Z
处理不同图形API的深度缓冲差异- OpenGL平台需要特殊处理NDC范围(默认[-1,1] vs DirectX的[0,1])
-
正交投影计算:
- 使用逆视图投影矩阵(UNITY_MATRIX_I_VP)进行坐标变换
- 版本兼容性处理:针对Unity 7.3.1+的矩阵修正
- 通过NDC坐标直接计算世界空间位置
-
透视投影计算:
- 基于视图方向向量和线性深度值重建位置
screenPos.w
存储投影前的z分量,用于方向向量归一化- 通过相机位置进行坐标系转换
-
投影模式混合:
unity_OrthoParams.w
作为混合因子(0=透视,1=正交)- 动态切换投影模式的计算结果
数学原理:
- 透视投影:
worldPos = depth * (viewDirection) - cameraPos
- 正交投影:
worldPos = invVP * ndcCoord
- 深度重建:利用深度缓冲值反向计算视锥体内的原始位置
典型应用场景:
- 屏幕空间反射/折射
- 后期处理效果(如景深、SSAO)
- 粒子系统世界空间定位
- 动态贴花投影
该代码展示了Unity Shader中处理多投影模式的核心技术,通过条件编译和矩阵操作实现了跨平台的深度重建功能。