GameTech/URP 渲染实战
源码 / Ch01/Editor/Ch01Shots.cs
Ch01/Editor/Ch01Shots.cs
// 第 1 章配图:坐标空间、法线、深度、插值
using Cookbook.Core;
using UnityEngine;
using UnityEngine.Rendering.Universal;

namespace Cookbook.Ch01
{
    static class Ch01Shots
    {
        // 三个形状各异、带旋转和非均匀缩放的物体 + 地面,全部用同一个调试材质
        static void SpaceScene(Shot s, Material m)
        {
            s.Floor(m);
            var cube = s.Cube(m, new Vector3(-1.55f, 0.5f, 0.5f));
            cube.transform.rotation = Quaternion.Euler(0, 35, 0);
            s.Sphere(m, new Vector3(0, 0.6f, 0), 1.2f);
            var cyl = s.Cylinder(m, new Vector3(1.55f, 0.75f, 0.4f));
            cyl.transform.localScale = new Vector3(0.8f, 0.75f, 0.8f);
            cyl.transform.rotation = Quaternion.Euler(0, 0, -15);
            s.LookAt(new Vector3(0.3f, 2.3f, -5.2f), new Vector3(0, 0.5f, 0.6f), 40);
        }

        [BookShot("ch01-spaces", W = 640, H = 420, Panels = 3)]
        static void Spaces(Shot s)
        {
            var m = s.Mat("Cookbook/Ch01/SpaceViz");
            m.SetFloat("_Mode", s.Panel);          // 0 物体 / 1 世界 / 2 观察
            m.SetFloat("_GridSize", s.Panel == 0 ? 1f : 0.5f);
            m.SetFloat("_DepthScale", 10f);
            SpaceScene(s, m);
        }

        [BookShot("ch01-ndc", W = 640, H = 420, Panels = 3)]
        static void Ndc(Shot s)
        {
            var m = s.Mat("Cookbook/Ch01/SpaceViz");
            m.SetFloat("_Mode", 3 + s.Panel);      // 3 NDC / 4 裁剪空间 w / 5 像素坐标
            m.SetFloat("_DepthScale", 12f);
            m.SetFloat("_PixelCell", 40f);
            SpaceScene(s, m);
        }

        static void NormalScene(Shot s, Material m)
        {
            s.Floor(m);
            var cube = s.Cube(m, new Vector3(-1.6f, 0.62f, 0.5f), 0.9f);
            cube.transform.rotation = Quaternion.Euler(25, 40, 15);
            var knot = s.Knot(m, new Vector3(0.05f, 0.95f, 0.2f), 0.85f);
            knot.transform.rotation = Quaternion.Euler(70, 20, 0);
            s.Sphere(m, new Vector3(1.65f, 0.55f, 0.4f), 1.1f);
            s.LookAt(new Vector3(0.2f, 2.2f, -4.8f), new Vector3(0, 0.6f, 0.4f), 40);
        }

        [BookShot("ch01-normals", W = 640, H = 420, Panels = 3)]
        static void Normals(Shot s)
        {
            var m = s.Mat("Cookbook/Ch01/NormalViz");
            m.SetFloat("_Mode", s.Panel);          // 0 物体空间 / 1 世界空间 / 2 观察空间
            NormalScene(s, m);
        }

        [BookShot("ch01-normal-scale", W = 640, H = 420, Panels = 3)]
        static void NormalScale(Shot s)
        {
            int[] modes = { 4, 3, 5 };              // 错误 / 正确 / 差异×4
            var m = s.Mat("Cookbook/Ch01/NormalViz");
            m.SetFloat("_Mode", modes[s.Panel]);
            m.SetColor("_BaseColor", new Color(0.9f, 0.88f, 0.85f));
            var floor = s.Lit(new Color(0.35f, 0.37f, 0.4f), 0, 0.1f);
            s.Floor(floor);
            var sph = s.Sphere(m, new Vector3(-0.9f, 0.55f, 0.2f));
            sph.transform.localScale = new Vector3(2.0f, 0.7f, 0.9f);   // 压扁、拉长的球
            sph.transform.rotation = Quaternion.Euler(0, 30, 0);
            var knot = s.Knot(m, new Vector3(1.3f, 0.75f, 0.4f));
            knot.transform.localScale = new Vector3(0.55f, 1.15f, 0.55f); // 竖向拉长的纽结
            knot.transform.rotation = Quaternion.Euler(0, 20, 0);
            s.SunDir(35, -70, 1.4f);
            s.Background(new Color(0.16f, 0.18f, 0.21f));
            s.LookAt(new Vector3(0.2f, 2.0f, -4.6f), new Vector3(0.1f, 0.6f, 0.3f), 38);
        }

