【渲染流水线】[逐片元阶段]-[深度测试]以UnityURP为例

  • 深度测试是通过比较当前片元深度值与深度缓冲区值决定是否丢弃该片元。URP自2018年随Unity 2019.1推出后,逐步替代了传统内置管线,其深度测试机制在移动端和PC平台均采用更高效的GPU指令优化。

【从UnityURP开始探索游戏渲染】专栏-直达

技术演进历程

传统深度测试阶段(2017年前)

  • 基于Built-in RP的深度缓冲机制
  • 硬编码实现Z-buffer算法
  • 缺乏跨平台统一管理

URP初期版本(2017-2020)

  • 引入可编程渲染管线架构
  • 实现轻量级深度预通道(DepthPrepass)
  • 支持_CameraDepthTexture自动生成

现代URP体系(2021至今)

  • 深度与法线图联合渲染(DepthNormalsPass)
  • 多平台深度格式优化(k_DepthStencilFormat)
  • 模板测试深度集成(Stencil Op枚举)

深度测试命令使用样例

现代URP优化

  • 结合SRP Batcher减少SetPass Calls,深度测试与模板测试并行处理提升性能
  • 通过Z值比较实现三维空间遮挡关系
  • 可配置Less/Equal/Greater等比较模式‌

URP_ZTestExample.shader

  • 通过Properties面板可动态配置8种ZTest模式
  • 支持深度写入(ZWrite)开关控制
  • 完整包含URP标准HLSL语法结构
  • 使用CBUFFER实现材质参数序列化
  • 默认渲染队列设置为Geometry(2000)

关键参数说明

  • _ZTestMode对应深度测试枚举值:
    • 1 2 3
    • 4(默认) 5
    • 6 7 8:Always
  • _ZWrite控制深度缓冲写入(0=Off,1=On)
  • 包含基础纹理采样和颜色混合功能

URP_ZTestExample.shader

c 复制代码
Shader "Custom/URP_ZTestExample"
{
    Properties
    {
        _MainTex("Base Texture", 2D) = "white" {}
        _Color("Tint Color", Color) = (1,1,1,1)
        [Enum(UnityEngine.Rendering.CompareFunction)]
        _ZTestMode("ZTest Mode", Int) = 4 // 默认LEqual
        [Toggle]_ZWrite("ZWrite", Float) = 1
    }

    SubShader
    {
        Tags {
            "RenderType"="Opaque"
            "RenderPipeline"="UniversalRenderPipeline"
            "Queue"="Geometry"
        }

        Pass
        {
            // ShaderLab命令配置
            ZTest [_ZTestMode]
            ZWrite [_ZWrite]
            Cull Back
            Blend Off
            
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            CBUFFER_START(UnityPerMaterial)
                float4 _Color;
                int _ZTestMode;
                float _ZWrite;
            CBUFFER_END

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = IN.uv;
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv) * _Color;
                return col;
            }
            ENDHLSL
        }
    }
}

命令选项

通过ShaderLab命令ZTest可设置深度测试比较规则,支持以下选项:

  • Less:深度小于当前缓存则通过(默认值)‌
  • Greater:深度大于当前缓存则通过
  • LEqual:深度小于等于当前缓存则通过
  • GEqual:深度大于等于当前缓存则通过
  • Equal:深度等于当前缓存则通过
  • NotEqual:深度不等于当前缓存则通过
  • Always:始终通过(等同于关闭深度测试)‌

URP深度测试渲染管线深度相关变量

核心深度纹理变量

‌_CameraDepthTexture

  • 场景深度纹理,存储非线性深度值(0-1范围)

  • 启用要求:URP Asset中勾选 ‌Depth Texture‌ 选项

  • 着色器声明:

    c 复制代码
    hlsl
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"

_CameraOpaqueTexture

  • 不透明通道后的屏幕图像(含深度信息)
  • 启用要求:URP Asset中勾选 ‌Opaque Texture‌ 选项
  • 典型应用:透明物体折射/毛玻璃效果

辅助深度相关功能

