源码 / Ch17/Editor/Shots17.cs
Ch17/Editor/Shots17.cs
// 第 17 章配图。拍摄时 _Time≈0,所有动画都用材质上的 _Phase/_Progress/_Frame 摆出姿态。 using Cookbook.Core; using UnityEngine; using UnityEngine.Rendering; namespace Cookbook.Ch17 { static class Shots17 { // 程序化旗面图案:三色横条 + 圆形徽记 static Texture2D FlagTex() { const int W = 256, H = 160; var t = new Texture2D(W, H, TextureFormat.RGBA32, true, false) { wrapMode = TextureWrapMode.Clamp, hideFlags = HideFlags.DontSave }; var px = new Color[W * H]; for (int y = 0; y < H; y++) for (int x = 0; x < W; x++) { float v = (float)y / H; Color c = v > 0.66f ? new Color(0.12f, 0.3f, 0.6f) : v > 0.33f ? new Color(0.95f, 0.93f, 0.88f) : new Color(0.75f, 0.15f, 0.15f); float dx = (x - W * 0.3f) / H, dy = (y - H * 0.5f) / H; float r = Mathf.Sqrt(dx * dx + dy * dy); if (r < 0.22f) c = r > 0.17f ? new Color(0.95f, 0.75f, 0.2f) : new Color(0.12f, 0.3f, 0.6f); px[y * W + x] = c; } t.SetPixels(px); t.Apply(true); return t; } static GameObject FlagWithPole(Shot s, Material flag, Vector3 polePos) { s.Cylinder(s.Lit(new Color(0.55f, 0.55f, 0.58f), 0.8f, 0.6f), polePos + new Vector3(0, 1.4f, 0), 1f) .transform.localScale = new Vector3(0.06f, 1.4f, 0.06f); // 网格是物体空间 XZ 平面(2×2 米):绕 X 转 -90° 立起来,再把 Z 压成 1.25 米高 var go = s.MeshObj(Meshes.Grid(2f, 80), flag, polePos + new Vector3(1.0f, 2.1f, 0), 1f, "Flag"); go.transform.rotation = Quaternion.Euler(-90, 0, 0); go.transform.localScale = new Vector3(1f, 1f, 0.62f); return go; } static Material FlagMat(Shot s) { var m = s.Mat("Cookbook/Ch17/Flag"); m.SetTexture("_BaseMap", FlagTex()); m.SetTexture("_NoiseTex", s.CoreTex("noise_fbm")); m.SetFloat("_Phase", 1.3f); return m; } [BookShot("ch17-flag", W = 640, H = 480, Panels = 3)] static void Flag(Shot s) { var m = FlagMat(s); m.SetFloat("_RecalcNormal", s.Panel == 0 ? 0 : 1); m.SetFloat("_NoiseStrength", s.Panel == 2 ? 0.5f : 0f); s.Floor(); FlagWithPole(s, m, new Vector3(-1.0f, 0, 0)); s.LookAt(new Vector3(0.6f, 2.3f, -3.6f), new Vector3(0, 1.9f, 0), 40); s.SunDir(35, -60, 1.3f); } [BookShot("ch17-shadowsync", W = 640, H = 480, Panels = 2)] static void ShadowSync(Shot s) { var m = FlagMat(s); m.SetFloat("_Amplitude", 0.25f); m.SetFloat("_DeformShadow", s.Panel); s.Floor(); FlagWithPole(s, m, new Vector3(-1.0f, 0, 0)); s.LookAt(new Vector3(0.3f, 4.2f, -3.2f), new Vector3(0.1f, 0.6f, 0.9f), 45); s.SunDir(62, -20, 1.3f); } // ---------------- 草 ---------------- [BookShot("ch17-grass", W = 640, H = 480, Panels = 2)] static void Grass(Shot s) { var ground = s.Lit(new Color(0.16f, 0.24f, 0.08f), 0, 0.1f); s.Floor(ground); var m = s.Mat("Cookbook/Ch17/Grass"); m.SetTexture("_WindNoise", s.CoreTex("noise_fbm")); m.SetFloat("_WindStrength", s.Panel == 0 ? 0 : 0.45f); m.SetFloat("_Sway", s.Panel == 0 ? 0 : 0.12f); m.SetFloat("_Phase", 2f); s.MeshObj(GrassField.Build(14f, 60000), m, new Vector3(0, 0, 4.5f), 1f, "Grass"); s.Sphere(s.Lit(new Color(0.8f, 0.3f, 0.25f), 0, 0.5f), new Vector3(0.9f, 0.5f, 2.2f), 1f); s.LookAt(new Vector3(0, 1.5f, -2.4f), new Vector3(0, 0.3f, 2.5f), 45); s.SunDir(35, -40, 1.3f); } // ---------------- Gerstner ---------------- [BookShot("ch17-gerstner", W = 640, H = 480, Panels = 3)] static void Gerstner(Shot s) { var m = s.Mat("Cookbook/Ch17/Gerstner"); m.SetFloat("_Phase", 0.7f); if (s.Panel < 2) { // 单个波:左 = 只有竖直位移的正弦波,中 = 同样参数的 Gerstner 波 m.SetVector("_WaveA", new Vector4(1, 0.15f, 0.85f, 3.2f)); m.SetVector("_WaveB", Vector4.zero); m.SetVector("_WaveC", Vector4.zero); m.SetVector("_WaveD", Vector4.zero); m.SetFloat("_Horizontal", s.Panel); m.SetFloat("_FoamThreshold", 0.25f); } else { m.SetVector("_WaveA", new Vector4(1, 0.2f, 0.4f, 4.5f)); m.SetVector("_WaveB", new Vector4(0.3f, 1, 0.25f, 3.1f)); m.SetVector("_WaveC", new Vector4(1, 0.8f, 0.2f, 1.7f)); m.SetVector("_WaveD", new Vector4(-0.5f, 1, 0.12f, 1.1f)); } s.Grid(m, new Vector3(0, 0, 4), 18, 260); s.LookAt(new Vector3(0, 2.4f, -3.2f), new Vector3(0, 0.0f, 3.5f), 45); s.SunDir(22, 160, 1.4f); // 逆光,便于看到高光带和波峰轮廓 } // ---------------- 膨胀 / 呼吸 ---------------- [BookShot("ch17-inflate", W = 480, H = 400, Panels = 4)] static void Inflate(Shot s) { s.Floor(); Material M(float amount, bool radial, float pulse = 0) { var m = s.Mat("Cookbook/Ch17/Inflate"); m.SetFloat("_Amount", amount); m.SetFloat("_PulseFreq", pulse); m.DisableKeyword("_DIR_NORMAL"); m.DisableKeyword("_DIR_RADIAL"); m.EnableKeyword(radial ? "_DIR_RADIAL" : "_DIR_NORMAL"); m.SetFloat("_Dir", radial ? 1 : 0); return m; } if (s.Panel < 3) { var m = s.Panel == 0 ? M(0, false) : s.Panel == 1 ? M(0.07f, false) : M(0.09f, false, 14f); if (s.Panel == 2) m.SetFloat("_Phase", 0f); s.Knot(m, new Vector3(0, 1.0f, 0), 1.1f); s.LookAt(new Vector3(0, 1.9f, -3.6f), new Vector3(0, 0.9f, 0), 38); } else { s.Cube(M(0.12f, false), new Vector3(-0.75f, 0.55f, 0), 0.8f).transform.rotation = Quaternion.Euler(0, 35, 0); s.Cube(M(0.12f, true), new Vector3(0.75f, 0.55f, 0), 0.8f).transform.rotation = Quaternion.Euler(0, 35, 0); s.LookAt(new Vector3(0, 1.9f, -3.6f), new Vector3(0, 0.6f, 0), 38); } s.SunDir(45, -35, 1.2f); } // ---------------- 碎裂 ---------------- [BookShot("ch17-shatter", W = 640, H = 480, Panels = 3)] static void Shatter(Shot s) { s.Floor(); float[] prog = { 0f, 0.3f, 0.55f }; var m = s.Mat("Cookbook/Ch17/Shatter"); m.SetFloat("_Progress", prog[s.Panel]); m.SetVector("_SweepDir", new Vector4(0, 0, 1, 0)); // 圆环物体空间的 z 轴立起来后朝上 m.SetFloat("_Distance", 0.5f); m.SetFloat("_Spin", 5f); var torus = s.MeshObj(ShatterMesh.Build(Meshes.Torus(0.75f, 0.3f)), m, new Vector3(-0.8f, 1.1f, 0.2f), 1f, "Torus"); torus.transform.rotation = Quaternion.Euler(80, 20, 0); var src2 = GameObject.CreatePrimitive(PrimitiveType.Sphere); var mesh2 = ShatterMesh.Build(src2.GetComponent<MeshFilter>().sharedMesh, 11); Object.DestroyImmediate(src2); var m2 = s.Mat("Cookbook/Ch17/Shatter"); m2.SetFloat("_Progress", prog[s.Panel]); m2.SetColor("_BaseColor", new Color(0.85f, 0.5f, 0.3f)); m2.SetVector("_SweepDir", new Vector4(1, 0, 0, 0)); m2.SetFloat("_Distance", 0.6f); m2.SetFloat("_Gravity", 0.9f); s.MeshObj(mesh2, m2, new Vector3(0.9f, 0.8f, 0.2f), 1.4f, "Sphere"); s.LookAt(new Vector3(0, 2.0f, -4.6f), new Vector3(0, 1.0f, 0), 38); s.SunDir(45, -35, 1.2f); s.Background(new Color(0.12f, 0.13f, 0.16f)); } // ---------------- VAT ---------------- static VatBaker.Result s_Vat; const int VatFrames = 48; static void EnsureVat() { if (s_Vat.mesh != null) return; var baseMesh = VatBaker.Tentacle(); var rest = baseMesh.vertices; s_Vat = VatBaker.Bake(baseMesh, VatFrames, f => VatBaker.TentacleFrame(rest, f, VatFrames)); s_Vat.mesh.hideFlags = s_Vat.position.hideFlags = s_Vat.normal.hideFlags = HideFlags.DontSave; } // 把位置贴图可视化:按包围盒把 xyz 映射到 0..1 显示 static Texture2D VisualizeVat(Texture2D pos, Bounds b) { var px = pos.GetPixels(); var outPx = new Color[px.Length]; for (int i = 0; i < px.Length; i++) { var p = new Vector3(px[i].r, px[i].g, px[i].b) - b.min; outPx[i] = new Color(p.x / b.size.x, p.y / b.size.y, p.z / b.size.z, 1); } var t = new Texture2D(pos.width, pos.height, TextureFormat.RGBA32, false, true) { filterMode = FilterMode.Point, hideFlags = HideFlags.DontSave }; t.SetPixels(outPx); t.Apply(false); return t; } [BookShot("ch17-vat", W = 640, H = 480, Panels = 2)] static void Vat(Shot s) { EnsureVat(); if (s.Panel == 0) { s.Background(new Color(0.95f, 0.95f, 0.95f)); s.Cam.orthographic = true; s.Cam.orthographicSize = 0.5f; s.Cam.transform.SetPositionAndRotation(new Vector3(0, 0, -5), Quaternion.identity); var m = s.Mat("Universal Render Pipeline/Unlit"); m.SetTexture("_BaseMap", VisualizeVat(s_Vat.position, s_Vat.mesh.bounds)); // 只显示靠近触手尖端的后 40% 顶点(列),纵向是全部 48 帧 m.SetTextureScale("_BaseMap", new Vector2(0.4f, 1)); m.SetTextureOffset("_BaseMap", new Vector2(0.6f, 0)); var q = s.Quad(m, Vector3.zero); q.transform.localScale = new Vector3(1.25f, 0.92f, 1); return; } s.Floor(); for (int i = 0; i < 5; i++) { var m = s.Mat("Cookbook/Ch17/VAT"); m.SetTexture("_PosTex", s_Vat.position); m.SetTexture("_NrmTex", s_Vat.normal); m.SetFloat("_Frame", i * 4.5f); // 同一份数据,不同的帧偏移 var go = new GameObject("Tentacle"); go.transform.position = new Vector3(-2.0f + i * 1.0f, 0, 0.3f); go.AddComponent<MeshFilter>().sharedMesh = s_Vat.mesh; go.AddComponent<MeshRenderer>().sharedMaterial = m; } s.LookAt(new Vector3(0, 1.9f, -5.6f), new Vector3(0, 1.1f, 0.3f), 40); s.SunDir(40, -30, 1.3f); } } }