源码 / _Core/Editor/Shot.cs
_Core/Editor/Shot.cs
// 书中配图的“摄影棚”:每个 [BookShot] 方法在一个全新的空场景里搭景,然后渲染成图片。 // 作者只需要写搭景代码,灯光、相机、天空、环境光、URP Asset 都有默认值。 using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using Object = UnityEngine.Object; namespace Cookbook.Core { [AttributeUsage(AttributeTargets.Method)] public class BookShotAttribute : Attribute { public readonly string Id; public int W = 960, H = 540, Panels = 1; /// true:相机渲染到 RGBAHalf 浮点目标(真正的 HDR,Bloom/Tonemapping 才能拿到 >1 的值),再转成 sRGB 存图 public bool Hdr; public BookShotAttribute(string id) { Id = id; } } public class Shot { public int Panel; // 多联图时当前是第几格(0 开始) public Camera Cam; public Light Sun; public UniversalRenderPipelineAsset Asset; public UniversalRendererData Renderer; public UniversalAdditionalCameraData CamData; internal readonly List<Object> Temp = new List<Object>(); // ---------- 材质 ---------- public Material Mat(string shaderName) { var sh = Shader.Find(shaderName); if (sh == null) throw new Exception("Shader not found: " + shaderName); var m = new Material(sh) { name = shaderName }; Temp.Add(m); return m; } /// URP 自带 Lit 材质,用来做对照或陪衬 public Material Lit(Color c, float metallic = 0f, float smoothness = 0.5f) { var m = Mat("Universal Render Pipeline/Lit"); m.SetColor("_BaseColor", c); m.SetFloat("_Metallic", metallic); m.SetFloat("_Smoothness", smoothness); return m; } /// 配套贴图:CoreTex("bricks_albedo") -> Assets/Cookbook/_Core/Textures/bricks_albedo.png public Texture2D CoreTex(string name) => Tex("Assets/Cookbook/_Core/Textures/" + name + ".png"); public Texture2D Tex(string assetPath) { var t = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath); if (t == null) throw new Exception("Texture not found: " + assetPath); return t; } // ---------- 物体 ---------- public GameObject Prim(PrimitiveType type, Material m, Vector3 pos, float scale = 1f) { var go = GameObject.CreatePrimitive(type); go.transform.position = pos; go.transform.localScale = Vector3.one * scale; if (m != null) go.GetComponent<MeshRenderer>().sharedMaterial = m; Object.DestroyImmediate(go.GetComponent<Collider>()); return go; } public GameObject Sphere(Material m, Vector3 pos, float scale = 1f) => Prim(PrimitiveType.Sphere, m, pos, scale); public GameObject Cube(Material m, Vector3 pos, float scale = 1f) => Prim(PrimitiveType.Cube, m, pos, scale); public GameObject Capsule(Material m, Vector3 pos, float scale = 1f) => Prim(PrimitiveType.Capsule, m, pos, scale); public GameObject Cylinder(Material m, Vector3 pos, float scale = 1f) => Prim(PrimitiveType.Cylinder, m, pos, scale); public GameObject Quad(Material m, Vector3 pos, float scale = 1f) => Prim(PrimitiveType.Quad, m, pos, scale); /// 地面:size×size 米的平面,默认浅灰棋盘格 Lit(每格 1 米) public GameObject Floor(Material m = null, float size = 60f, float y = 0f) { if (m == null) { m = Lit(new Color(0.75f, 0.75f, 0.75f), 0, 0.15f); m.SetTexture("_BaseMap", CoreTex("checker")); m.SetTextureScale("_BaseMap", new Vector2(size / 8f, size / 8f)); } var go = Prim(PrimitiveType.Plane, m, new Vector3(0, y, 0)); go.transform.localScale = new Vector3(size / 10f, 1, size / 10f); go.name = "Floor"; return go; } public GameObject MeshObj(Mesh mesh, Material m, Vector3 pos, float scale = 1f, string name = "Mesh") { var go = new GameObject(name); go.transform.position = pos; go.transform.localScale = Vector3.one * scale; go.AddComponent<MeshFilter>().sharedMesh = mesh; go.AddComponent<MeshRenderer>().sharedMaterial = m; Temp.Add(mesh); return go; } public GameObject Torus(Material m, Vector3 pos, float R = 0.7f, float r = 0.28f) => MeshObj(Meshes.Torus(R, r), m, pos, 1, "Torus"); public GameObject Knot(Material m, Vector3 pos, float scale = 1f) => MeshObj(Meshes.TorusKnot(), m, pos, scale, "Knot"); /// 细分平面(顶点动画、水面用),中心在 pos,边长 size,每边 n 段 public GameObject Grid(Material m, Vector3 pos, float size = 4f, int n = 128) => MeshObj(Meshes.Grid(size, n), m, pos, 1, "Grid"); /// 一排小球:count 个,从 x0 起每隔 step 一个,matFor(i) 给出第 i 个的材质 public void SphereRow(int count, Func<int, Material> matFor, float y = 0.5f, float z = 0f, float step = 1.1f, float scale = 1f) { float x0 = -(count - 1) * step * 0.5f; for (int i = 0; i < count; i++) Sphere(matFor(i), new Vector3(x0 + i * step, y, z), scale); } // ---------- 灯光 ---------- public Light Point(Vector3 pos, Color c, float intensity = 2f, float range = 6f) { var go = new GameObject("Point"); go.transform.position = pos; var l = go.AddComponent<Light>(); l.type = LightType.Point; l.color = c; l.intensity = intensity; l.range = range; return l; } public Light Spot(Vector3 pos, Vector3 target, Color c, float intensity = 4f, float range = 10f, float angle = 40f) { var go = new GameObject("Spot"); go.transform.position = pos; go.transform.LookAt(target); var l = go.AddComponent<Light>(); l.type = LightType.Spot; l.color = c; l.intensity = intensity; l.range = range; l.spotAngle = angle; return l; } public void SunDir(float pitch, float yaw, float intensity = 1.3f) { Sun.transform.rotation = Quaternion.Euler(pitch, yaw, 0); Sun.intensity = intensity; } // ---------- 相机与背景 ---------- public void LookAt(Vector3 pos, Vector3 target, float fov = 35f) { Cam.transform.position = pos; Cam.transform.LookAt(target); Cam.fieldOfView = fov; } public void Background(Color c) { Cam.clearFlags = CameraClearFlags.SolidColor; Cam.backgroundColor = c; } // ---------- URP ---------- /// 打开相机后处理并返回全局 Volume 的 Profile,用 profile.Add<Bloom>() 添加效果 public VolumeProfile Post() { CamData.renderPostProcessing = true; var go = new GameObject("Global Volume"); var v = go.AddComponent<Volume>(); v.isGlobal = true; var p = ScriptableObject.CreateInstance<VolumeProfile>(); Temp.Add(p); v.sharedProfile = p; return p; } /// 往本张图专用的 Renderer 里加一个 Renderer Feature public T Feature<T>() where T : ScriptableRendererFeature { var f = ScriptableObject.CreateInstance<T>(); f.name = typeof(T).Name; Temp.Add(f); Renderer.rendererFeatures.Add(f); Renderer.SetDirty(); return f; } /// 设置 URP Asset 上没有公开 setter 的字段(例如 m_SoftShadowsSupported) public void AssetProp(string field, bool value) { var so = new SerializedObject(Asset); so.FindProperty(field).boolValue = value; so.ApplyModifiedPropertiesWithoutUndo(); } public void AssetProp(string field, int value) { var so = new SerializedObject(Asset); var p = so.FindProperty(field); p.intValue = value; so.ApplyModifiedPropertiesWithoutUndo(); } } }