深度重建函数(需包含 Core.hlsl

c 复制代码
hlsl
float linearDepth = LinearEyeDepth(depthSample, _ZBufferParams); // 转换为线性深度
float normalizedDepth = Linear01Depth(depthSample, _ZBufferParams); // [0,1]归一化

深度降采样控制(URP Asset设置)

  • Opaque Downsampling:调整不透明纹理分辨率(None/2x/4x)

注意事项

  • 默认不生成 _CameraDepthNormalsTexture,需通过 ‌Renderer Feature‌ 手动实现
  • 移动平台需谨慎使用深度纹理,可能影响性能
  • 深度测试模式通过 ZTest 指令动态调整(如 ZTest Greater

核心应用场景

水体交互效果

实现原理

  • 通过深度差计算水面淹没区域

关键技术

  • 观察空间坐标转换

性能优化

  • 半透明队列+深度写入关闭

代码举例 WaterDepth.shader

  • 实现透明水体的深度效果,包含深度纹理采样和透明度计算。
  • 包含深度纹理声明和采样
  • 支持UV变换和材质参数序列化
  • 保持原Shader的透明混合效果
c 复制代码
Shader "Custom/WaterDepth"
{
    Properties
    {
        [MainTexture] _MainTex("Base (RGB)", 2D) = "white" {}
        _DepthFactor("Depth Factor", Range(0,1)) = 0.5
    }

    SubShader
    {
        Tags 
        {
            "Queue"="Transparent"
            "RenderType"="Transparent"
            "RenderPipeline"="UniversalRenderPipeline"
        }

        Pass
        {
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);

            CBUFFER_START(UnityPerMaterial)
                float4 _MainTex_ST;
                float _DepthFactor;
            CBUFFER_END

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float4 screenPos : TEXCOORD0;
                float2 uv : TEXCOORD1;
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.screenPos = ComputeScreenPos(OUT.positionHCS);
                OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex);
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
                float depth = SampleSceneDepth(screenUV);
                depth = LinearEyeDepth(depth, _ZBufferParams);
                float sceneZ = depth - IN.screenPos.w;
                float waterDepth = saturate(sceneZ * _DepthFactor);

                half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
                col.a = waterDepth;
                return col;
            }
            ENDHLSL
        }
    }
}

遮挡剔除优化

实现原理

  • Early-Z技术预判

关键技术

  • DepthPrepass优先机制

性能优化

  • 实例化批处理

代码举例 DepthOfField.shader

  • 实现URP后处理景深效果
  • 包含深度纹理采样
  • 支持焦点距离和模糊强度调节
  • 保持原Shader的景深计算逻辑
  • 采用URP的深度采样API
c 复制代码
Shader "Hidden/DepthOfField"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalRenderPipeline" }
        
        Cull Off 
        ZWrite Off 
        ZTest Always

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);

            CBUFFER_START(UnityPerMaterial)
                float _FocusDistance;
                float _BlurSize;
            CBUFFER_END

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = IN.uv;
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                float depth = SampleSceneDepth(IN.uv);
                depth = Linear01Depth(depth, _ZBufferParams);
                
                float blur = saturate(abs(depth - _FocusDistance) * _BlurSize);
                half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
                col.rgb = lerp(col.rgb, col.rgb * 0.5, blur);
                return col;
            }
            ENDHLSL
        }
    }
}

景深特效系统

实现原理

  • 线性深度值插值计算

关键技术

  • SAMPLE_DEPTH_TEXTURE宏

性能优化

  • 降采样+高斯模糊迭代

代码举例 StencilDepth.shader

  • 实现URP模板测试功能
  • 支持模板缓冲测试
  • 保留原Shader的纹理采样功能
  • 采用CBUFFER管理材质参数
c 复制代码
Shader "Custom/StencilDepth"
{
    Properties
    {
        [MainTexture] _MainTex("Texture", 2D) = "white" {}
        _StencilRef("Stencil Ref", Int) = 1
    }

    SubShader
    {
        Tags 
        {
            "Queue"="Geometry"
            "RenderPipeline"="UniversalRenderPipeline"
        }

        Stencil
        {
            Ref [_StencilRef]
            Comp Less
            Pass Replace
        }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);

            CBUFFER_START(UnityPerMaterial)
                float4 _MainTex_ST;
                int _StencilRef;
            CBUFFER_END

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex);
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
            }
            ENDHLSL
        }
    }
}

深度测试优化建议

格式选择

  • 移动端使用16-bit深度(k_DepthBufferBits)
  • PC端推荐32-bit精度

渲染策略

