// WL-814q — 캡처 4장(+개요) · 1080×1920 · Screenshots_WL/WL814q/ public static class WL814q_Capture { const string Out = "Screenshots_WL/WL814q/"; const string ScenePath = "Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity"; const string DemoPath = "Assets/3DPixelArtEnvironment/Demo/Demo.unity"; const int W = 1080, H = 1920; static UnityEngine.Texture2D Shot(UnityEngine.Camera cam, int w, int h) { var rt = new UnityEngine.RenderTexture(w, h, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); rt.filterMode = UnityEngine.FilterMode.Point; rt.antiAliasing = 1; rt.Create(); var prev = cam.targetTexture; cam.targetTexture = rt; cam.Render(); var oldActive = UnityEngine.RenderTexture.active; 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(); UnityEngine.RenderTexture.active = oldActive; cam.targetTexture = prev; rt.Release(); UnityEngine.Object.DestroyImmediate(rt); return tex; } /// 점(Point) 확대 — 도트 격자를 그대로 키운다. static UnityEngine.Texture2D Upscale(UnityEngine.Texture2D src, int w, int h) { var d = new UnityEngine.Texture2D(w, h, UnityEngine.TextureFormat.RGB24, false); var sp = src.GetPixels32(); var dp = new UnityEngine.Color32[w * h]; for (int y = 0; y < h; y++) { int sy = y * src.height / h; if (sy >= src.height) sy = src.height - 1; for (int x = 0; x < w; x++) { int sx = x * src.width / w; if (sx >= src.width) sx = src.width - 1; dp[y * w + x] = sp[sy * src.width + sx]; } } d.SetPixels32(dp); d.Apply(); return d; } static void Save(UnityEngine.Texture2D t, string name) { var dir = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), Out); System.IO.Directory.CreateDirectory(dir); System.IO.File.WriteAllBytes(System.IO.Path.Combine(dir, name), UnityEngine.ImageConversion.EncodeToPNG(t)); UnityEngine.Object.DestroyImmediate(t); } /// 도트A: pixelHeight 240 저해상도 렌더 → 점 확대. static UnityEngine.Texture2D DotA(UnityEngine.Camera cam, float orthoSize) { bool o = cam.orthographic; float os = cam.orthographicSize; cam.orthographic = true; cam.orthographicSize = orthoSize; int ph = 240, pw = UnityEngine.Mathf.RoundToInt(240f * W / H); // 135×240 var small = Shot(cam, pw, ph); var big = Upscale(small, W, H); UnityEngine.Object.DestroyImmediate(small); cam.orthographic = o; cam.orthographicSize = os; return big; } static UnityEngine.Texture2D SideBySide(UnityEngine.Texture2D a, UnityEngine.Texture2D b, int gap) { int w = a.width + gap + b.width, h = UnityEngine.Mathf.Max(a.height, b.height); var d = new UnityEngine.Texture2D(w, h, UnityEngine.TextureFormat.RGB24, false); var px = new UnityEngine.Color32[w * h]; for (int i = 0; i < px.Length; i++) px[i] = new UnityEngine.Color32(18, 18, 22, 255); d.SetPixels32(px); d.Apply(); UnityEngine.Graphics.CopyTexture(a, 0, 0, 0, 0, a.width, a.height, d, 0, 0, 0, 0); UnityEngine.Graphics.CopyTexture(b, 0, 0, 0, 0, b.width, b.height, d, 0, 0, a.width + gap, 0); return d; } public static string Run() { var sb = new System.Text.StringBuilder(); var scene = UnityEditor.SceneManagement.EditorSceneManager.OpenScene(ScenePath, UnityEditor.SceneManagement.OpenSceneMode.Single); // 🔴 Water_Plane 프리팹의 PlanarReflectionProbe 도 Camera 다 — 이름으로 고른다 UnityEngine.Camera cam = null; foreach (var c in UnityEngine.Object.FindObjectsByType( UnityEngine.FindObjectsSortMode.None)) if (c.gameObject.name == "Main Camera") cam = c; if (cam == null) return "NO CAMERA"; var camPos = cam.transform.position; var camRot = cam.transform.rotation; // 🔴 씬을 연 직후 첫 렌더는 터레인 머티리얼/스플랫이 아직 안 붙는다 — 버리는 렌더 2장으로 예열 for (int i = 0; i < 2; i++) UnityEngine.Object.DestroyImmediate(Shot(cam, 64, 64)); // ⓐ 일반 모드 — 같은 위치·각도 · 퍼스펙티브 · 풀해상도(도트 없음) cam.orthographic = false; // 주시점까지 거리에서 세로 화각이 ortho 3.4 와 같아지도록 FOV 를 맞춘다(같은 구도 유지) float dist = UnityEngine.Vector3.Distance(camPos, new UnityEngine.Vector3(0f, 1.06f, 0f)); cam.fieldOfView = 2f * UnityEngine.Mathf.Atan2(3.4f, dist) * UnityEngine.Mathf.Rad2Deg; var a = Shot(cam, W, H); Save(a, "a_normal_1080x1920.png"); sb.AppendLine("a_normal: perspective fov=" + cam.fieldOfView.ToString("F2") + " dist=" + dist.ToString("F3")); cam.orthographic = true; cam.orthographicSize = 3.4f; // ⓑ 도트A — 같은 구도 · orthoSize 3.4 · pixelHeight 240 Save(DotA(cam, 3.4f), "b_dotA_1080x1920.png"); sb.AppendLine("b_dotA: ortho 3.4 · 135x240 -> point x8"); // ⓒ 캐릭터가 보이는 눈높이 구도(도트A) — 더 가깝고 낮게 // 캐릭터를 크게 — 도트A 각도(43.61°) 유지 · orthoSize 만 줄이고 주시점을 앞으로 밀어 캐릭터를 아래쪽에 cam.transform.position = camPos; cam.transform.rotation = camRot; var fwd = cam.transform.forward; fwd.y = 0f; fwd.Normalize(); cam.transform.position = camPos + fwd * 1.9f; Save(DotA(cam, 2.2f), "c_dotA_eye_1080x1920.png"); sb.AppendLine("c_dotA_eye: ortho 2.0 · 135x240 -> point x8 · pos=" + cam.transform.position); // 개요(참고) — 아레나 전체 + 연못 + 풍차 (퍼스펙티브 3/4 뷰) cam.orthographic = false; cam.fieldOfView = 40f; cam.transform.position = new UnityEngine.Vector3(30f, 20f, -33f); cam.transform.LookAt(new UnityEngine.Vector3(1f, 1.5f, -1f)); Save(Shot(cam, W, H), "e_overview_1080x1920.png"); sb.AppendLine("e_overview: perspective fov40 @" + cam.transform.position); cam.orthographic = true; cam.orthographicSize = 3.4f; // ⓓ 비교용 — 데모의 부품은 우리보다 5~6배 크다(House 9.2 m · Tree 13.5 m vs 캐릭터 1.19 m). // 도트A(ortho 3.4)로 데모를 찍으면 집 벽 한 조각만 나온다 → 두 씬 모두 ortho 10 · 같은 내림각 43.61° 로 맞춘다. const float CmpOrtho = 10f; float kk = CmpOrtho / 3.4f; // 각도 유지 · 카메라만 뒤로(near 0.3 에 잘리지 않게) cam.transform.position = camPos * kk; cam.transform.rotation = camRot; cam.orthographic = true; cam.orthographicSize = CmpOrtho; var mine = DotA(cam, CmpOrtho); var mineHalf = Upscale(mine, W / 2, H / 2); UnityEngine.Object.DestroyImmediate(mine); // 텍스처를 씬 전환에서 지키기 mineHalf.hideFlags = UnityEngine.HideFlags.HideAndDontSave; // 데모 씬 — 같은 규격(도트A)으로. 🔴 저장하지 않는다. UnityEditor.SceneManagement.EditorSceneManager.OpenScene(DemoPath, UnityEditor.SceneManagement.OpenSceneMode.Single); UnityEngine.Camera demoCam = null; foreach (var c in UnityEngine.Object.FindObjectsByType( UnityEngine.FindObjectsSortMode.None)) if (c.gameObject.name == "Main Camera") demoCam = c; UnityEngine.Texture2D demoHalf = null; if (demoCam != null) { // 데모의 집 앞(House (2))을 기준점으로 — 오브젝트 바운즈에서 직접 발밑을 잡는다(지형 추정 금지) var focus = new UnityEngine.Vector3(94f, 4f, 78f); var hg = UnityEngine.GameObject.Find("House (2)"); if (hg == null) hg = UnityEngine.GameObject.Find("Light_Post"); if (hg != null) { var hr = hg.GetComponentInChildren(); if (hr != null) { var hb = hr.bounds; focus = new UnityEngine.Vector3(hb.center.x, hb.min.y, hb.center.z); sb.AppendLine("demo focus = " + hg.name + " bounds " + hb.size + " @ " + focus); } } float camUp = 5.4335f, camHoriz = 4.5911f; float yaw = 215f; var dir = new UnityEngine.Vector3(UnityEngine.Mathf.Sin(yaw * UnityEngine.Mathf.Deg2Rad), 0f, UnityEngine.Mathf.Cos(yaw * UnityEngine.Mathf.Deg2Rad)); float k = CmpOrtho / 3.4f; // 같은 각도를 유지한 채 카메라만 뒤로 demoCam.transform.position = focus + UnityEngine.Vector3.up * camUp * k - dir * camHoriz * k; demoCam.transform.LookAt(focus + UnityEngine.Vector3.up * 1.06f); demoCam.orthographic = true; demoCam.orthographicSize = CmpOrtho; var d = DotA(demoCam, CmpOrtho); demoHalf = Upscale(d, W / 2, H / 2); demoHalf.hideFlags = UnityEngine.HideFlags.HideAndDontSave; UnityEngine.Object.DestroyImmediate(d); sb.AppendLine("demo shot ok @" + demoCam.transform.position); } else sb.AppendLine("DEMO CAMERA NOT FOUND"); if (demoHalf != null) { var cmp = SideBySide(demoHalf, mineHalf, 16); Save(cmp, "d_compare_demo_vs_arena.png"); sb.AppendLine("d_compare: left=Critter Demo · right=WL_ArenaProto (both dotA 240p)"); UnityEngine.Object.DestroyImmediate(demoHalf); } UnityEngine.Object.DestroyImmediate(mineHalf); // 🔴 데모 씬은 저장하지 않고 원래 씬으로 되돌린다 UnityEditor.SceneManagement.EditorSceneManager.OpenScene(ScenePath, UnityEditor.SceneManagement.OpenSceneMode.Single); sb.AppendLine("OUT " + System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), Out)); return sb.ToString(); } }