GameTech/URP 渲染实战
源码 / Ch09/Editor/Ch09Shots.cs
Ch09/Editor/Ch09Shots.cs
// 第 9 章配图:BRDF 各项的粗糙度扫描、NDF/G 对比、菲涅尔、白炉测试、Gamma vs 线性、HDR 与色调映射、两种工作流。
using Cookbook.Core;
using UnityEngine;
using UnityEngine.Rendering.Universal;

namespace Cookbook.Ch09
{
    static class Ch09Shots
    {
        static readonly float[] Rough = { 0.1f, 0.3f, 0.5f, 0.7f, 0.9f };

        // 线性颜色 → 交给 SetColor/Background 的 sRGB 值(Unity 在线性空间下会把 Color 属性当 sRGB 再转回线性)
        static Color Lin(float r, float g, float b) => new Color(r, g, b).gamma;
        static Vector4 V(float x) => new Vector4(x, x, x, 0);

        // 一排 5 个球,用正交相机正面拍,避免透视把两端的球拉变形
        static void Row(Shot s, System.Func<int, Material> mat)
        {
            s.Background(new Color(0.16f, 0.17f, 0.19f));
            s.SphereRow(5, mat, 0f, 0f, 1.15f, 1f);
            s.Cam.orthographic = true;
            s.Cam.orthographicSize = 0.74f;
            s.Cam.transform.position = new Vector3(0, 0, -10);
            s.Cam.transform.rotation = Quaternion.identity;
            s.SunDir(35, 30, 1.0f);           // 光从左上前方来
            s.Sun.shadows = LightShadows.None;
        }

        static Material Terms(Shot s, float mode, float rough)
        {
            var m = s.Mat("Cookbook/Ch09/BRDFTerms");
            m.SetFloat("_Mode", mode);
            m.SetFloat("_Roughness", rough);
            return m;
        }

        [BookShot("ch09-terms-d", W = 960, H = 220)]
        static void TermsD(Shot s)
        {
            Row(s, i => { var m = Terms(s, 0, Rough[i]); m.SetFloat("_Display", 1); m.SetFloat("_Exposure", 1f); return m; });
        }

        [BookShot("ch09-terms-g", W = 960, H = 220)]
        static void TermsG(Shot s)
        {
            Row(s, i => { var m = Terms(s, 1, Rough[i]); m.SetFloat("_GModel", 1); return m; });
        }

        [BookShot("ch09-terms-f", W = 960, H = 220)]
        static void TermsF(Shot s)
        {
            // 这一排扫的是 F0:水、塑料/玻璃、钻石、金、银(金属取常见资料中的近似线性值,仅作示意)
            Vector4[] f0 =
            {
                V(0.02f), V(0.04f), V(0.17f),
                new Vector4(1.00f, 0.71f, 0.29f, 0), new Vector4(0.95f, 0.93f, 0.88f, 0)
            };
            Row(s, i => { var m = Terms(s, 2, 0.5f); m.SetVector("_F0", f0[i]); return m; });
        }

        [BookShot("ch09-terms-spec", W = 960, H = 220)]
        static void TermsSpec(Shot s)
        {
            Row(s, i =>
            {
                var m = Terms(s, 4, Rough[i]);
                m.SetVector("_F0", V(0.9f));
                m.SetFloat("_Display", 1); m.SetFloat("_Exposure", 1f);
                return m;
            });
        }

        [BookShot("ch09-terms-full", W = 960, H = 220)]
        static void TermsFull(Shot s)
        {
            Row(s, i =>
            {
                var m = Terms(s, 6, Rough[i]);
                m.SetColor("_Albedo", new Color(0.45f, 0.1f, 0.07f));
                m.SetVector("_F0", V(0.04f));
                return m;
            });
            s.Sun.intensity = 2.2f;
        }

        // 单球特写的公共布景
        static void Single(Shot s, Material m, float pitch, float yaw)
        {
            s.Background(new Color(0.16f, 0.17f, 0.19f));
            s.Sphere(m, Vector3.zero, 2f);
            s.LookAt(new Vector3(0, 0, -6.2f), Vector3.zero, 24);
            s.SunDir(pitch, yaw, 1f);
            s.Sun.shadows = LightShadows.None;
        }

        [BookShot("ch09-ndf-compare", W = 420, H = 360, Panels = 3)]
        static void NdfCompare(Shot s)
        {
            var m = Terms(s, 0, 0.45f);
            m.SetFloat("_NDF", s.Panel);           // GGX / Beckmann / Blinn-Phong
            m.SetFloat("_Display", 1); m.SetFloat("_Exposure", 0.6f);
            Single(s, m, 30, 25);
        }

        [BookShot("ch09-g-compare", W = 420, H = 360, Panels = 3)]
        static void GCompare(Shot s)
        {
            var m = Terms(s, 1, 1.0f);
            m.SetFloat("_GModel", new[] { 0f, 1f, 3f }[s.Panel]);   // 分离 / 高度相关 / 差值×10
            Single(s, m, 10, 70);
        }

