只用一张深度图就能还原每个像素对应的世界空间位置:用 NDC 坐标 + 逆 VP 矩阵反算。这是 SSAO、SSR、体积雾等所有屏幕空间效果的底层基础。
一、核心原理
当我们渲染一个 3D 场景时,GPU 会将顶点从世界空间 变换到屏幕空间 ,这个过程涉及 View 矩阵和 Projection 矩阵。深度重建的本质就是反向这个过程。

Pworld = (VP)-1 × Pndc
世界坐标 = 逆 View-Projection 矩阵 × 标准化设备坐标
为什么不用线性深度? 透视投影的非线性深度分布使得直接反算不可行。NDC 深度值 [0,1] 对应的是经过透视除法的齐次坐标,只有逆 VP 矩阵才能正确还原原始的 3D 位置。
二、完整实现代码
下面是一个完整的 Shader 实现,使用 URP 的内置函数和属性来重建世界坐标。
cs
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);
}
}
cs
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进行深度重建
}
}
cs
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 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 实现要点
cs
性能提示:逆 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 将为 null
SRP Batcher 兼容性
使用常量缓冲区传递矩阵时,需确保格式兼容以获得最佳性能
六、URP 配置要点
要在 URP 中使用深度重建功能,需要正确配置渲染管线资产:
1
打开 URP Asset
选中你的 URP Renderer Asset
2
启用 Depth Texture
勾选 "Depth Texture" 选项
代码中启用深度纹理(备选方案):
cs
// 在你的 Renderer 的 BeginCameraRendering 中
var cameraData = renderingData.cameraData;
if (cameraData.cameraType == CameraType.Game)
{
cameraData.camera.depthTextureMode |= DepthTextureMode.Depth;
}