GameTech/URP 渲染实战
源码 / shaders/Ch12/Skin.glsl
shaders/Ch12/Skin.glsl
// 皮肤:三种次表面散射近似的漫反射 + 双瓣 GGX 高光。
//   LAMBERT —— 对照组;WRAP —— 按通道不同包裹量的 Wrap Lighting;LUT —— 预积分皮肤散射(Penner 2011)
// (GLSL 版,对应 Ch12/Skin.shader)
Shader "Cookbook/Ch12/Skin"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }

        GLSLINCLUDE
        uniform vec4  _BaseColor, _ScatterColor;
        uniform float _Wrap, _CurvatureScale, _Curvature, _Smoothness, _SpecularF0;
        uniform sampler2D _SkinLUT;
        ENDGLSL

        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode"="UniversalForwardOnly" }

            GLSLPROGRAM
            #pragma shader_feature_local_fragment _DIFFUSE_LAMBERT _DIFFUSE_WRAP _DIFFUSE_LUT

            #include "Ch12/Ch12Common.glsl"

            #ifdef VERTEX
            void main() { Ch12Vert(); }
            #endif

            #ifdef FRAGMENT
            struct MaterialCtx
            {
                vec3 N, V;
                vec3 albedo;
                float curvature;   // 0~1,查 LUT 的 v 坐标
                float a;
            };

            vec3 SkinDiffuse(MaterialCtx m, float NoLu, float shadow)
            {
            #if defined(_DIFFUSE_WRAP)
                // 每个通道包裹量不同:红光在皮下走得最远,所以红通道包得最多
                // 阴影同样折进 N·L:阴影里的点按“刚好背光”处理,包裹项让阴影边缘也透出一点红
                vec3 w = _Wrap * _ScatterColor.rgb;
                float ndl = mix(min(NoLu, 0.0), NoLu, shadow);
                return saturate((ndl + w) / (1.0 + w));
            #elif defined(_DIFFUSE_LUT)
                // 预积分 LUT:u = N·L 映射到 0~1,v = 曲率
                // 阴影先折进 N·L 再查表,让阴影边缘也带上散射的红色过渡
                float u = mix(min(NoLu, 0.0), NoLu, shadow) * 0.5 + 0.5;
                return textureLod(_SkinLUT, vec2(u, m.curvature), 0.0).rgb;
            #else
                return vec3(saturate(NoLu) * shadow);
            #endif
            }

            vec3 ShadeOneLight(MaterialCtx m, Light light)
            {
                vec3 L = light.direction;
                float NoLu = dot(m.N, L);
                float NoL = saturate(NoLu);
                vec3 diffuse = m.albedo * SkinDiffuse(m, NoLu, light.shadowAttenuation);

                // 双瓣高光:一个偏光滑、一个偏粗糙,各占一半,模拟皮脂层 + 角质层
                vec3 spec = 0.5 * AdvSpecularGGX(m.N, m.V, L, m.a, _SpecularF0)
                          + 0.5 * AdvSpecularGGX(m.N, m.V, L, saturate(m.a * 2.2), _SpecularF0);
                spec *= PI * NoL * light.shadowAttenuation;

                return (diffuse + spec) * light.color * light.distanceAttenuation;
            }

            #include "Ch12/LightLoop.glsl"

            void main()
            {
                vec3 T, B, N; Ch12BuildFrame(T, B, N);
                vec3 V = GetWorldSpaceNormalizeViewDir(positionWS);
                vec2 screenUV = GetNormalizedScreenSpaceUV(gl_FragCoord);

                // 曲率 = 法线变化量 / 位置变化量,用屏幕空间导数估计(单位 1/米)
                float curv = length(fwidth(N)) / max(length(fwidth(positionWS)), 1e-5);
                curv = saturate(curv * _CurvatureScale);   // 换算到 LUT 的 0~1,比例取决于场景单位和 LUT 的定义
                if (_Curvature >= 0.0) curv = _Curvature;

                MaterialCtx m;
                m.N = N; m.V = V;
                m.albedo = _BaseColor.rgb;
                m.curvature = curv;
                float pr = 1.0 - _Smoothness;
                m.a = max(pr * pr, 0.002);

                vec3 color = AccumulateDirectLighting(m, positionWS, screenUV);

                float NoV = saturate(dot(N, V));
                vec2 AB = AdvEnvBRDFApprox(pr, NoV);
                color += SampleSH(N) * m.albedo;
                color += GlossyEnvironmentReflection(reflect(-V, N), positionWS, pr, 1.0) * (_SpecularF0 * AB.x + AB.y);

                color = MixFog(color, fogFactor);
                fragColor = vec4(color, 1.0);
            }
            #endif
            ENDGLSL
        }

        UsePass "Cookbook/Common/SHADOWCASTER"
        UsePass "Cookbook/Common/DEPTHONLY"
        UsePass "Cookbook/Common/DEPTHNORMALS"
    }
}