GameTech/URP 渲染实战
源码 / Ch29/HalfPrecisionDemo.shader
Ch29/HalfPrecisionDemo.shader
// 案例 29.4:half 精度不够用的典型现场——远离原点时用 half 存世界坐标再算 UV。
// 桌面平台常把 half 当 float 执行,看不出问题;这里按 half 的格式(10 位尾数)手动把数值舍入到
// 它能表示的最近值,模拟它在移动 GPU 上真正以 16 位存储时的样子。
Shader "Cookbook/Ch29/HalfPrecisionDemo"
{
    Properties
    {
        [MainTexture] _BaseMap ("Base Map", 2D) = "white" {}
        _Tiling ("World UV Tiling (1/m)", Float) = 0.5
        [Toggle] _EmulateHalf ("Emulate Half Storage", Float) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }
        Pass
        {
            Name "Demo"
            Tags { "LightMode"="UniversalForward" }
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                float _Tiling, _EmulateHalf;
            CBUFFER_END

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

            Varyings vert(Attributes input)
            {
                Varyings o;
                o.positionWS = TransformObjectToWorld(input.positionOS.xyz);
                o.positionCS = TransformWorldToHClip(o.positionWS);
                return o;
            }

            // half:1 位符号 + 5 位指数 + 10 位尾数。数值落在 [2^e, 2^(e+1)) 时,相邻两个可表示值相差 2^(e-10)
            float2 ToHalfAndBack(float2 v)
            {
                float2 e = floor(log2(max(abs(v), 6.1e-5)));   // 6.1e-5 ≈ half 最小正规数
                float2 ulp = exp2(e - 10);
                return round(v / ulp) * ulp;
            }

            half4 frag(Varyings i) : SV_Target
            {
                float2 p = i.positionWS.xz;
                if (_EmulateHalf > 0.5) p = ToHalfAndBack(p);      // 相当于 half2 p = positionWS.xz;
                float2 uv = p * _Tiling;
                half3 c = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv).rgb;
                return half4(c, 1);
            }
            ENDHLSL
        }
    }
}