GameTech/URP 渲染实战
源码 / Ch03/C03_09_ScrollMigrated.shader
Ch03/C03_09_ScrollMigrated.shader
// 案例 3.9:把一个 Built-in 管线的“滚动贴图 Unlit”迁移到 URP。
// 对照正文里的 Built-in 原版逐行看:每一处改动都对应对照表里的一行。
Shader "Cookbook/Ch03/ScrollMigrated"
{
    Properties
    {
        // _MainTex → _BaseMap,_Color → _BaseColor:沿用 URP 命名,换 Shader 时材质参数能自动对上
        [MainTexture] _BaseMap ("Texture", 2D) = "white" {}
        [MainColor]   _BaseColor ("Tint", Color) = (1, 1, 1, 1)
        _ScrollSpeed ("Scroll Speed (xy)", Vector) = (0.2, 0, 0, 0)
        // 额外的手动偏移:编辑器里静帧截图或用动画/脚本驱动时用
        _ScrollOffset ("Scroll Offset (xy)", Vector) = (0, 0, 0, 0)
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

        CBUFFER_START(UnityPerMaterial)
            float4 _BaseMap_ST;
            half4  _BaseColor;
            float4 _ScrollSpeed;
            float4 _ScrollOffset;
        CBUFFER_END

        TEXTURE2D(_BaseMap);
        SAMPLER(sampler_BaseMap);
        ENDHLSL

        Pass
        {
            Name "Unlit"
            // Unlit 用 UniversalForwardOnly:切到 Deferred 渲染路径也不会消失(见正文 3.4 节)
            Tags { "LightMode"="UniversalForwardOnly" }

            HLSLPROGRAM                                  // CGPROGRAM → HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog

            struct Attributes                            // appdata → Attributes
            {
                float4 positionOS : POSITION;            // vertex → positionOS
                float2 uv         : TEXCOORD0;
                half4  color      : COLOR;               // fixed4 → half4(URP 不再有 fixed)
            };

            struct Varyings                              // v2f → Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv         : TEXCOORD0;
                half4  color      : COLOR;
                float  fogFactor  : TEXCOORD1;           // UNITY_FOG_COORDS(1) → 自己声明
            };

            Varyings vert (Attributes input)
            {
                Varyings output;
                // UnityObjectToClipPos → TransformObjectToHClip
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                // _Time.y 的含义与 Built-in 相同:游戏开始后的秒数
                float2 scroll = _ScrollSpeed.xy * _Time.y + _ScrollOffset.xy;
                output.uv = TRANSFORM_TEX(input.uv, _BaseMap) + scroll;
                output.color = input.color;
                // UNITY_TRANSFER_FOG(o, o.pos) → ComputeFogFactor
                output.fogFactor = ComputeFogFactor(output.positionCS.z);
                return output;
            }

            half4 frag (Varyings input) : SV_Target
            {
                // tex2D(_MainTex, uv) → SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv)
                half4 col = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor * input.color;
                // UNITY_APPLY_FOG(i.fogCoord, col) → MixFog
                col.rgb = MixFog(col.rgb, input.fogFactor);
                return half4(col.rgb, 1);
            }
            ENDHLSL
        }

        // Built-in 版本靠 Fallback 借来阴影 Pass;URP 里自己写一个最简的 ShadowCaster
        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode"="ShadowCaster" }
            ZWrite On
            ColorMask 0

            HLSLPROGRAM
            #pragma vertex ShadowVert
            #pragma fragment ShadowFrag
            #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW
            // Shadows.hlsl 用到了 CommonMaterial.hlsl 里的 LerpWhiteTo,单独引用时要先包含它
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"

            float3 _LightDirection;
            float3 _LightPosition;

            struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; };
            struct Varyings   { float4 positionCS : SV_POSITION; };

            Varyings ShadowVert (Attributes input)
            {
                Varyings output;
                float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
                float3 normalWS   = TransformObjectToWorldNormal(input.normalOS);
            #if defined(_CASTING_PUNCTUAL_LIGHT_SHADOW)
                float3 lightDirectionWS = normalize(_LightPosition - positionWS);
            #else
                float3 lightDirectionWS = _LightDirection;
            #endif
                output.positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, lightDirectionWS));
            #if UNITY_REVERSED_Z
                output.positionCS.z = min(output.positionCS.z, UNITY_NEAR_CLIP_VALUE);
            #else
                output.positionCS.z = max(output.positionCS.z, UNITY_NEAR_CLIP_VALUE);
            #endif
                return output;
            }

            half4 ShadowFrag (Varyings input) : SV_Target { return 0; }
            ENDHLSL
        }

        Pass
        {
            Name "DepthOnly"
            Tags { "LightMode"="DepthOnly" }
            ZWrite On
            ColorMask R

            HLSLPROGRAM
            #pragma vertex DepthVert
            #pragma fragment DepthFrag

            struct Attributes { float4 positionOS : POSITION; };
            struct Varyings   { float4 positionCS : SV_POSITION; };

            Varyings DepthVert (Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                return output;
            }

            half DepthFrag (Varyings input) : SV_Target { return input.positionCS.z; }
            ENDHLSL
        }
    }
}