GameTech/URP 渲染实战
源码 / Ch08/SHBasis.shader
Ch08/SHBasis.shader
// 可视化前三阶(l = 0,1,2)实数球谐基函数 Y_lm。
// 模式 0:在单位球面上着色;模式 1:把球面顶点沿径向推到 |Y_lm| 处,得到经典的“花瓣”形状。
// 暖色 = 正值,冷色 = 负值。
Shader "Cookbook/Ch08/SHBasis"
{
    Properties
    {
        _L("Band l", Range(0, 2)) = 1
        _M("Order m", Range(-2, 2)) = 0
        [Enum(Sphere,0,Lobe,1)] _Mode("Mode", Float) = 1
        _Scale("Lobe Scale", Float) = 1
        _PosColor("Positive", Color) = (0.93, 0.48, 0.22, 1)
        _NegColor("Negative", Color) = (0.16, 0.48, 0.72, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        CBUFFER_START(UnityPerMaterial)
            half4 _PosColor;
            half4 _NegColor;
            float _L;
            float _M;
            float _Mode;
            float _Scale;
        CBUFFER_END

        // 实数球谐基函数,系数与 core 的 SphericalHarmonics.hlsl 中 kSHBasis0~4 一致。
        // d 为单位方向;Unity 的 SphericalHarmonicsL2 也按这 9 个函数、这个顺序存系数。
        float SHBasis(int l, int m, float3 d)
        {
            float x = d.x, y = d.y, z = d.z;
            if (l == 0) return 0.282095;                                   // Y00 = 1/2·sqrt(1/π)
            if (l == 1)
            {
                if (m == -1) return 0.488603 * y;                          // Y1-1
                if (m == 0)  return 0.488603 * z;                          // Y10
                return 0.488603 * x;                                       // Y11
            }
            if (m == -2) return 1.092548 * x * y;                          // Y2-2
            if (m == -1) return 1.092548 * y * z;                          // Y2-1
            if (m == 0)  return 0.315392 * (3.0 * z * z - 1.0);            // Y20
            if (m == 1)  return 1.092548 * x * z;                          // Y21
            return 0.546274 * (x * x - y * y);                             // Y22
        }
        ENDHLSL

        Pass
        {
            Name "Unlit"
            Tags { "LightMode"="UniversalForwardOnly" }
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            Varyings vert(Attributes input)
            {
                Varyings o;
                float3 dir = normalize(input.positionOS.xyz);
                float y = SHBasis((int)round(_L), (int)round(_M), dir);
                float3 p = _Mode > 0.5 ? dir * abs(y) * _Scale : input.positionOS.xyz;
                o.positionWS = TransformObjectToWorld(p);
                o.positionCS = TransformWorldToHClip(o.positionWS);
                o.value = y;
                return o;
            }

            half4 frag(Varyings i) : SV_Target
            {
                // 变形后的网格没有正确法线,用屏幕导数求面法线,做一点简单明暗
                float3 n = normalize(cross(ddy(i.positionWS), ddx(i.positionWS)));
                float3 v = GetWorldSpaceNormalizeViewDir(i.positionWS);
                if (dot(n, v) < 0) n = -n;
                float3 l = normalize(float3(-0.4, 0.8, -0.6));
                half shade = 0.35 + 0.65 * saturate(dot(n, l));
                half3 c = i.value >= 0 ? _PosColor.rgb : _NegColor.rgb;
                if (_Mode < 0.5) c *= saturate(abs(i.value) * 2.0) * 0.85 + 0.15;   // 球面模式:亮度 = |Y|
                return half4(c * shade, 1);
            }
            ENDHLSL
        }
    }
}