一段根据片元深度值重建对应的深度坐标值的代码及其解释

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(可能是线性深度值)、viewDirscreenPos.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可能是在归一化某个方向向量,或者进行透视校正。

关键分析:

  1. 深度处理机制

    • UNITY_REVERSED_Z 处理不同图形API的深度缓冲差异
    • OpenGL平台需要特殊处理NDC范围(默认[-1,1] vs DirectX的[0,1])
  2. 正交投影计算

    • 使用逆视图投影矩阵(UNITY_MATRIX_I_VP)进行坐标变换
    • 版本兼容性处理:针对Unity 7.3.1+的矩阵修正
    • 通过NDC坐标直接计算世界空间位置
  3. 透视投影计算

    • 基于视图方向向量和线性深度值重建位置
    • screenPos.w 存储投影前的z分量,用于方向向量归一化
    • 通过相机位置进行坐标系转换
  4. 投影模式混合

    • unity_OrthoParams.w 作为混合因子(0=透视,1=正交)
    • 动态切换投影模式的计算结果

数学原理:

  • 透视投影worldPos = depth * (viewDirection) - cameraPos
  • 正交投影worldPos = invVP * ndcCoord
  • 深度重建:利用深度缓冲值反向计算视锥体内的原始位置

典型应用场景:

  • 屏幕空间反射/折射
  • 后期处理效果(如景深、SSAO)
  • 粒子系统世界空间定位
  • 动态贴花投影

该代码展示了Unity Shader中处理多投影模式的核心技术,通过条件编译和矩阵操作实现了跨平台的深度重建功能。

kumakoko avatar
kumakoko
pure coder
comments powered by Disqus