GameTech/URP 渲染实战

案例 3.9迁移实战:把 Built-in 的滚动贴图 Shader 搬到 URP进阶

目标:拿一个典型的 Built-in 管线 Unlit Shader(滚动贴图 + 顶点色 + 雾),逐行改成 URP 写法。很多项目升级 URP 时,最多的工作量就是这类“小 Shader”。下面是 Built-in 原版(它在 URP 工程里不能正确工作,仅作对照):

Built-in 原版(对照用)
Shader "Legacy/ScrollUnlit"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        _ScrollSpeed ("Scroll Speed (xy)", Vector) = (0.2, 0, 0, 0)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #include "UnityCG.cginc"

            struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; fixed4 color : COLOR; };
            struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; fixed4 color : COLOR; UNITY_FOG_COORDS(1) };

            sampler2D _MainTex; float4 _MainTex_ST;
            fixed4 _Color; float4 _ScrollSpeed;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex) + _ScrollSpeed.xy * _Time.y;
                o.color = v.color;
                UNITY_TRANSFER_FOG(o, o.pos);
                return o;
            }
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color * i.color;
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
    Fallback "Unlit/Texture"
}

URP 版本的完整文件是 Ch03/C03_09_ScrollMigrated.shader,颜色 Pass 如下,每一处改动都在注释里标出了对应的 Built-in 写法:

Assets/Cookbook/Ch03/C03_09_ScrollMigrated.shader(节选:pass)
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
}
Pass
{
    Name "Unlit"
    // Unlit 用 UniversalForwardOnly:切到 Deferred 渲染路径也不会消失(见正文 3.4 节)
    Tags { "LightMode"="UniversalForwardOnly" }

    GLSLPROGRAM                                  // CGPROGRAM → HLSLPROGRAM(网页版:GLSLPROGRAM)
    #pragma multi_compile_fog

    // appdata → Attributes(网页版:固定名字的顶点输入 positionOS / texcoord0 / color)
    // v2f → Varyings(网页版:逐个 VARYING)
    VARYING vec2 uv;
    VARYING vec4 vColor;                         // fixed4 → half4(URP 不再有 fixed)
    VARYING float fogFactor;                     // UNITY_FOG_COORDS(1) → 自己声明

    #ifdef VERTEX
    void main()
    {
        // UnityObjectToClipPos → TransformObjectToHClip
        gl_Position = TransformObjectToHClip(positionOS.xyz);
        // _Time.y 的含义与 Built-in 相同:游戏开始后的秒数
        vec2 scroll = _ScrollSpeed.xy * _Time.y + _ScrollOffset.xy;
        uv = TransformTex(texcoord0.xy, _BaseMap_ST) + scroll;
        vColor = color;
        // UNITY_TRANSFER_FOG(o, o.pos) → ComputeFogFactor
        fogFactor = ComputeFogFactor(gl_Position.z);
    }
    #endif

    #ifdef FRAGMENT
    void main()
    {
        // tex2D(_MainTex, uv) → SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv)(网页版:texture)
        vec4 col = texture(_BaseMap, uv) * _BaseColor * vColor;
        // UNITY_APPLY_FOG(i.fogCoord, col) → MixFog
        col.rgb = MixFog(col.rgb, fogFactor);
        fragColor = vec4(col.rgb, 1);
    }
    #endif
    ENDGLSL
}

除了逐行替换,还有四处“结构性”改动:

  1. SubShader 加上 RenderPipeline 标签,Pass 加上 LightMode(Unlit 用 UniversalForwardOnly,见 3.4 节)。
  2. 属性改名为 _BaseMap / _BaseColor 并加 [MainTexture]/[MainColor]。这不是语法要求,但遵循 URP 命名后,材质在 URP 自带 Shader 与你的 Shader 之间切换时贴图和颜色能自动保留,URP 的材质升级工具和许多插件也按这个名字找属性。如果项目里有大量旧材质,也可以保留 _MainTex 这个名字,换取零迁移成本——两种选择都可以,团队统一即可。
  3. 所有属性进 UnityPerMaterial,放在 HLSLINCLUDE 里供三个 Pass 共享。
  4. 删掉 Fallback,手写 ShadowCaster 和 DepthOnly。Built-in 版本靠 Fallback 借用别的 Shader 的阴影 Pass;URP 下应该自己提供。

另外,为了能在书里拍静帧,我们加了一个 _ScrollOffset 参数:_Time 在编辑器截图时接近 0,用它可以手动指定“滚到哪了”。实际使用时 _ScrollSpeed 驱动自动滚动,_ScrollOffset 也可以交给动画曲线或脚本控制,做出“按事件推进”的传送带。

图 3-16迁移后的 Shader 用在一个圆柱上,_ScrollOffset.x 分别为 0、0.25、0.5,贴图沿 U 方向绕圆柱滚动