URP 渲染实战 / 第 16 章 透明、混合与模板测试 / 案例 16.2
案例 16.2ZTest / ZWrite / Cull 实验台入门
目标:把三个状态都做成材质属性,在“一面墙挡住一个纽结”的场景里观察不同组合。
Assets/Cookbook/Ch16/RenderStates.shader
// 深度/剔除状态实验台:ZTest、ZWrite、Cull 都是材质属性,可在 Inspector 里直接切换。 // 渲染状态写成 [属性名] 的形式,Unity 会在提交绘制时读取材质上的值。 Shader "Cookbook/Ch16/RenderStates" { Properties { _BaseColor ("Color", Color) = (0.9, 0.5, 0.2, 1) [HDR] _BackColor ("Back Face Color", Color) = (0.2, 0.6, 0.9, 1) [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 4 // LessEqual [Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 1 [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 2 // Back } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" } HLSLINCLUDE #include "Assets/Cookbook/Ch16/Ch16Common.hlsl" CBUFFER_START(UnityPerMaterial) half4 _BaseColor, _BackColor; float _ZTest, _ZWrite, _Cull; CBUFFER_END ENDHLSL Pass { Name "ForwardLit" Tags { "LightMode"="UniversalForwardOnly" } ZTest [_ZTest] ZWrite [_ZWrite] Cull [_Cull] HLSLPROGRAM #pragma vertex Ch16Vert #pragma fragment frag #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN #pragma multi_compile_fragment _ _SHADOWS_SOFT _SHADOWS_SOFT_LOW _SHADOWS_SOFT_MEDIUM _SHADOWS_SOFT_HIGH half4 frag(Varyings i, FRONT_FACE_TYPE face : FRONT_FACE_SEMANTIC) : SV_Target { bool front = IS_FRONT_VFACE(face, true, false); float3 N = normalize(i.normalWS) * (front ? 1.0 : -1.0); half3 albedo = front ? _BaseColor.rgb : _BackColor.rgb; return half4(SimpleLighting(albedo, N, i.positionWS, 0.4h), 1); } ENDHLSL } Pass { Name "ShadowCaster" Tags { "LightMode"="ShadowCaster" } ZWrite On ZTest LEqual ColorMask 0 Cull [_Cull] HLSLPROGRAM #pragma vertex vert #pragma fragment frag float4 vert(Attributes v) : SV_POSITION { return Ch16ShadowPositionCS(v.positionOS.xyz, v.normalOS); } half4 frag() : SV_Target { return 0; } ENDHLSL } } }
// 深度/剔除状态实验台:ZTest、ZWrite、Cull 都是材质属性,可在 Inspector 里直接切换。(GLSL 版) // 渲染状态写成 [属性名] 的形式,Unity 会在提交绘制时读取材质上的值。 Shader "Cookbook/Ch16/RenderStates" { SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" } GLSLINCLUDE #include "Ch16/Ch16Common.glsl" uniform vec4 _BaseColor, _BackColor; ENDGLSL Pass { Name "ForwardLit" Tags { "LightMode"="UniversalForwardOnly" } ZTest [_ZTest] ZWrite [_ZWrite] Cull [_Cull] GLSLPROGRAM #ifdef VERTEX void main() { Ch16Vert(); } #endif #ifdef FRAGMENT void main() { bool front = gl_FrontFacing; vec3 N = normalize(normalWS) * (front ? 1.0 : -1.0); vec3 albedo = front ? _BaseColor.rgb : _BackColor.rgb; fragColor = vec4(SimpleLighting(albedo, N, positionWS, 0.4), 1.0); } #endif ENDGLSL } Pass { Name "ShadowCaster" Tags { "LightMode"="ShadowCaster" } ZWrite On ZTest LEqual ColorMask 0 Cull [_Cull] GLSLPROGRAM #ifdef VERTEX void main() { gl_Position = Ch16ShadowPositionCS(positionOS.xyz, normalOS); } #endif #ifdef FRAGMENT void main() { fragColor = vec4(0.0); } #endif ENDGLSL } } }
属性用 Unity 自带的枚举类型声明,状态块里用方括号引用:
Assets/Cookbook/Ch16/RenderStates.shader(节选:props)
[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 4 // LessEqual [Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 1 [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 2 // Back
// 深度/剔除状态实验台:ZTest、ZWrite、Cull 都是材质属性,可在 Inspector 里直接切换。(GLSL 版) // 渲染状态写成 [属性名] 的形式,Unity 会在提交绘制时读取材质上的值。 Shader "Cookbook/Ch16/RenderStates" { SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" } GLSLINCLUDE #include "Ch16/Ch16Common.glsl" uniform vec4 _BaseColor, _BackColor; ENDGLSL Pass { Name "ForwardLit" Tags { "LightMode"="UniversalForwardOnly" } ZTest [_ZTest] ZWrite [_ZWrite] Cull [_Cull] GLSLPROGRAM #ifdef VERTEX void main() { Ch16Vert(); } #endif #ifdef FRAGMENT void main() { bool front = gl_FrontFacing; vec3 N = normalize(normalWS) * (front ? 1.0 : -1.0); vec3 albedo = front ? _BaseColor.rgb : _BackColor.rgb; fragColor = vec4(SimpleLighting(albedo, N, positionWS, 0.4), 1.0); } #endif ENDGLSL } Pass { Name "ShadowCaster" Tags { "LightMode"="ShadowCaster" } ZWrite On ZTest LEqual ColorMask 0 Cull [_Cull] GLSLPROGRAM #ifdef VERTEX void main() { gl_Position = Ch16ShadowPositionCS(positionOS.xyz, normalOS); } #endif #ifdef FRAGMENT void main() { fragColor = vec4(0.0); } #endif ENDGLSL } } }
Assets/Cookbook/Ch16/RenderStates.shader(节选:state)
拍摄时把纽结材质的队列设为 2010(m.renderQueue = 2010),保证墙先画。原因很关键:ZTest 比较的是“此刻”深度缓冲里的值。如果纽结先于墙绘制,深度缓冲里还没有墙,Always 和 Greater 就看不出任何区别。

第三幅图就是“X 光”的雏形:ZTest Greater 恰好选出了被遮挡的部分。不过它还有一个问题——纽结自己挡住自己的部分也满足 Greater,后面的模板测试小节会解决它。