案例 6.8DepthOnly 与 DepthNormals Pass入门
目标:弄清这两个 Pass 什么时候被调用、缺了会怎样,并写出可复用的实现。
除了颜色 Pass 和阴影 Pass,URP 还会在某些情况下单独渲染一遍场景的深度或深度+法线:
- DepthOnly:深度预渲染(Depth Prepass)。当某个功能需要相机深度纹理
_CameraDepthTexture、而又无法直接拷贝深度缓冲时(与平台、MSAA、渲染设置有关),URP 会先用所有物体的 DepthOnly Pass 画一遍深度。缺少这个 Pass 的物体不会出现在深度纹理里,软粒子、水的深浅、深度雾等效果会“看不见”它。 - DepthNormals:当有功能通过
ConfigureInput(ScriptableRenderPassInput.Normal)请求法线纹理_CameraNormalsTexture时触发,典型的就是 SSAO(Source 为 Depth Normals 时)、部分贴花模式和自定义的屏幕空间描边。这个 Pass 同时写深度和世界空间法线。
// DepthOnly Pass:只写深度。URP 做深度预渲染(Depth Prepass)、生成 _CameraDepthTexture 时会用到它。 #ifndef COOKBOOK_CH06_DEPTH_ONLY_PASS_INCLUDED #define COOKBOOK_CH06_DEPTH_ONLY_PASS_INCLUDED #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct DepthOnlyAttributes { float4 positionOS : POSITION; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct DepthOnlyVaryings { #if defined(_ALPHATEST_ON) float2 uv : TEXCOORD0; #endif float4 positionCS : SV_POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_OUTPUT_STEREO }; DepthOnlyVaryings DepthOnlyVertex(DepthOnlyAttributes input) { DepthOnlyVaryings output = (DepthOnlyVaryings)0; UNITY_SETUP_INSTANCE_ID(input); UNITY_TRANSFER_INSTANCE_ID(input, output); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); output.positionCS = TransformObjectToHClip(input.positionOS.xyz); #if defined(_ALPHATEST_ON) output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap); #endif return output; } half DepthOnlyFragment(DepthOnlyVaryings input) : SV_Target { UNITY_SETUP_INSTANCE_ID(input); UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); #if defined(_ALPHATEST_ON) clip(SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv).a * _BaseColor.a - _Cutoff); #endif // 深度由硬件写入;某些路径会把深度作为颜色写进 R 通道(Pass 里 ColorMask R),所以照 URP 的做法返回 z return input.positionCS.z; } #endif
// DepthOnly Pass:只写深度。URP 做深度预渲染(Depth Prepass)、生成 _CameraDepthTexture 时会用到它。(GLSL 版,对应 Ch06/DepthOnlyPass.hlsl) #if defined(_ALPHATEST_ON) VARYING vec2 uv; #endif #ifdef VERTEX void main() { gl_Position = TransformObjectToHClip(positionOS.xyz); #if defined(_ALPHATEST_ON) uv = TransformTex(texcoord0.xy, _BaseMap_ST); #endif } #endif #ifdef FRAGMENT void main() { #if defined(_ALPHATEST_ON) if (texture(_BaseMap, uv).a * _BaseColor.a - _Cutoff < 0.0) discard; #endif // 深度由硬件写入;某些路径会把深度作为颜色写进 R 通道(Pass 里 ColorMask R),所以照 URP 的做法返回 z fragColor = vec4(gl_FragCoord.z); } #endif
// DepthNormals Pass:同时写深度和世界空间法线到 _CameraNormalsTexture。 // SSAO(Depth Normals 模式)、贴花、部分自定义屏幕空间效果都依赖它。 #ifndef COOKBOOK_CH06_DEPTH_NORMALS_PASS_INCLUDED #define COOKBOOK_CH06_DEPTH_NORMALS_PASS_INCLUDED #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct DepthNormalsAttributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct DepthNormalsVaryings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 normalWS : TEXCOORD1; UNITY_VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_OUTPUT_STEREO }; DepthNormalsVaryings DepthNormalsVertex(DepthNormalsAttributes input) { DepthNormalsVaryings output = (DepthNormalsVaryings)0; UNITY_SETUP_INSTANCE_ID(input); UNITY_TRANSFER_INSTANCE_ID(input, output); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); output.positionCS = TransformObjectToHClip(input.positionOS.xyz); output.normalWS = TransformObjectToWorldNormal(input.normalOS); output.uv = input.texcoord; #if defined(_ALPHATEST_ON) output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap); #endif return output; } void DepthNormalsFragment(DepthNormalsVaryings input , out half4 outNormalWS : SV_Target0 #ifdef _WRITE_RENDERING_LAYERS , out float4 outRenderingLayers : SV_Target1 #endif ) { UNITY_SETUP_INSTANCE_ID(input); UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); #if defined(_ALPHATEST_ON) clip(SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv).a * _BaseColor.a - _Cutoff); #endif // URP 14 的 _CameraNormalsTexture 直接存归一化的世界空间法线(不做 0.5 偏移) outNormalWS = half4(NormalizeNormalPerPixel(input.normalWS), 0.0); #ifdef _WRITE_RENDERING_LAYERS // 开启“渲染层”且有贴花等需求时,URP 会让这个 Pass 顺便写出网格的渲染层 outRenderingLayers = float4(EncodeMeshRenderingLayer(GetMeshRenderingLayer()), 0, 0, 0); #endif } #endif
// DepthNormals Pass:同时写深度和世界空间法线到 _CameraNormalsTexture。(GLSL 版,对应 Ch06/DepthNormalsPass.hlsl) // SSAO(Depth Normals 模式)、贴花、部分自定义屏幕空间效果都依赖它。 VARYING vec2 uv; VARYING vec3 normalWS; #ifdef VERTEX void main() { gl_Position = TransformObjectToHClip(positionOS.xyz); normalWS = TransformObjectToWorldNormal(normalOS); uv = texcoord0.xy; #if defined(_ALPHATEST_ON) uv = TransformTex(texcoord0.xy, _BaseMap_ST); #endif } #endif #ifdef FRAGMENT void main() { #if defined(_ALPHATEST_ON) if (texture(_BaseMap, uv).a * _BaseColor.a - _Cutoff < 0.0) discard; #endif // URP 14 的 _CameraNormalsTexture 直接存归一化的世界空间法线(不做 0.5 偏移) // (网页版不支持“渲染层”贴图,_WRITE_RENDERING_LAYERS 那一路省略) fragColor = vec4(NormalizeNormalPerPixel(normalWS), 0.0); } #endif
两点说明:其一,DepthOnly 的片元返回 positionCS.z 并在 Pass 上声明 ColorMask R,与 URP 的 DepthOnlyPass.hlsl 一致——某些路径下深度会被写进一张颜色格式的纹理,这时 R 通道的值才是深度。其二,URP 14 的法线纹理直接存归一化的世界空间法线;只有 Deferred 开启 Accurate G-buffer normals 时才会用八面体编码(_GBUFFER_NORMALS_OCT),而 Lit 的 DepthNormals Pass 并不声明这个关键字。_WRITE_RENDERING_LAYERS 来自 RenderingLayers.hlsl(用 #include_with_pragmas 引入),开启渲染层并且贴花需要它时,这个 Pass 会顺便写出网格的渲染层。

UniversalForward 还是 UniversalForwardOnly?
URP 的 Deferred 路径会把有 UniversalGBuffer Pass 的材质写进 G-Buffer,再统一计算光照;没有 G-Buffer Pass 的材质,只有标记为 UniversalForwardOnly(或无标签的旧式 Pass)时,才会在 Deferred 的“Forward Only”通道里前向绘制。只标记 UniversalForward 又没有 G-Buffer Pass 的材质,在 Deferred 下根本不会被绘制。所以光照模型无法写进 G-Buffer 的自定义 Shader(Blinn-Phong、卡通、皮肤……)应该用 UniversalForwardOnly,它在 Forward / Forward+ 下与 UniversalForward 的行为完全相同。配套地,深度法线 Pass 要标 DepthNormalsOnly:Deferred 的深度法线预渲染只收集这个标签;Forward 下 DepthNormals 和 DepthNormalsOnly 都会被绘制,所以两者只能选一个。URP 自带的 Complex Lit 就是这样组织的。