// WL-816f §2 — 「같은 화각·같은 거리」 바닥 비교용 렌더. // 표준 바닥 카메라 = 직교 size 5 · euler(30,60,0) · 1080×1920 (814r/816a/816e 화각과 같은 방향) // 데모는 에디트 모드에서 인스턴싱을 직접 발행해 그린다(데모 씬 Play 금지 · §9). public static class WL816f_Cmp { public const string Dir = "Screenshots_WL/WL816f/"; public const int W = 1080, H = 1920; // ── 데모 바닥 ────────────────────────────────────────────────────── public static void Demo() { UnityEditor.SceneManagement.EditorSceneManager.OpenScene( "Assets/3DPixelArtEnvironment/Demo/Demo.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); var sb = new System.Text.StringBuilder(); // 평평한 풀밭 지점을 찾는다(경사가 작고 1층 스플랫이 지배적인 곳) var terr = UnityEngine.Object.FindFirstObjectByType(); UnityEngine.Vector3 spot = new UnityEngine.Vector3(130f, 0f, 90f); if (terr != null) { var td = terr.terrainData; float best = 999f; for (float x = 40f; x < 180f; x += 10f) for (float z = 40f; z < 180f; z += 10f) { float u = x / td.size.x, v = z / td.size.z; var nrm = td.GetInterpolatedNormal(u, v); float slope = 1f - nrm.y; var c = td.alphamapTextures[0].GetPixelBilinear(u, v); if (c.r < 0.9f) continue; // 1층(풀)이 지배적인 곳만 if (slope < best) { best = slope; spot = new UnityEngine.Vector3(x, td.GetInterpolatedHeight(u, v) + terr.transform.position.y, z); } } sb.AppendLine("데모 바닥 지점 " + spot.ToString("F2") + " slope=" + best.ToString("F4")); } var cam = GroundCam(spot, 6f); int n = WL816f_Inst.Publish(sb); Shoot(cam, Dir + "x_demo_ground.png", sb); // 넓은 화각(ortho 10)도 하나 cam = GroundCam(spot, 10f); WL816f_Inst.Publish(sb); Shoot(cam, Dir + "x_demo_wide.png", sb); sb.AppendLine("데모 인스턴싱 구성 " + n); System.IO.File.WriteAllText("AgentScripts/WL816f_DEMO.txt", sb.ToString()); UnityEngine.Debug.Log(sb.ToString()); } /// 표준 바닥 카메라 — 지점 spot 을 데모 화각으로 본다. public static UnityEngine.Camera GroundCam(UnityEngine.Vector3 spot, float size) { var go = UnityEngine.GameObject.Find("~WL816fGroundCam"); if (go == null) { go = new UnityEngine.GameObject("~WL816fGroundCam"); go.hideFlags = UnityEngine.HideFlags.DontSave; } var c = go.GetComponent(); if (c == null) c = go.AddComponent(); c.orthographic = true; c.orthographicSize = size; c.nearClipPlane = 0.3f; c.farClipPlane = 500f; c.clearFlags = UnityEngine.CameraClearFlags.SolidColor; c.backgroundColor = new UnityEngine.Color(0.192f, 0.302f, 0.475f, 1f); c.cullingMask = ~0; c.depth = -100f; var rot = UnityEngine.Quaternion.Euler(30f, 60f, 0f); go.transform.rotation = rot; go.transform.position = spot - rot * UnityEngine.Vector3.forward * 60f; if (go.GetComponent() == null) go.AddComponent(); return c; } public static void Shoot(UnityEngine.Camera cam, string path, System.Text.StringBuilder sb) { if (cam == null) { if (sb != null) sb.AppendLine("카메라 없음 " + path); return; } System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); var rt = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); rt.antiAliasing = 1; rt.Create(); var prevT = cam.targetTexture; var prevA = UnityEngine.RenderTexture.active; cam.targetTexture = rt; cam.Render(); UnityEngine.RenderTexture.active = rt; var tex = new UnityEngine.Texture2D(W, H, UnityEngine.TextureFormat.RGB24, false); tex.ReadPixels(new UnityEngine.Rect(0, 0, W, H), 0, 0); tex.Apply(); System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex)); UnityEngine.RenderTexture.active = prevA; cam.targetTexture = prevT; UnityEngine.Object.DestroyImmediate(tex); rt.Release(); UnityEngine.Object.DestroyImmediate(rt); WL816f_Inst.Free(); if (sb != null) sb.AppendLine("캡처 " + path); } } /// 에디트 모드에서 InstancesBehaviour 가 그릴 것을 이번 프레임에 직접 발행한다(Play 없이). public static class WL816f_Inst { static readonly System.Collections.Generic.List s_pending = new System.Collections.Generic.List(); public static int Publish(System.Text.StringBuilder sb) { int n = 0; long inst = 0; var bs = UnityEngine.Object.FindObjectsByType( UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); for (int i = 0; i < bs.Length; i++) { var b = bs[i]; System.Collections.Generic.Dictionary> data = null; try { data = b.GetInstanceData(); } catch { continue; } if (data == null) continue; var bounds = b.CalculateInstancesBounds(); var l2w = b.transform.localToWorldMatrix; l2w.m03 = 0; l2w.m13 = 0; l2w.m23 = 0; foreach (var kv in data) { if (kv.Value == null || kv.Value.Count == 0) continue; var cfg = new Environment.Instancing.InstancingConfiguration(kv.Key, kv.Value, "_InstanceData"); cfg.MaterialPropertyBlock.SetMatrix("_LocalToWorld", l2w); UnityEngine.Graphics.DrawMeshInstancedIndirect(cfg.Mesh, 0, cfg.Material, bounds, cfg.CommandBuffer, 0, cfg.MaterialPropertyBlock, UnityEngine.Rendering.ShadowCastingMode.Off, false, 0); n++; inst += kv.Value.Count; s_pending.Add(cfg); } } if (sb != null) sb.AppendLine(" 인스턴스 " + inst + " (구성 " + n + ")"); return n; } public static void Free() { for (int i = 0; i < s_pending.Count; i++) s_pending[i].FreeMemory(); s_pending.Clear(); } }