GameTech/URP 渲染实战

案例 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)
ZTest [_ZTest]
ZWrite [_ZWrite]
Cull [_Cull]
ZTest [_ZTest]
ZWrite [_ZWrite]
Cull [_Cull]

拍摄时把纽结材质的队列设为 2010(m.renderQueue = 2010),保证墙先画。原因很关键:ZTest 比较的是“此刻”深度缓冲里的值。如果纽结先于墙绘制,深度缓冲里还没有墙,Always 和 Greater 就看不出任何区别。

图 16-4从左到右:ZTest LEqual(默认,墙挡住纽结);ZTest Always(无视深度,纽结画在墙前面);ZTest Greater(只在“已经有更近的东西”处通过,于是只剩被墙挡住的部分);Cull Front(只画背面,看到的是纽结管子的内壁,用另一种颜色表示)

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