3、深度图后处理入门:后处理管线架构、CommandBuffer与PostProcess Layer、第一个深度后处理Shader
各位同学,欢迎来到深度图后处理的第一课。
说实话,很多做渲染的同行,一提到「深度图」就觉得这是高端玩法。其实不然。深度图说白了就是一张记录了每个像素离相机有多远的灰度图。你想想看,有了这个距离信息,我们能做的事就太多了——景深、雾效、边缘光、扫描线效果……
但这一切的前提是:你得先把后处理管线搭起来。今天我们就从零开始,把这条「流水线」彻底讲透。
3.1 后处理管线架构:一张图看懂全流程
先别急着写代码。我习惯在动手前,先把整个流程在脑子里过一遍。后处理管线,说白了就是「渲染完场景后,再对最终画面做一次加工」的流水线。
下面这张图是我自己画的,帮你快速建立整体认知:
嗯,这张图其实已经说得很清楚了。但我要强调一点:深度图不是自动就有的。你得主动告诉渲染管线:「嘿,把深度信息给我保留一份。」
3.2 CommandBuffer vs PostProcess Layer:我该用哪个?
这是新手最容易纠结的问题。我直接给你结论:
| 对比维度 | CommandBuffer | PostProcess Layer |
|---|---|---|
| 灵活度 | 极高,可插入任意渲染阶段 | 中等,基于 Volume 框架 |
| 学习曲线 | 较陡,需理解管线事件 | 平缓,拖拽即可使用 |
| 性能开销 | 可控,只执行你写的代码 | 有框架本身的开销 |
| 适用场景 | 自定义渲染、模组级深度处理 | 标准后处理效果堆叠 |
3.3 实战:搭建你的第一个深度后处理管线
好,理论说完了,咱们直接上手。我会用 Unity URP 来演示,但思路在 Built-in 管线里也通用。
3.3.1 第一步:开启深度纹理
在 URP 的 Render Pipeline Asset 里,找到 Depth Texture 选项,设为 On。这一步不做,后面全是白搭。
3.3.2 第二步:用 CommandBuffer 获取深度图
我个人习惯把 CommandBuffer 的创建和销毁逻辑封装在一个 MonoBehaviour 里。这样好管理。
using UnityEngine;
using UnityEngine.Rendering;
public class DepthGrabber : MonoBehaviour
{
private CommandBuffer cmd;
private Camera cam;
void Start()
{
cam = GetComponent<Camera>();
cmd = new CommandBuffer();
cmd.name = "DepthGrabber";
// 在渲染不透明物体之后,获取深度图
int tempRT = Shader.PropertyToID("_TempDepthRT");
cmd.GetTemporaryRT(tempRT, -1, -1, 24, FilterMode.Point, RenderTextureFormat.Depth);
cmd.Blit(BuiltinRenderTextureType.CameraTarget, tempRT);
cmd.SetGlobalTexture("_MyDepthTexture", tempRT);
cam.AddCommandBuffer(CameraEvent.AfterForwardOpaque, cmd);
}
void OnDestroy()
{
if (cmd != null)
{
cam.RemoveCommandBuffer(CameraEvent.AfterForwardOpaque, cmd);
cmd.Release();
}
}
}
这段代码干了什么?说白了就是:在场景的不透明物体渲染完后,把当前的深度缓冲复制到一张临时 RT 里,然后存成全局纹理 _MyDepthTexture。这样你的 Shader 就能直接采样了。
3.3.3 第三步:写一个深度可视化 Shader
这是最激动人心的部分。我们来写一个最简单的 Shader,把深度图直接显示在屏幕上——这样你就能「看到」深度了。
Shader "Custom/DepthViewer"
{
Properties
{
_MainTex ("Base", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
// 注意:这里用我们刚才设置的全局纹理名
sampler2D _MyDepthTexture;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 采样深度图
float depth = tex2D(_MyDepthTexture, i.uv).r;
// 把深度映射到 0~1 之间显示
return fixed4(depth, depth, depth, 1);
}
ENDCG
}
}
}
3.4 把 Shader 挂到后处理管线上
有了 Shader,怎么让它生效?两种方式:
- 用 PostProcess Layer:写一个继承自
PostProcessEffectRenderer的类,把 Shader 塞进去。适合标准框架。 - 用 CommandBuffer 直接 Blit:在 CommandBuffer 里加一个
Blit命令,把屏幕内容用我们的 Shader 处理一遍。
我个人更推荐第二种,因为更直接、更可控。你想想看,CommandBuffer 就像是你自己在管线上「插了一脚」,想在哪插就在哪插。
// 在之前的 DepthGrabber 脚本里追加
Material depthMat = new Material(Shader.Find("Custom/DepthViewer"));
cmd.Blit(BuiltinRenderTextureType.CameraTarget, BuiltinRenderTextureType.CameraTarget, depthMat);
嗯,就这么简单。现在运行游戏,你应该能看到整个画面变成了灰度图——近处的物体亮,远处的暗。恭喜你,你的第一个深度后处理效果已经跑起来了。
3.5 避坑指南:我当年犯过的错
- 深度图是反的?检查一下你的投影矩阵。如果是透视投影,深度值在非线性空间里是「近大远小」的。想线性化?用
Linear01Depth函数。 - CommandBuffer 没生效?确认 Camera 的
depthTextureMode是否设置为DepthTextureMode.Depth。我当年漏了这一行,查了三天。 - 性能突然掉帧?看看你是不是每帧都在
Start里创建 CommandBuffer。正确的做法是在OnEnable里创建,OnDisable里释放。
好了,这一章的内容就到这里。你学会了后处理管线的架构、CommandBuffer 和 PostProcess Layer 的区别,还亲手写了一个深度可视化 Shader。这些东西是后面所有高级效果的基础——景深、扫描线、边缘光,全都建立在今天的内容之上。