URP 渲染实战 / 第 2 章 URP 架构与一帧的流程 / 案例 2.5
案例 2.5同一个 Shader 的三个 Pass,谁会被画?进阶
目标:写一个有三个 Pass 的 Shader:UniversalForward 做正常着色、一个不写 LightMode 的 Pass 做外描边、一个 LightMode 为自定义名字 CookbookXRay 的 Pass 做“透过墙看到”的 X 光效果,然后验证 URP 会画哪些。
Assets/Cookbook/Ch02/LightModeDemo.shader(节选:passes)
Pass { Name "Forward" Tags { "LightMode"="UniversalForward" } HLSLPROGRAM #pragma vertex VertDefault #pragma fragment Frag half4 Frag(Varyings input) : SV_Target { float3 n = normalize(input.normalWS); Light light = GetMainLight(); half3 diffuse = light.color * saturate(dot(n, light.direction)); half3 ambient = SampleSH(n); return half4(_BaseColor.rgb * (diffuse + ambient), 1); } ENDHLSL } Pass { Name "Outline" // 没有 LightMode 标签 → URP 视为 "SRPDefaultUnlit" Cull Front HLSLPROGRAM #pragma vertex Vert #pragma fragment Frag Varyings Vert(Attributes input) { Varyings output; // 沿法线把顶点往外推,只画背面,就得到一圈“外壳”描边(第 14 章细讲) float3 positionWS = TransformObjectToWorld(input.positionOS.xyz); float3 normalWS = TransformObjectToWorldNormal(input.normalOS); output.positionCS = TransformWorldToHClip(positionWS + normalWS * _OutlineWidth); output.normalWS = normalWS; return output; } half4 Frag(Varyings input) : SV_Target { return _OutlineColor; } ENDHLSL } Pass { Name "XRay" Tags { "LightMode"="CookbookXRay" } // 只在“被挡住”的地方画:深度比已有深度更远才通过 ZTest Greater ZWrite Off Blend SrcAlpha OneMinusSrcAlpha HLSLPROGRAM #pragma vertex VertDefault #pragma fragment Frag half4 Frag(Varyings input) : SV_Target { return _XRayColor; } ENDHLSL }
Pass { Name "Forward" Tags { "LightMode"="UniversalForward" } GLSLPROGRAM #ifdef VERTEX void main() { VertDefault(); } #endif #ifdef FRAGMENT void main() { vec3 n = normalize(normalWS); Light light = GetMainLight(); vec3 diffuse = light.color * saturate(dot(n, light.direction)); vec3 ambient = SampleSH(n); fragColor = vec4(_BaseColor.rgb * (diffuse + ambient), 1); } #endif ENDGLSL } Pass { Name "Outline" // 没有 LightMode 标签 → URP 视为 "SRPDefaultUnlit" Cull Front GLSLPROGRAM #ifdef VERTEX void main() { // 沿法线把顶点往外推,只画背面,就得到一圈“外壳”描边(第 14 章细讲) vec3 positionWS = TransformObjectToWorld(positionOS.xyz); vec3 nWS = TransformObjectToWorldNormal(normalOS); gl_Position = TransformWorldToHClip(positionWS + nWS * _OutlineWidth); normalWS = nWS; } #endif #ifdef FRAGMENT void main() { fragColor = _OutlineColor; } #endif ENDGLSL } Pass { Name "XRay" Tags { "LightMode"="CookbookXRay" } // 只在“被挡住”的地方画:深度比已有深度更远才通过 ZTest Greater ZWrite Off Blend SrcAlpha OneMinusSrcAlpha GLSLPROGRAM #ifdef VERTEX void main() { VertDefault(); } #endif #ifdef FRAGMENT void main() { fragColor = _XRayColor; } #endif ENDGLSL }
拍摄脚本分三格:第一格用 Material.SetShaderPassEnabled("SRPDefaultUnlit", false) 关掉描边 Pass(按 LightMode 关闭);第二格打开它;第三格再给 Renderer 加一个 URP 自带的 RenderObjects Feature,让它在 AfterRenderingOpaques 时点名绘制 LightMode 为 CookbookXRay 的 Pass。对应的编辑器操作是:在 Universal Renderer Data 上 ,Event 选 AfterRenderingOpaques,Filters 里 Queue 选 Opaque、Layer Mask 选 Everything,LightMode Tags 填 CookbookXRay。

右图的 X 光 Pass 用了 ZTest Greater:只有当它比深度缓冲里已有的深度“更远”(被挡住)时才通过,于是只有墙后的部分被染成蓝色。这个效果第 16 章还会用模板测试做得更精细。这里的重点是理解:LightMode 是 Shader 与管线之间的“接头暗号”。URP 认识的暗号,它会自动在对应的时机画;不认识的,就需要你用 Feature 去点名。
本案例出自《URP 渲染实战》第 2 章「URP 架构与一帧的流程」。在章节中阅读上下文 →