GameTech/URP 渲染实战
源码 / Ch01/ScreenUV.shader
Ch01/ScreenUV.shader
// 第 1 章 案例 1.5:屏幕 UV 应该在哪里做除法
// 用屏幕 UV 采样一个程序化棋盘格。三种写法:
//   0 正确:顶点里输出未除 w 的 positionNDC,片元里再除(URP 的 positionNDC / 旧的 ComputeScreenPos 就是为此设计)
//   1 错误:顶点里先除 w,再交给默认的透视校正插值
//   2 也正确:顶点里先除 w,但用 noperspective 在屏幕空间线性插值
//   3 最省事:片元里直接用 SV_POSITION 算 GetNormalizedScreenSpaceUV
Shader "Cookbook/Ch01/ScreenUV"
{
    Properties
    {
        [Enum(DivideInFragment,0,DivideInVertex,1,DivideInVertexNoPerspective,2,FromSVPosition,3)] _Mode ("写法", Float) = 0
        _Cells ("竖直方向格数", Float) = 10
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }

        Pass
        {
            Name "ScreenUV"
            Tags { "LightMode"="UniversalForward" }
            Cull Off

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            CBUFFER_START(UnityPerMaterial)
                float _Mode;
                float _Cells;
            CBUFFER_END

            struct Attributes
            {
                float4 positionOS : POSITION;
            };

            struct Varyings
            {
                float4 positionCS  : SV_POSITION;
                float4 positionNDC : TEXCOORD0;                  // 齐次屏幕坐标 (x, y, z, w),xy 在 [0, w]
                float2 uvDivided   : TEXCOORD1;                  // 顶点里已经除过 w
                noperspective float2 uvDividedNP : TEXCOORD2;    // 同上,但按屏幕空间线性插值
            };

            Varyings vert(Attributes input)
            {
                Varyings output;
                VertexPositionInputs pos = GetVertexPositionInputs(input.positionOS.xyz);
                output.positionCS  = pos.positionCS;
                output.positionNDC = pos.positionNDC;
                output.uvDivided   = pos.positionNDC.xy / pos.positionNDC.w;
                output.uvDividedNP = pos.positionNDC.xy / pos.positionNDC.w;
                return output;
            }

            float Checker(float2 uv)
            {
                float aspect = _ScreenParams.x / _ScreenParams.y;
                float2 cell = floor(uv * float2(_Cells * aspect, _Cells));
                return fmod(abs(cell.x + cell.y), 2.0);
            }

            half4 frag(Varyings input) : SV_Target
            {
                float2 uv;
                int mode = (int)_Mode;
                if (mode == 0)      uv = input.positionNDC.xy / input.positionNDC.w;   // 插值之后再除
                else if (mode == 1) uv = input.uvDivided;
                else if (mode == 2) uv = input.uvDividedNP;
                else                uv = GetNormalizedScreenSpaceUV(input.positionCS);

                float k = Checker(uv);
                half3 col = lerp(half3(0.92, 0.92, 0.94), half3(0.12, 0.42, 0.56), k);
                return half4(col, 1);
            }
            ENDHLSL
        }
    }
}