- 深度测试是通过比较当前片元深度值与深度缓冲区值决定是否丢弃该片元。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 选项
-
着色器声明:
chlsl #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模式单独处理其深度,避免与静态合批冲突
- 平台差异 :OpenGL等平台需处理深度值范围(
动态物体使用CopyDepth模式
-
Shader队列要求
动态物体Shader需使用不透明渲染队列:
cTags { "Queue"="Geometry" // 半透明队列无法使用深度图 "RenderType"="Opaque" }
-
深度采样声明
在动态物体的Shader中显式声明深度纹理:
chlsl TEXTURE2D(_CameraDepthTexture); SAMPLER(sampler_CameraDepthTexture);
-
摄像机设置
确保动态物体所在摄像机的渲染路径:
ccsharpCopy Code var cameraData = camera.GetUniversalAdditionalCameraData(); cameraData.requiresDepthTexture = true;// 强制深度纹理可用
-
RenderPass优先级
DepthPrepass默认在阴影渲染后执行 ,优先于CopyDepth Pass。动态物体深度通过后续的CopyDepth Pass复制到同一
_CameraDepthTexture
。
验证与调试
-
Frame Debugger检查
开启
Window > Analysis > Frame Debugger
:→ 确认存在DepthPrepass通道(静态物体深度)
→ 检查CopyDepth通道是否处理动态物体深度
-
深度值测试
在Shader中输出线性深度验证:
chlsl float depth = SampleSceneDepth(uv); depth = Linear01Depth(depth, _ZBufferParams); return float4(depth.xxx, 1); // 灰度图显示深度
注意事项
- 平台兼容性 OpenGL平台需特殊处理深度值范围(
UNITY_REVERSED_Z
宏判断)。
内存控制
- 按需开启_CameraDepthTexture
- 避免多Pass重复采样
【从UnityURP开始探索游戏渲染】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)