源码 / Ch25/Editor/Ch25Shots.cs
Ch25/Editor/Ch25Shots.cs
// 第 25 章配图:每张图都在空场景里搭景,并给本张图专用的 Renderer 加上要演示的 Feature。 using System.IO; using Cookbook.Core; using UnityEngine; using UnityEngine.Experimental.Rendering.Universal; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace Cookbook.Ch25 { static class Ch25Shots { const int LayerA = 8; // 演示用的“选中层” const int LayerB = 9; // 演示用的“幽灵层” // 通用场景:棋盘地面 + 几个彩色物体 static void Stage(Shot s, int markedLayer = -1) { s.Floor(); var knot = s.Knot(s.Lit(new Color(0.95f, 0.45f, 0.12f), 0f, 0.6f), new Vector3(0, 0.95f, 0.4f), 1.0f); var red = 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); if (markedLayer >= 0) { knot.layer = markedLayer; red.layer = markedLayer; } s.LookAt(new Vector3(0, 1.75f, -5.3f), new Vector3(0, 0.6f, 0.2f), 38); } [BookShot("ch25-tint", W = 640, H = 400, Panels = 3)] static void Tint(Shot s) { Stage(s); if (s.Panel == 0) return; // 第 1 格:不加 Feature var f = s.Feature<TintFeature>(); if (s.Panel == 1) { f.settings.invert = 1; f.settings.desaturate = 0; } else { f.settings.invert = 0; f.settings.desaturate = 1; f.settings.tint = new Color(1.25f, 0.95f, 0.65f); } } // 案例 25.2:同一个“去色”Pass 放在三个不同事件上 [BookShot("ch25-event", W = 640, H = 400, Panels = 3)] static void EventOrder(Shot s) { if (s.Panel == 0) { // 顺便把生命周期探针的输出写进文件,正文里引用的就是这份真实日志 string path = Path.GetFullPath(Path.Combine(Application.dataPath, "../Logs/ch25_lifecycle.txt")); File.WriteAllText(path, ""); LifecycleProbeFeature.Sink = line => File.AppendAllText(path, line + "\n"); s.Feature<LifecycleProbeFeature>(); } else LifecycleProbeFeature.Sink = null; Stage(s); var glass = s.Mat("Cookbook/Ch25/SimpleTransparent"); glass.SetColor("_BaseColor", new Color(0.2f, 0.85f, 1f, 0.55f)); s.Sphere(glass, new Vector3(-0.35f, 0.7f, -1.2f), 0.9f); var glass2 = s.Mat("Cookbook/Ch25/SimpleTransparent"); glass2.SetColor("_BaseColor", new Color(1f, 0.25f, 0.7f, 0.55f)); s.Cube(glass2, new Vector3(1.0f, 0.55f, -1.3f), 0.55f).transform.rotation = Quaternion.Euler(20, 40, 0); s.LookAt(new Vector3(0, 1.6f, -4.6f), new Vector3(0, 0.75f, 0), 40); var f = s.Feature<TintFeature>(); f.settings.invert = 0; f.settings.desaturate = 1; f.settings.passEvent = s.Panel == 0 ? RenderPassEvent.AfterRenderingOpaques : s.Panel == 1 ? RenderPassEvent.AfterRenderingSkybox : RenderPassEvent.AfterRenderingTransparents; } [BookShot("ch25-fullscreen", W = 640, H = 400, Panels = 3)] static void FullScreen(Shot s) { Stage(s); // 加一排远处的柱子,让深度雾有层次 for (int i = 0; i < 6; i++) s.Cylinder(s.Lit(new Color(0.8f, 0.78f, 0.72f), 0, 0.3f), new Vector3(-5f + i * 2f, 1.5f, 4f + i * 2.5f), 1f) .transform.localScale = new Vector3(0.6f, 1.5f, 0.6f); if (s.Panel == 0) return; var f = s.Feature<FullScreenPassRendererFeature>(); if (s.Panel == 1) { f.passMaterial = s.Mat("Cookbook/Ch25/FullscreenNightVision"); f.injectionPoint = FullScreenPassRendererFeature.InjectionPoint.AfterRenderingPostProcessing; f.fetchColorBuffer = true; // 需要读屏幕颜色 f.requirements = ScriptableRenderPassInput.None; } else { f.passMaterial = s.Mat("Cookbook/Ch25/FullscreenDepthFog"); f.injectionPoint = FullScreenPassRendererFeature.InjectionPoint.BeforeRenderingPostProcessing; f.fetchColorBuffer = true; f.requirements = ScriptableRenderPassInput.Depth; // 需要 _CameraDepthTexture } } [BookShot("ch25-renderobjects", W = 640, H = 400, Panels = 2)] static void RenderObjectsShot(Shot s) { Stage(s, LayerA); // 纽结和红球放到第 8 层 if (s.Panel == 0) return; var f = s.Feature<RenderObjects>(); f.settings.Event = RenderPassEvent.AfterRenderingOpaques; f.settings.filterSettings.RenderQueueType = RenderQueueType.Opaque; f.settings.filterSettings.LayerMask = 1 << LayerA; f.settings.overrideMode = RenderObjects.RenderObjectsSettings.OverrideMaterialMode.Material; f.settings.overrideMaterial = s.Mat("Cookbook/Ch25/RimOverlay"); f.settings.overrideMaterialPassIndex = 0; // RenderObjects 只在 Create() 里把 settings 转成 Pass 参数。这里 Renderer 尚未创建, // 它创建时会调用每个 Feature 的 Create();若 Renderer 已在运行,改完 settings 必须手动再调一次。 f.Create(); } // 案例 25.5:遮罩 RT 的调试视图(透视 vs 只看可见部分) [BookShot("ch25-layer-mask", W = 640, H = 400, Panels = 3)] static void LayerMaskShot(Shot s) { Stage(s, LayerA); // 一块挡在红球前面的板子,用来观察“透视/不透视”的区别 s.Cube(s.Lit(new Color(0.55f, 0.5f, 0.45f), 0, 0.3f), new Vector3(-1.2f, 0.45f, -1.25f), 1f) .transform.localScale = new Vector3(0.9f, 0.9f, 0.08f); if (s.Panel == 0) return; var f = s.Feature<LayerOutlineFeature>(); f.settings.layerMask = 1 << LayerA; f.settings.mode = LayerOutlineFeature.Mode.DebugMask; f.settings.seeThrough = s.Panel == 1; } [BookShot("ch25-layer-outline", W = 640, H = 400, Panels = 3)] static void LayerOutlineShot(Shot s) { Stage(s, LayerA); s.Cube(s.Lit(new Color(0.55f, 0.5f, 0.45f), 0, 0.3f), new Vector3(-1.2f, 0.45f, -1.25f), 1f) .transform.localScale = new Vector3(0.9f, 0.9f, 0.08f); var f = s.Feature<LayerOutlineFeature>(); f.settings.layerMask = 1 << LayerA; f.settings.width = 4; switch (s.Panel) { case 0: f.settings.seeThrough = true; break; case 1: f.settings.seeThrough = false; break; default: f.settings.seeThrough = true; f.settings.outlineColor = new Color(0.2f, 0.9f, 1f, 1f); f.settings.fillColor = new Color(0.2f, 0.9f, 1f, 0.35f); // 纯色填充 + 描边 f.settings.width = 6; break; } } // 案例 25.7:幽灵层 static void GhostStage(Shot s, bool naive) { s.Floor(); s.Cube(s.Lit(new Color(0.15f, 0.35f, 0.9f), 0f, 0.4f), new Vector3(1.6f, 0.4f, 0.4f), 0.8f); s.Sphere(s.Lit(new Color(0.85f, 0.12f, 0.12f), 0f, 0.75f), new Vector3(-1.7f, 0.45f, 0.6f), 0.9f); // 一根挡在角色前方的柱子:验证合成时的深度遮挡 s.Cylinder(s.Lit(new Color(0.8f, 0.78f, 0.72f), 0, 0.3f), new Vector3(0.55f, 1f, -0.9f), 1f) .transform.localScale = new Vector3(0.3f, 1f, 0.3f); // 由几个基本体拼成的“小人”:身体、头、双臂、双腿互相穿插 Material mat; if (naive) { mat = s.Mat("Cookbook/Ch25/SimpleTransparent"); mat.SetColor("_BaseColor", new Color(0.6f, 0.9f, 1.0f, 0.45f)); } else mat = s.Lit(new Color(0.75f, 0.92f, 1f), 0f, 0.6f); var root = new GameObject("Ghost").transform; void Part(PrimitiveType t, Vector3 p, Vector3 scl, Vector3 rot) { var go = s.Prim(t, mat, p); go.transform.localScale = scl; go.transform.rotation = Quaternion.Euler(rot); go.transform.SetParent(root, true); go.layer = LayerB; go.GetComponent<Renderer>().shadowCastingMode = ShadowCastingMode.Off; } Part(PrimitiveType.Capsule, new Vector3(0, 1.05f, 0), new Vector3(0.55f, 0.5f, 0.4f), Vector3.zero); // 身体 Part(PrimitiveType.Sphere, new Vector3(0, 1.75f, 0), Vector3.one * 0.48f, Vector3.zero); // 头 Part(PrimitiveType.Capsule, new Vector3(-0.22f, 1.05f, -0.28f), new Vector3(0.2f, 0.42f, 0.2f), new Vector3(-55, 0, -40)); // 左臂(向前抱在胸前) Part(PrimitiveType.Capsule, new Vector3(0.22f, 1.05f, -0.28f), new Vector3(0.2f, 0.42f, 0.2f), new Vector3(-55, 0, 40)); // 右臂 Part(PrimitiveType.Capsule, new Vector3(-0.15f, 0.42f, 0), new Vector3(0.22f, 0.42f, 0.22f), Vector3.zero); // 左腿 Part(PrimitiveType.Capsule, new Vector3(0.15f, 0.42f, 0), new Vector3(0.22f, 0.42f, 0.22f), Vector3.zero); // 右腿 s.LookAt(new Vector3(0, 1.5f, -4.0f), new Vector3(0, 0.95f, 0), 38); } [BookShot("ch25-ghost", W = 640, H = 440, Panels = 3)] static void Ghost(Shot s) { GhostStage(s, naive: s.Panel == 0); if (s.Panel == 0) return; // 第 1 格:逐物体半透明(对照组) // 幽灵层不参与主不透明 Pass,只由 Feature 单独绘制 s.Renderer.opaqueLayerMask = ~(1 << LayerB); var f = s.Feature<GhostLayerFeature>(); f.settings.layerMask = 1 << LayerB; if (s.Panel == 1) { f.settings.rimWidth = 0; f.settings.opacity = 0.45f; } else { f.settings.rimWidth = 3; f.settings.opacity = 0.3f; } } } }