GameTech/URP 渲染实战
源码 / Ch26/Editor/Ch26Shots.cs
Ch26/Editor/Ch26Shots.cs
// 第 26 章配图:每张图 = 场景 + s.Feature<自定义 Feature>() + s.Post().Add<自定义 Volume 组件>()。
using Cookbook.Core;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace Cookbook.Ch26
{
    static class Ch26Shots
    {
        static Material Emissive(Shot s, Color c, float intensity)
        {
            var m = s.Lit(c * 0.2f, 0f, 0.5f);
            m.EnableKeyword("_EMISSION");
            m.SetColor("_EmissionColor", c * intensity);
            return m;
        }

        // 通用场景:彩色物体 + 几个 HDR 自发光物体(给 Bloom 用)
        static void Stage(Shot s, float glow = 2.5f)
        {
            s.Floor();
            s.Knot(s.Lit(new Color(0.95f, 0.45f, 0.12f), 0f, 0.6f), new Vector3(0, 0.95f, 0.4f), 1.0f);
            s.Sphere(s.Lit(new Color(0.85f, 0.12f, 0.12f), 0f, 0.75f), new Vector3(-1.55f, 0.45f, -0.3f), 0.9f);
            var cube = s.Cube(s.Lit(new Color(0.15f, 0.35f, 0.9f), 0f, 0.4f), new Vector3(1.55f, 0.4f, -0.1f), 0.8f);
            cube.transform.rotation = Quaternion.Euler(0, 30, 0);
            s.Sphere(s.Lit(new Color(0.9f, 0.9f, 0.92f), 1f, 0.85f), new Vector3(0.7f, 0.3f, -1.0f), 0.6f);
            s.Capsule(s.Lit(new Color(0.2f, 0.75f, 0.35f), 0f, 0.5f), new Vector3(-0.6f, 0.5f, 1.8f), 1.0f);
            // 自发光:三颗小灯球 + 一根霓虹灯条
            s.Sphere(Emissive(s, new Color(0.05f, 0.55f, 1f), glow), new Vector3(-0.55f, 0.18f, -1.3f), 0.36f);
            s.Sphere(Emissive(s, new Color(1f, 0.1f, 0.6f), glow), new Vector3(2.3f, 0.2f, -0.9f), 0.4f);
            s.Sphere(Emissive(s, new Color(1f, 0.7f, 0.1f), glow), new Vector3(-2.4f, 1.3f, 1.2f), 0.35f);
            var bar = s.Cube(Emissive(s, new Color(1f, 0.3f, 0.05f), glow), new Vector3(0.9f, 2.05f, 2.4f), 1f);
            bar.transform.localScale = new Vector3(2.6f, 0.08f, 0.08f);
            s.LookAt(new Vector3(0, 1.75f, -5.3f), new Vector3(0, 0.65f, 0.2f), 38);
        }

        static Volume GlobalVolume() => Object.FindObjectOfType<Volume>();

        [BookShot("ch26-saturation", W = 640, H = 400, Panels = 3)]
        static void Saturation(Shot s)
        {
            Stage(s);
            s.Feature<SaturationFeature>();
            var v = s.Post().Add<SaturationVolume>();
            if (s.Panel == 1) v.saturation.Override(0f);
            if (s.Panel == 2) { v.saturation.Override(1.6f); v.contrast.Override(1.25f); }
        }

        [BookShot("ch26-volume-weight", W = 640, H = 400, Panels = 3)]
        static void VolumeWeight(Shot s)
        {
            Stage(s);
            s.Feature<SaturationFeature>();
            var v = s.Post().Add<SaturationVolume>();
            v.saturation.Override(0f);
            GlobalVolume().weight = new[] { 0.25f, 0.5f, 1f }[s.Panel];
        }

        [BookShot("ch26-gaussian", W = 640, H = 400, Panels = 3)]
        static void Gaussian(Shot s)
        {
            Stage(s);
            s.Feature<GaussianBlurFeature>();
            var v = s.Post().Add<GaussianBlurVolume>();
            if (s.Panel == 1) { v.spread.Override(1f); v.iterations.Override(1); v.downsample.Override(1); }
            if (s.Panel == 2) { v.spread.Override(1.5f); v.iterations.Override(3); v.downsample.Override(4); }
        }

        [BookShot("ch26-kawase", W = 640, H = 400, Panels = 3)]
        static void Kawase(Shot s)
        {
            Stage(s);
            s.Feature<KawaseBlurFeature>();
            var v = s.Post().Add<KawaseBlurVolume>();
            switch (s.Panel)
            {
                case 0: v.mode.Override(KawaseMode.Kawase); v.iterations.Override(5); break;
                case 1: v.mode.Override(KawaseMode.DualKawase); v.iterations.Override(3); break;
                default: v.mode.Override(KawaseMode.DualKawase); v.iterations.Override(5); break;
            }
        }

        [BookShot("ch26-bloom", W = 480, H = 320, Panels = 4, Hdr = true)]   // Hdr:浮点相机目标,亮度可远大于 1
        static void BloomShot(Shot s)
        {
            Stage(s, glow: 10f);                             // 自发光强度 10:远超阈值
            s.Background(new Color(0.02f, 0.025f, 0.04f));   // 暗背景更能看出泛光
            s.SunDir(50, -30, 0.8f);
            var prof = s.Post();
            // 四格都加同样的 ACES 色调映射(URP 自带),只有 Bloom 不同
            prof.Add<Tonemapping>().mode.Override(TonemappingMode.ACES);
            if (s.Panel == 0) return;

            s.Feature<MyBloomFeature>();
            var v = prof.Add<MyBloomVolume>();
            v.threshold.Override(1f);
            v.softKnee.Override(0.5f);
            v.intensity.Override(1f);
            v.scatter.Override(0.7f);
            v.debugView.Override(s.Panel == 1 ? BloomDebugView.Prefilter
                               : s.Panel == 2 ? BloomDebugView.BloomOnly : BloomDebugView.None);
        }

        // 与 URP 自带 Bloom 对照(同样的阈值与强度)
        [BookShot("ch26-bloom-compare", W = 640, H = 400, Panels = 2, Hdr = true)]
        static void BloomCompare(Shot s)
        {
            Stage(s, glow: 10f);
            s.Background(new Color(0.02f, 0.025f, 0.04f));
            s.SunDir(50, -30, 0.8f);
            var prof = s.Post();
            prof.Add<Tonemapping>().mode.Override(TonemappingMode.ACES);
            if (s.Panel == 0)
            {
                s.Feature<MyBloomFeature>();
                var v = prof.Add<MyBloomVolume>();
                v.threshold.Override(1f); v.softKnee.Override(0.5f); v.intensity.Override(1f); v.scatter.Override(0.7f);
            }
            else
            {
                // URP 的 threshold 按 Gamma 空间理解(内部 GammaToLinear),1 在两种空间里相同;
                // scatter 内部被重映射到 0.05~0.95,0.72 ≈ 我们的 0.7
                var b = prof.Add<Bloom>();
                b.threshold.Override(1f); b.intensity.Override(1f); b.scatter.Override(0.72f);
            }
        }

        [BookShot("ch26-radial", W = 640, H = 400, Panels = 2)]
        static void Radial(Shot s)
        {
            Stage(s);
            s.Feature<RadialBlurFeature>();
            var v = s.Post().Add<RadialBlurVolume>();
            if (s.Panel == 1) { v.strength.Override(0.6f); v.samples.Override(16); v.center.Override(new Vector2(0.5f, 0.55f)); }
        }

        [BookShot("ch26-lens", W = 640, H = 400, Panels = 3)]
        static void Lens(Shot s)
        {
            Stage(s);
            // 两个 Feature 都在 BeforeRenderingPostProcessing:入队顺序 = Renderer 列表顺序
            s.Feature<MyChromaticAberrationFeature>();
            s.Feature<MyVignetteFeature>();
            var prof = s.Post();
            var ca = prof.Add<MyChromaticAberration>();
            var vg = prof.Add<MyVignette>();
            if (s.Panel != 1) ca.intensity.Override(1f);
            if (s.Panel != 0) { vg.intensity.Override(0.6f); vg.smoothness.Override(0.7f); vg.rounded.Override(true); }
            if (s.Panel == 2) vg.color.Override(new Color(0.12f, 0.02f, 0.05f));
        }

        [BookShot("ch26-pixelate", W = 640, H = 400, Panels = 3)]
        static void Pixelate(Shot s)
        {
            Stage(s);
            s.Feature<PixelateFeature>();
            var v = s.Post().Add<PixelateVolume>();
            v.pixelSize.Override(5);
            if (s.Panel == 1) { v.colorLevels.Override(6); v.dither.Override(0f); }
            if (s.Panel == 2) { v.colorLevels.Override(6); v.dither.Override(1f); }
        }

        [BookShot("ch26-crt", W = 640, H = 400, Panels = 2)]
        static void Crt(Shot s)
        {
            Stage(s);
            s.Feature<CrtFeature>();
            var v = s.Post().Add<CrtVolume>();
            if (s.Panel == 1) { v.intensity.Override(1f); v.curvature.Override(0.18f); v.lineCount.Override(160f); }
        }

        [BookShot("ch26-edge", W = 640, H = 400, Panels = 3)]
        static void Edge(Shot s)
        {
            Stage(s);
            s.Feature<EdgeDetectFeature>();
            var v = s.Post().Add<EdgeDetectVolume>();
            v.intensity.Override(1f);
            v.thickness.Override(1f);
            switch (s.Panel)
            {
                case 0: v.edgesOnly.Override(1f); v.normalThreshold.Override(4f); break;   // 只用深度
                case 1: v.edgesOnly.Override(1f); break;                                     // 深度 + 法线
                default: v.edgesOnly.Override(0f); break;                                    // 叠在原图上
            }
        }

        [BookShot("ch26-hitflash", W = 640, H = 400, Panels = 3)]
        static void HitFlash(Shot s)
        {
            Stage(s);
            s.Feature<HitFlashFeature>();
            var v = s.Post().Add<HitFlashVolume>();
            v.intensity.Override(new[] { 0.3f, 0.65f, 1f }[s.Panel]);
            v.phase.Override(1.7f);
        }
    }
}