Project_WL/AgentScripts/WL814u_Probe2.cs

173 lines
9.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// WL814u_Probe2.cs — §1 보정 실측 + UV 점유 + 픽셀 떨림(pixel crawl) 실험 (에디터 전용)
// 🔴 씬 저장 0 · 에셋 수정 0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Rendering.Universal;
public static class WL814u_Probe2
{
const string SCENE = "Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity";
const string OUT = "AgentScripts/WL814u_PROBE2.txt";
const int LAYER = 31;
static readonly StringBuilder sb = new StringBuilder();
static void L(string s) { sb.AppendLine(s); Console.WriteLine("[814u2] " + s); }
public static void Run()
{
try { Body(); }
catch (Exception e) { L("EXCEPTION " + e); }
finally { Directory.CreateDirectory(Path.GetDirectoryName(OUT)); File.WriteAllText(OUT, sb.ToString(), new UTF8Encoding(true)); }
}
class Shot { public Color32[] px; public int w, h; }
static Shot Render(Camera cam, int w, int h, Color bg)
{
var rt = new RenderTexture(w, h, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
rt.antiAliasing = 1; rt.filterMode = FilterMode.Point;
var pT = cam.targetTexture; var pA = RenderTexture.active; var pC = cam.clearFlags; var pB = cam.backgroundColor;
cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = bg; cam.targetTexture = rt; cam.Render();
RenderTexture.active = rt;
var t = new Texture2D(w, h, TextureFormat.RGBA32, false); t.ReadPixels(new Rect(0, 0, w, h), 0, 0); t.Apply();
cam.targetTexture = pT; RenderTexture.active = pA; cam.clearFlags = pC; cam.backgroundColor = pB;
var s = new Shot { px = t.GetPixels32(), w = w, h = h };
UnityEngine.Object.DestroyImmediate(t); rt.Release(); UnityEngine.Object.DestroyImmediate(rt);
return s;
}
static bool[] Mask(Camera cam, int w, int h, out int x0, out int y0, out int x1, out int y1, out int cnt, out Shot color)
{
var b = Render(cam, w, h, Color.black); var wt = Render(cam, w, h, Color.white);
color = b;
var m = new bool[w * h]; x0 = w; y0 = h; x1 = -1; y1 = -1; cnt = 0;
for (int i = 0; i < m.Length; i++)
{
int d = Mathf.Max(Mathf.Abs(b.px[i].r - wt.px[i].r), Mathf.Max(Mathf.Abs(b.px[i].g - wt.px[i].g), Mathf.Abs(b.px[i].b - wt.px[i].b)));
if (d < 128) { m[i] = true; cnt++; int x = i % w, y = i / w; if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y; }
}
return m;
}
static void Body()
{
EditorSceneManager.OpenScene(SCENE, OpenSceneMode.Single);
GameObject root = null;
foreach (var go in UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects())
if (go.name.Contains("LH_M05")) { root = go; break; }
if (root == null) { L("!! root"); return; }
var smrs = root.GetComponentsInChildren<SkinnedMeshRenderer>(true).Where(r => r.enabled && r.gameObject.activeInHierarchy).ToArray();
// ── UV 점유 (렌더러별 · 자기 메시) ─────────────────────────────
L("== UV 점유 (렌더러별 · 텍스처 2048 기준) ==");
foreach (var r in smrs)
{
var mesh = r.sharedMesh; if (mesh == null) continue;
var uv = mesh.uv; var tri = mesh.triangles;
float u0 = 9, u1 = -9, v0 = 9, v1 = -9;
var cells = new HashSet<int>(); // 64텍셀 격자 점유 칸 수
foreach (var i in tri)
{
var q = uv[i];
if (q.x < u0) u0 = q.x; if (q.x > u1) u1 = q.x; if (q.y < v0) v0 = q.y; if (q.y > v1) v1 = q.y;
cells.Add(Mathf.Clamp((int)(q.x * 32), 0, 31) * 32 + Mathf.Clamp((int)(q.y * 32), 0, 31));
}
L(string.Format(" {0}: u[{1:F4}..{2:F4}] v[{3:F4}..{4:F4}] → {5:F0}×{6:F0} 텍셀 · 64격자 점유 {7}/1024칸 · verts {8} tri {9}",
r.name, u0, u1, v0, v1, (u1 - u0) * 2048, (v1 - v0) * 2048, cells.Count, mesh.vertexCount, tri.Length / 3));
}
Transform neck = root.GetComponentsInChildren<Transform>(true).FirstOrDefault(t => t.name == "Neck_M");
Camera scam = Camera.main ?? UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).FirstOrDefault(c => c.orthographic);
var all = root.GetComponentsInChildren<Transform>(true);
var orig = new Dictionary<Transform, int>();
foreach (var t in all) { orig[t] = t.gameObject.layer; t.gameObject.layer = LAYER; }
var faceR = smrs.Where(r => r.name == "Face").ToArray();
try
{
Do("A. 현재 (ortho 10 · 1080×1920)", scam, 10f, 1080, 1920, root, smrs, faceR, neck);
Do("B. 도트A (ortho 3.4 · 135×240)", scam, 3.4f, 135, 240, root, smrs, faceR, neck);
}
finally { foreach (var kv in orig) if (kv.Key != null) kv.Key.gameObject.layer = kv.Value; }
}
static void Do(string title, Camera scam, float ortho, int W, int H, GameObject root, SkinnedMeshRenderer[] smrs, SkinnedMeshRenderer[] faceR, Transform neck)
{
L(""); L("════════ " + title + " ════════");
var go = new GameObject("WL814u_Cam2");
var cam = go.AddComponent<Camera>();
cam.CopyFrom(scam); cam.orthographic = true; cam.orthographicSize = ortho;
cam.cullingMask = 1 << LAYER; cam.targetTexture = null;
cam.aspect = (float)W / H; // 🔴 해상도 고정 (1차 probe 의 머리 측정 버그 원인)
cam.transform.SetPositionAndRotation(scam.transform.position, scam.transform.rotation);
var ucd = cam.GetUniversalAdditionalCameraData(); if (ucd != null) { ucd.renderPostProcessing = false; ucd.SetRenderer(0); }
var b = new Bounds(root.transform.position, Vector3.zero); foreach (var r in smrs) b.Encapsulate(r.bounds);
cam.transform.position = b.center - cam.transform.forward * 50f;
int x0, y0, x1, y1, cnt; Shot col;
foreach (var r in smrs) r.enabled = true;
var mAll = Mask(cam, W, H, out x0, out y0, out x1, out y1, out cnt, out col);
L(string.Format(" 전체: 키 {0} px · 폭 {1} px · 면적 {2} px (y[{3}..{4}])", y1 - y0 + 1, x1 - x0 + 1, cnt, y0, y1));
// 머리 = Neck_M 위 (뷰포트 기준 · 해상도 무관)
var vp = cam.WorldToViewportPoint(neck.position);
int neckY = Mathf.RoundToInt(vp.y * H);
int hx0 = W, hx1 = -1, hcnt = 0;
for (int y = Mathf.Max(0, neckY); y <= y1; y++) for (int x = 0; x < W; x++) if (mAll[y * W + x]) { hcnt++; if (x < hx0) hx0 = x; if (x > hx1) hx1 = x; }
int headPx = y1 - neckY + 1;
L(string.Format(" 🔴 머리(Neck_M y={0} → 정수리 y={1}): 높이 {2} px · 폭 {3} px · 면적 {4} px · 머리:키 = 1:{5:F2}",
neckY, y1, headPx, hx1 - hx0 + 1, hcnt, (float)(y1 - y0 + 1) / Mathf.Max(1, headPx)));
// 얼굴 데칼
foreach (var r in smrs) r.enabled = faceR.Contains(r);
int fx0, fy0, fx1, fy1, fc; Shot fcol;
var mF = Mask(cam, W, H, out fx0, out fy0, out fx1, out fy1, out fc, out fcol);
if (fc > 0)
{
L(string.Format(" 🔴 얼굴 데칼(이목구비 전부): {0} × {1} px · 면적 {2} px", fx1 - fx0 + 1, fy1 - fy0 + 1, fc));
L(string.Format(" 🔴 눈~턱(얼굴 데칼 위 {0} → 턱=Neck_M {1}): {2} px", fy1, neckY, fy1 - neckY + 1));
float sx = (fx1 - fx0 + 1) / 889f, sy = (fy1 - fy0 + 1) / 617f;
L(string.Format(" 눈 1개 = {0:F2} × {1:F2} px · 눈썹 {2:F2} × {3:F2} px · 입 {4:F2} × {5:F2} px · 텍셀 1개 = {6:F4} px",
277 * sx, 183 * sy, 165 * sx, 107 * sy, 67 * sx, 14 * sy, sx));
}
else L(" 얼굴 데칼: 0 px");
foreach (var r in smrs) r.enabled = true;
// ── 픽셀 떨림 실험: 캐릭터를 0.5° 씩 돌리며 실루엣/색이 몇 % 바뀌나 ──
L(" ── 픽셀 떨림(pixel crawl) 실험 · 캐릭터 yaw 를 0.5° 씩 16회 ──");
var y0rot = root.transform.eulerAngles.y;
bool[] prevM = null; Shot prevC = null; var flips = new List<float>(); var colch = new List<float>();
for (int i = 0; i <= 16; i++)
{
root.transform.rotation = Quaternion.Euler(0, y0rot + i * 0.5f, 0);
int a0, a1, a2, a3, ac; Shot c;
var m = Mask(cam, W, H, out a0, out a2, out a1, out a3, out ac, out c);
if (prevM != null)
{
int flip = 0, chg = 0, both = 0;
for (int k = 0; k < m.Length; k++)
{
if (m[k] != prevM[k]) flip++;
if (m[k] && prevM[k]) { both++; if (Mathf.Abs(c.px[k].r - prevC.px[k].r) + Mathf.Abs(c.px[k].g - prevC.px[k].g) + Mathf.Abs(c.px[k].b - prevC.px[k].b) > 24) chg++; }
}
flips.Add(100f * flip / Mathf.Max(1, ac));
colch.Add(100f * chg / Mathf.Max(1, both));
}
prevM = m; prevC = c;
}
root.transform.rotation = Quaternion.Euler(0, y0rot, 0);
L(string.Format(" 0.5° 회전마다 실루엣 픽셀의 {0:F1} % 가 켜지거나 꺼진다 (최소 {1:F1} · 최대 {2:F1})", flips.Average(), flips.Min(), flips.Max()));
L(string.Format(" 0.5° 회전마다 남아 있는 픽셀의 {0:F1} % 가 색이 바뀐다(Δ>24) (최소 {1:F1} · 최대 {2:F1})", colch.Average(), colch.Min(), colch.Max()));
L(" → 1° 당 실루엣 " + (flips.Average() * 2).ToString("F1") + " % · 색 " + (colch.Average() * 2).ToString("F1") + " %");
UnityEngine.Object.DestroyImmediate(go);
}
}