        [BookShot("ch09-fresnel-angle", W = 420, H = 300, Panels = 3)]
        static void FresnelAngle(Shot s)
        {
            // 一块光滑的电介质地板,相机仰角从高到低;灯放在相机的镜面方向上,让高光正好落在画面中央。
            // 高光中心处 v·h = sin(仰角):70° → 0.94,35° → 0.57,12° → 0.21
            float e = new[] { 70f, 35f, 12f }[s.Panel];
            var m = Terms(s, 4, 0.35f);
            m.SetVector("_F0", V(0.04f));
            m.SetFloat("_Display", 1); m.SetFloat("_Exposure", 1.0f);
            s.Background(new Color(0.16f, 0.17f, 0.19f));
            s.Grid(m, Vector3.zero, 40f, 8);
            float r = e * Mathf.Deg2Rad;
            s.LookAt(new Vector3(0, 6 * Mathf.Sin(r), -6 * Mathf.Cos(r)), Vector3.zero, 40);
            s.SunDir(e, 180, 1f);
            s.Sun.shadows = LightShadows.None;
        }

        [BookShot("ch09-diffuse", W = 420, H = 360, Panels = 3)]
        static void Diffuse(Shot s)
        {
            var m = Terms(s, 5, s.Panel == 2 ? 1f : 0.02f);
            m.SetFloat("_DiffuseModel", s.Panel == 0 ? 0 : 1);
            m.SetColor("_Albedo", new Color(0.85f, 0.85f, 0.85f));
            Single(s, m, 0, 0);   // 灯放在相机正后方:l = v,最能看出 Disney 漫反射在掠射角的差别
        }

        [BookShot("ch09-furnace", W = 360, H = 360, Panels = 4)]
        static void Furnace(Shot s)
        {
            var env = new Color(0.5f, 0.5f, 0.5f);
            var m = s.Mat("Cookbook/Ch09/WhiteFurnace");
            m.SetFloat("_Model", s.Panel == 0 ? 0 : 1);
            m.SetFloat("_Roughness", new[] { 1f, 0.2f, 0.6f, 1f }[s.Panel]);
            m.SetFloat("_Albedo", 1f);
            m.SetColor("_EnvColor", env);
            m.SetFloat("_SampleCount", 1024);
            s.Background(env);                     // 背景亮度 = 环境亮度
            s.Sphere(m, Vector3.zero, 2f);
            s.LookAt(new Vector3(0, 0, -6.4f), Vector3.zero, 24);
            s.Sun.intensity = 0;
        }

        [BookShot("ch09-gamma", W = 480, H = 400, Panels = 2)]
        static void Gamma(Shot s)
        {
            var m = s.Mat("Cookbook/Ch09/GammaVsLinear");
            m.SetFloat("_GammaSpace", s.Panel == 0 ? 0 : 1);
            m.SetColor("_BaseColor", new Color(0.8f, 0.8f, 0.8f));
            m.SetColor("_Light2Color", new Color(0.25f, 0.5f, 1f));
            m.SetVector("_Light2Dir", new Vector4(1f, 0.25f, -0.35f, 0));
            s.Background(new Color(0.08f, 0.08f, 0.09f));
            s.Sphere(m, Vector3.zero, 2f);
            s.LookAt(new Vector3(0, 0, -6.2f), Vector3.zero, 26);
            s.SunDir(15, 70, 1f);
            s.Sun.color = new Color(1f, 0.6f, 0.25f);
            s.Sun.shadows = LightShadows.None;
        }

        [BookShot("ch09-hdr", W = 420, H = 320, Panels = 3)]
        static void Hdr(Shot s)
        {
            s.Floor();
            s.Sphere(s.Lit(new Color(0.9f, 0.9f, 0.9f), 1f, 0.92f), new Vector3(-0.85f, 0.55f, 0.3f), 1.1f);
            s.Sphere(s.Lit(new Color(0.8f, 0.25f, 0.15f), 0f, 0.8f), new Vector3(0.85f, 0.55f, 0.3f), 1.1f);
            var glow = s.Lit(Color.black, 0, 0.5f);
            glow.EnableKeyword("_EMISSION");
            glow.SetColor("_EmissionColor", new Color(1f, 0.4f, 0.1f) * 5f);
            s.Cube(glow, new Vector3(0, 0.25f, -0.6f), 0.5f);
            s.SunDir(35, -40, 4.5f);
            if (s.Panel > 0)
            {
                var t = s.Post().Add<Tonemapping>();
                t.mode.Override(s.Panel == 1 ? TonemappingMode.Neutral : TonemappingMode.ACES);
            }
        }

        [BookShot("ch09-workflow", W = 420, H = 320, Panels = 3)]
        static void Workflow(Shot s)
        {
            s.Floor();
            Material m;
            if (s.Panel == 0)
            {
                m = s.Lit(new Color(1f, 0.78f, 0.34f), 1f, 0.75f);          // 金属度工作流:金属 = 1,底色即 F0
            }
            else
            {
                m = s.Lit(Color.black, 0f, 0.75f);
                m.SetFloat("_WorkflowMode", 0);
                m.EnableKeyword("_SPECULAR_SETUP");                        // 脚本建的材质不会跑 ShaderGUI,关键字要自己开
                if (s.Panel == 1)
                    m.SetColor("_SpecColor", new Color(1f, 0.78f, 0.34f)); // 镜面工作流:漫反射黑、镜面色 = 同样的金色
                else
                {
                    m.SetColor("_BaseColor", new Color(0.7f, 0.08f, 0.06f));
                    m.SetColor("_SpecColor", new Color(0.25f, 0.45f, 0.75f)); // 红色漫反射 + 蓝色高光:金属度工作流表达不了
                }
            }
            s.Sphere(m, new Vector3(0, 0.75f, 0), 1.5f);
            s.LookAt(new Vector3(0, 1.35f, -4.0f), new Vector3(0, 0.7f, 0), 32);
        }
    }
}