3、深度图后处理入门:后处理管线架构、CommandBuffer与PostProcess Layer、第一个深度后处理Shader

各位同学,欢迎来到深度图后处理的第一课。

说实话,很多做渲染的同行,一提到「深度图」就觉得这是高端玩法。其实不然。深度图说白了就是一张记录了每个像素离相机有多远的灰度图。你想想看,有了这个距离信息,我们能做的事就太多了——景深、雾效、边缘光、扫描线效果……

但这一切的前提是:你得先把后处理管线搭起来。今天我们就从零开始,把这条「流水线」彻底讲透。

3.1 后处理管线架构:一张图看懂全流程

先别急着写代码。我习惯在动手前,先把整个流程在脑子里过一遍。后处理管线,说白了就是「渲染完场景后,再对最终画面做一次加工」的流水线。

下面这张图是我自己画的,帮你快速建立整体认知:

后处理管线架构图 阶段1:场景渲染 渲染管线输出颜色+深度 阶段2:深度图提取 CommandBuffer 或 _CameraDepthTexture 阶段3:后处理Shader 采样深度图,输出效果 输出 核心思路:在场景渲染完成后,插入一个「深度图采样 + 像素处理」的环节 关键组件: • Camera 组件:必须开启 Depth Texture 选项 • CommandBuffer:在渲染管线中插入自定义命令 • PostProcess Layer:Unity 官方后处理框架,管理 Effect 堆叠

嗯,这张图其实已经说得很清楚了。但我要强调一点:深度图不是自动就有的。你得主动告诉渲染管线:「嘿,把深度信息给我保留一份。」

3.2 CommandBuffer vs PostProcess Layer:我该用哪个?

这是新手最容易纠结的问题。我直接给你结论:

对比维度 CommandBuffer PostProcess Layer
灵活度 极高,可插入任意渲染阶段 中等,基于 Volume 框架
学习曲线 较陡,需理解管线事件 平缓,拖拽即可使用
性能开销 可控,只执行你写的代码 有框架本身的开销
适用场景 自定义渲染、模组级深度处理 标准后处理效果堆叠
💡 我的建议:如果你只是想做简单的颜色调整、Bloom,用 PostProcess Layer 就够了。但咱们这门课讲的是「模组级深度图后处理」,说白了就是要深入管线底层——所以我会重点讲 CommandBuffer 方案。

3.3 实战:搭建你的第一个深度后处理管线

好,理论说完了,咱们直接上手。我会用 Unity URP 来演示,但思路在 Built-in 管线里也通用。

3.3.1 第一步:开启深度纹理

在 URP 的 Render Pipeline Asset 里,找到 Depth Texture 选项,设为 On。这一步不做,后面全是白搭。

⚠️ 我曾经踩过的坑:有次我忘了开这个选项,折腾了一下午,深度图采样出来全是黑的。后来才发现——没开深度纹理,GPU 根本不会往 _CameraDepthTexture 里写数据。记住,这是第一步。

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
        }
    }
}
🔍 这里有个细节:深度值在非线性空间(0~1)里,近处变化大,远处变化小。你看到的画面应该是近处亮、远处暗。如果你看到一片纯白或纯黑,说明深度图没传进来——检查一下 CommandBuffer 里的纹理名字是否一致。

3.4 把 Shader 挂到后处理管线上

有了 Shader,怎么让它生效?两种方式:

  1. 用 PostProcess Layer:写一个继承自 PostProcessEffectRenderer 的类,把 Shader 塞进去。适合标准框架。
  2. 用 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 里释放。
💡 调试小技巧:在 Unity 的 Frame Debugger 里,你可以看到 CommandBuffer 插入的位置和执行的 Blit 操作。这是排查问题的利器,一定要学会用。

好了,这一章的内容就到这里。你学会了后处理管线的架构、CommandBuffer 和 PostProcess Layer 的区别,还亲手写了一个深度可视化 Shader。这些东西是后面所有高级效果的基础——景深、扫描线、边缘光,全都建立在今天的内容之上。


专注资料整理