        [BookShot("ch01-depth", W = 640, H = 400, Panels = 3)]
        static void Depth(Shot s)
        {
            // 深度纹理要在“透明物体之前”拷贝出来,DepthViz 才能读到当前帧的深度
            s.Renderer.copyDepthMode = CopyDepthMode.AfterOpaques;
            s.Cam.nearClipPlane = 0.3f;    // Unity 新建相机的默认值
            s.Cam.farClipPlane = 40f;

            s.Floor();
            for (int i = 0; i < 8; i++)
            {
                float z = i * 4f;
                s.Cube(s.Lit(new Color(0.8f, 0.5f, 0.3f)), new Vector3(-2.2f, 1.25f, z)).transform.localScale = new Vector3(0.6f, 2.5f, 0.6f);
                s.Cube(s.Lit(new Color(0.3f, 0.55f, 0.8f)), new Vector3(2.2f, 1.25f, z + 2f)).transform.localScale = new Vector3(0.6f, 2.5f, 0.6f);
            }
            s.Sphere(s.Lit(new Color(0.9f, 0.9f, 0.9f)), new Vector3(-0.5f, 0.5f, 1.0f), 1.0f);
            s.Sphere(s.Lit(new Color(0.9f, 0.9f, 0.9f)), new Vector3(0.6f, 0.8f, 8f), 1.6f);
            s.LookAt(new Vector3(0, 1.6f, -3.0f), new Vector3(0, 1.0f, 10f), 55);

            var m = s.Mat("Cookbook/Ch01/DepthViz");
            m.SetFloat("_Mode", s.Panel);          // 0 原始 / 1 Linear01 / 2 Eye 色带
            m.SetFloat("_BandWidth", 1f);
            m.SetFloat("_EyeMax", 30f);
            // 全屏四边形:顶点着色器会无视它的位置,但它的包围盒必须在视锥内,否则会被 CPU 剔除
            var q = s.Quad(m, s.Cam.transform.position + s.Cam.transform.forward * 2f);
            q.transform.rotation = s.Cam.transform.rotation;
        }

        [BookShot("ch01-interp", W = 640, H = 420, Panels = 2)]
        static void Interp(Shot s)
        {
            var m = s.Mat("Cookbook/Ch01/PerspectiveInterp");
            m.SetTexture("_MainTex", s.CoreTex("uv_grid"));
            m.SetFloat("_Tiling", 2f);
            m.SetFloat("_Affine", s.Panel);        // 0 透视校正 / 1 仿射
            // 只有两个三角形的大平面:三角形越大、深度跨度越大,仿射插值的错误越明显
            s.MeshObj(Meshes.Grid(8f, 1), m, Vector3.zero, 1f, "BigQuad");
            var wall = s.MeshObj(Meshes.Grid(3f, 1), m, new Vector3(3.0f, 1.5f, 1.8f), 1f, "Wall");
            wall.transform.rotation = Quaternion.Euler(-90, 40, 0);   // 一面斜对着相机的墙,同样只有两个三角形
            s.Background(new Color(0.2f, 0.22f, 0.25f));
            s.LookAt(new Vector3(-0.6f, 1.5f, -5.2f), new Vector3(0.3f, 0.2f, 0.5f), 50);
        }

        [BookShot("ch01-screenuv", W = 640, H = 420, Panels = 3)]
        static void ScreenUv(Shot s)
        {
            var m = s.Mat("Cookbook/Ch01/ScreenUV");
            m.SetFloat("_Mode", s.Panel);          // 0 片元里除 / 1 顶点里除(错)/ 2 顶点里除 + noperspective
            m.SetFloat("_Cells", 9f);
            s.MeshObj(Meshes.Grid(10f, 1), m, Vector3.zero, 1f, "BigQuad");
            var cube = s.Cube(m, new Vector3(0.8f, 0.9f, 1.2f), 1.8f);
            cube.transform.rotation = Quaternion.Euler(0, 40, 0);
            s.Background(new Color(0.2f, 0.22f, 0.25f));
            s.LookAt(new Vector3(-0.8f, 1.6f, -4.6f), new Vector3(0.3f, 0.5f, 0.8f), 50);
        }
    }
}