静态场景启用DepthPrepass

  • URP Asset配置

    启用深度纹理生成:

    Project Settings > Graphics > URP Global Settings → 勾选‌Depth Texture‌选项。

  • Renderer Data设置

    在使用的Renderer Asset(如UniversalRenderer_Forward)中:

    → 添加‌SSAO效果‌(Screen Space Ambient Occlusion)

    → 将SSAO的‌Source属性设为Depth

    此操作强制URP启用DepthPrepass通道渲染静态物体深度到_CameraDepthTexture

    • 性能影响 ‌DepthPrepass增加Draw Call,建议静态物体使用‌Batching静态合批
    • 启用静态合批全局设置‌
      • 路径 ‌:Edit > Project Settings > Player
      • 操作 ‌:在Other Settings面板中勾选‌Static Batching‌选项
      • 作用‌:允许Unity在构建时合并静态物体的网格数据,减少运行时Draw Call数量
    • 标记静态物体‌
      • 选中场景中的静态物体
      • 在Inspector窗口右上角点击‌Static‌下拉菜单
      • 勾选‌Batching Static‌选项(若仅需合批可不勾选其他Static选项)
      • 注意‌:标记为静态的物体将无法在运行时移动,否则会导致合批失效
    • Shader兼容性检查‌
      • 要求 ‌:静态物体需使用‌相同Shader变体‌,且材质属性结构一致
      • 验证 ‌:通过Frame Debugger检查合批效果,确认是否存在‌SRP Batch ‌或‌Static Batch‌条目
      • SRP Batcher优先级‌:若同时启用SRP Batcher,其优先级高于静态合批,需确保Shader代码兼容SRP Batcher(如避免使用MaterialPropertyBlock)
      • GPU Instancing‌:对重复静态物体(如植被)可启用GPU Instancing,进一步减少Draw Call
    • 性能验证与调试‌
      • 工具 ‌:使用Window > Analysis > Frame Debugger
        • 检查‌DepthPrepass‌通道的Draw Call数量
        • 确认静态物体是否合并为‌StaticBatch‌条目
      • 指标 ‌:重点关注‌SetPass Call‌的减少情况,而非仅Draw Call数量
    • 注意事项
      • 平台差异 ‌:OpenGL等平台需处理深度值范围(UNITY_REVERSED_Z
      • 动态物体‌:若场景含动态物体,需通过CopyDepth模式单独处理其深度,避免与静态合批冲突

动态物体使用CopyDepth模式

  • Shader队列要求

    动态物体Shader需使用‌不透明渲染队列‌:

    c 复制代码
    Tags {
      "Queue"="Geometry"  // 半透明队列无法使用深度图
      "RenderType"="Opaque"
    }
  • 深度采样声明

    在动态物体的Shader中显式声明深度纹理:

    c 复制代码
    hlsl
    TEXTURE2D(_CameraDepthTexture);
    SAMPLER(sampler_CameraDepthTexture);
  • 摄像机设置

    确保动态物体所在摄像机的渲染路径:

    c 复制代码
    csharpCopy Code
    var cameraData = camera.GetUniversalAdditionalCameraData();
    cameraData.requiresDepthTexture = true;// 强制深度纹理可用
  • RenderPass优先级

    DepthPrepass默认在‌阴影渲染后执行 ‌,优先于CopyDepth Pass。动态物体深度通过后续的CopyDepth Pass复制到同一_CameraDepthTexture

验证与调试‌

  • Frame Debugger检查

    开启Window > Analysis > Frame Debugger

    → 确认存在‌DepthPrepass‌通道(静态物体深度)

    → 检查‌CopyDepth‌通道是否处理动态物体深度

  • 深度值测试

    在Shader中输出线性深度验证:

    c 复制代码
    hlsl
    float depth = SampleSceneDepth(uv);
    depth = Linear01Depth(depth, _ZBufferParams);
    return float4(depth.xxx, 1); // 灰度图显示深度

注意事项

  • 平台兼容性 ‌OpenGL平台需特殊处理深度值范围(UNITY_REVERSED_Z宏判断)。‌

内存控制

  • 按需开启_CameraDepthTexture
  • 避免多Pass重复采样

【从UnityURP开始探索游戏渲染】专栏-直达

(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)

相关推荐
Thomas_YXQ1 天前
Unity3D编辑器扩展-物体批量替换设置材质
游戏·unity·编辑器·游戏引擎·材质
雪下的新火1 天前
Unity-HDRP场景搭建-那山
经验分享·笔记·unity·游戏引擎·场景搭建
郝学胜-神的一滴2 天前
深度解析游戏引擎中的相机:视图矩阵
程序人生·unity·矩阵·游戏引擎·godot·图形渲染·虚幻
谷宇.2 天前
【Unity3D实例-功能-拔枪】角色拔枪(三)IK的使用-紧握武器
游戏·unity·c#·unity3d·游戏开发·游戏编程·steam
SmalBox3 天前
【渲染流水线】[逐片元阶段]-[模版测试]以UnityURP为例
unity·渲染
小蜗 strong3 天前
unity中实现机械臂自主运动
unity·游戏引擎
★YUI★3 天前
学习游戏制作记录(制作系统与物品掉落系统)8.16
学习·游戏·ui·unity·c#
SmalBox4 天前
【渲染流水线】[逐片元阶段]-[透明度测试]以UnityURP为例
unity·渲染
三只坚果4 天前
blender制作动画导入unity两种方式
unity·游戏引擎·blender