Unity Shader 深度重建世界坐标

张开发
2026/4/21 8:17:27 15 分钟阅读

分享文章

Unity Shader 深度重建世界坐标
只用一张深度图就能还原每个像素对应的世界空间位置用 NDC 坐标 逆 VP 矩阵反算。这是 SSAO、SSR、体积雾等所有屏幕空间效果的底层基础。一、核心原理当我们渲染一个 3D 场景时GPU 会将顶点从世界空间变换到屏幕空间这个过程涉及 View 矩阵和 Projection 矩阵。深度重建的本质就是反向这个过程。Pworld (VP)-1 × Pndc世界坐标 逆 View-Projection 矩阵 × 标准化设备坐标为什么不用线性深度透视投影的非线性深度分布使得直接反算不可行。NDC 深度值 [0,1] 对应的是经过透视除法的齐次坐标只有逆 VP 矩阵才能正确还原原始的 3D 位置。二、完整实现代码下面是一个完整的 Shader 实现使用 URP 的内置函数和属性来重建世界坐标。using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public class DepthReconstructionFeature : ScriptableRendererFeature DepthReconstructionPass m_ScriptablePass; public override void Create() { m_ScriptablePass new DepthReconstructionPass(); m_ScriptablePass.renderPassEvent RenderPassEvent.AfterRenderingOpaques; } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { m_ScriptablePass.Setup(renderer.cameraDepthTexture); renderer.EnqueuePass(m_ScriptablePass); } }using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public class DepthReconstructionFeature : ScriptableRendererFeature DepthReconstructionPass m_ScriptablePass; public override void Create() { m_ScriptablePass new DepthReconstructionPass(); m_ScriptablePass.renderPassEvent RenderPassEvent.AfterRenderingOpaques; } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { m_ScriptablePass.Setup(renderer.cameraDepthTexture); renderer.EnqueuePass(m_ScriptablePass); } } DepthReconstructionPass.cs using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public class DepthReconstructionPass : ScriptableRenderPass private Shader m_Shader; private Material m_Material; private RTHandle m_DepthTexture; public void Setup(RTHandle depthTexture) { m_DepthTexture depthTexture; m_Shader Shader.Find(Hidden/DepthReconstruction); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (m_Shader null) return; if (m_Material null) m_Material new Material(m_Shader); ref CameraData cameraData ref renderingData.cameraData; Matrix4x4 invVP cameraData.camera.projectionMatrix * cameraData.camera.worldToCameraMatrix; invVP invVP.inverse; m_Material.SetMatrix(_InvVP矩阵, invVP); m_Material.SetTexture(_CameraDepthTexture, m_DepthTexture); // 在此处绘制全屏Quad进行深度重建 } }Shader Hidden/DepthReconstruction Properties { _MainTex (Screen Texture, 2D) white {} } CGINCLUDE #include UnityCG.cginc // // 核心方法从深度图重建世界坐标 // float3 ReconstructWorldPosition(float2 uv, float rawDepth) { // 步骤1构建 NDC 坐标 // 将屏幕UV转换为NDC空间 [-1, 1] float2 ndc uv * 2.0 - 1.0; // 步骤2构造齐次坐标 // NDC_z 已经经过透视除法需要恢复为 clip space float4 clipPos float4(ndc.x, ndc.y, rawDepth * 2.0 - 1.0, 1.0); // 步骤3逆变换到世界空间 // 乘以逆 VP 矩阵 float4 worldPos mul(_InvVP, clipPos); // 步骤4透视除法 // w 分量保存原始深度信息除以它得到真实坐标 worldPos / worldPos.w; return worldPos.xyz; } ENDCG三、矩阵变换详解理解 VP 矩阵及其逆矩阵是掌握深度重建的关键。下面的流程图展示了完整的变换链路1世界矩阵 (M)模型自身变换2视图矩阵 (V)相机坐标系变换3投影矩阵 (P)透视/正交投影性能提示逆 VP 矩阵可以在 C# 端预计算并传递给 Shader避免在 GPU 上进行昂贵的矩阵求逆运算。矩阵作用输入空间输出空间View (V)将世界坐标转换到相机视角World SpaceView SpaceProjection (P)将视图坐标投影到裁剪空间View SpaceClip SpaceVP组合变换一步到位World SpaceClip Space(VP)⁻¹逆向重建世界坐标NDC SpaceWorld Space四、应用场景深度重建是众多屏幕空间技术的基石。掌握这项技术后你可以实现以下效果SSAOScreen Space Ambient Occlusion屏幕空间环境光遮蔽SSRScreen Space Reflections屏幕空间反射体积雾Volumetric Fog基于深度的雾效计算SSAO 实现要点性能提示逆 VP 矩阵可以在 C# 端预计算并传递给 Shader避免在 GPU 上进行昂贵的矩阵求逆运算。 矩阵 作用 输入空间 输出空间 View (V) 将世界坐标转换到相机视角 World Space View Space Projection (P) 将视图坐标投影到裁剪空间 View Space Clip Space VP 组合变换一步到位 World Space Clip Space (VP)⁻¹ 逆向重建世界坐标 NDC Space World Space 四、应用场景 深度重建是众多屏幕空间技术的基石。掌握这项技术后你可以实现以下效果 SSAO Screen Space Ambient Occlusion 屏幕空间环境光遮蔽 SSR Screen Space Reflections 屏幕空间反射 体积雾 Volumetric Fog 基于深度的雾效计算 SSAO 实现要点 SSAO.frag (片段着色器) // 采样周围多个点进行深度比较 float CalculateAO(float2 uv, float3 normal) { float ao 0.0; // 获取当前像素的世界坐标 float depth SAMPLE_TEXTURE2D(_CameraDepthTexture, uv); float3 currentPos ReconstructWorldPosition(uv, depth); // 在半球方向采样多个点 for (int i 0; i 16; i) { float3 sampleDir GetHemisphereSample(i, normal); float3 samplePos currentPos sampleDir * radius; // 将采样点投影回屏幕空间 float2 sampleUV ProjectToScreen(samplePos); float sampleDepth SAMPLE_TEXTURE2D(_CameraDepthTexture, sampleUV); // 深度差异决定遮蔽程度 float rangeCheck smoothstep(0.0, 1.0, radius / abs(currentPos.z - samplePos.z)); ao (samplePos.z currentPos.z ? 1.0 : 0.0) * rangeCheck; } return 1.0 - (ao / 16.0); }五、关键要点总结NDC 坐标转换uv * 2.0 - 1.0 是将 [0,1] 范围映射到 [-1,1] 的标准操作透视除法不可省略w 分量保存了原始深度信息必须除以 w 才能得到正确的世界坐标逆矩阵计算时机建议在 C# 端计算逆矩阵并通过 SetMatrix 传递避免 GPU 端的矩阵求逆开销深度纹理格式确保在 URP Asset 中开启 Depth Texture否则 m_DepthTexture 将为 nullSRP Batcher 兼容性使用常量缓冲区传递矩阵时需确保格式兼容以获得最佳性能六、URP 配置要点要在 URP 中使用深度重建功能需要正确配置渲染管线资产1打开 URP Asset选中你的 URP Renderer Asset2启用 Depth Texture勾选 Depth Texture 选项代码中启用深度纹理备选方案// 在你的 Renderer 的 BeginCameraRendering 中 var cameraData renderingData.cameraData; if (cameraData.cameraType CameraType.Game) { cameraData.camera.depthTextureMode | DepthTextureMode.Depth; }

更多文章