Project_WL/AgentScripts/WL814v_Probe.cs

178 lines
11 KiB
C#
Raw Permalink 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.

// WL814v_Probe.cs — §1-1 부위별 「텍셀 → 화면 픽셀」 밀도 실측 (에디터 전용)
// 🔴 씬 저장 0 · 에셋 수정 0 · ProjectSettings 0. 카메라·포즈는 814u 와 동일(ortho 10 · 1080×1920 · 캐릭터를 카메라 정면으로).
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 WL814v_Probe
{
const string SCENE = "Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity";
const string OUT = "AgentScripts/WL814v_PROBE.txt";
const int LAYER = 31;
const int W = 1080, H = 1920;
static readonly StringBuilder sb = new StringBuilder();
static void L(string s) { sb.AppendLine(s); Console.WriteLine("[814vP] " + s); }
class Shot { public Color32[] px; public int w, h; }
static Shot Render(Camera cam, Color bg)
{
var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
rt.antiAliasing = 1; rt.filterMode = FilterMode.Point;
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 = null; 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;
}
// 검정/흰 배경 2장 차분 = 실루엣 마스크 (반투명·안티앨리어싱 무시)
static bool[] Mask(Camera cam, out int x0, out int y0, out int x1, out int y1, out int cnt)
{
var b = Render(cam, Color.black); var wt = Render(cam, Color.white);
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;
}
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)); }
}
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();
var scam = Camera.main ?? UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).First(c => c.orthographic);
float camYaw = scam.transform.eulerAngles.y;
root.transform.rotation = Quaternion.Euler(0, camYaw + 180f, 0); // 814u 캡처와 동일 포즈
L(string.Format("포즈 = 카메라 정면 yaw {0:F0}° · 카메라 ortho 10 · {1}×{2} (814u 동일)", camYaw + 180f, W, H));
var all = root.GetComponentsInChildren<Transform>(true);
var origLayer = new Dictionary<Transform, int>();
foreach (var t in all) { origLayer[t] = t.gameObject.layer; t.gameObject.layer = LAYER; }
var b = new Bounds(root.transform.position, Vector3.zero); foreach (var r in smrs) b.Encapsulate(r.bounds);
var go2 = new GameObject("WL814v_Cam");
var cam = go2.AddComponent<Camera>(); cam.CopyFrom(scam);
cam.orthographic = true; cam.orthographicSize = 10f; cam.aspect = (float)W / H; cam.targetTexture = null;
cam.cullingMask = 1 << LAYER;
cam.transform.SetPositionAndRotation(b.center - scam.transform.forward * 50f, scam.transform.rotation);
var ucd = cam.GetUniversalAdditionalCameraData(); if (ucd != null) { ucd.renderPostProcessing = false; ucd.SetRenderer(0); }
for (int i = 0; i < 3; i++) Render(cam, Color.black); // 배치 워밍업
try
{
int x0, y0, x1, y1, cntAll;
foreach (var r in smrs) r.enabled = true;
Mask(cam, out x0, out y0, out x1, out y1, out cntAll);
L(string.Format("전체 실루엣: 키 {0} px · 폭 {1} px · 면적 {2} px", y1 - y0 + 1, x1 - x0 + 1, cntAll));
L("");
L("== 부위별 「텍셀 → 화면 픽셀」 밀도 (앞면 삼각형만 · 화면면적 가중) ==");
L(" (UV 면적은 실효 텍스처 512² 기준 · 괄호는 원본 2048² 기준)");
foreach (var target in smrs)
{
// 이 렌더러만 켜서 실제로 그려지는 화면 픽셀 수(가림 반영)
foreach (var r in smrs) r.enabled = (r == target);
int a0, b0, a1, b1, cnt;
Mask(cam, out a0, out b0, out a1, out b1, out cnt);
var mesh = new Mesh();
target.BakeMesh(mesh, true); // useScale = true → 스케일 포함
var verts = mesh.vertices; var uvs = mesh.uv; var tris = mesh.triangles;
var mNoScale = Matrix4x4.TRS(target.transform.position, target.transform.rotation, Vector3.one);
var mFull = target.transform.localToWorldMatrix;
// 🔴 cam.WorldToScreenPoint 는 GameView 크기를 쓴다(배치 = 엉뚱한 값) → VP 행렬로 직접 1080×1920 으로 투영
var VP = cam.projectionMatrix * cam.worldToCameraMatrix;
System.Func<Vector3, Vector3> Proj = (wp) =>
{
var c = VP * new Vector4(wp.x, wp.y, wp.z, 1f);
if (Mathf.Abs(c.w) > 1e-9f) { c.x /= c.w; c.y /= c.w; c.z /= c.w; }
return new Vector3((c.x * 0.5f + 0.5f) * W, (c.y * 0.5f + 0.5f) * H, c.z);
};
double bestScore = double.MaxValue; string bestTag = "?"; double dens = 0, sArea = 0, uArea = 0; int nFront = 0;
foreach (var mode in new[] { 0, 1 })
{
var M = (mode == 0) ? mNoScale : mFull;
double sA = 0, uA = 0; int nf = 0, nb = 0; double sAb = 0, uAb = 0;
float px0 = 1e9f, py0 = 1e9f, px1 = -1e9f, py1 = -1e9f;
for (int i = 0; i < tris.Length; i += 3)
{
Vector3 p0 = Proj(M.MultiplyPoint3x4(verts[tris[i]]));
Vector3 p1 = Proj(M.MultiplyPoint3x4(verts[tris[i + 1]]));
Vector3 p2 = Proj(M.MultiplyPoint3x4(verts[tris[i + 2]]));
float cross = (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
px0 = Mathf.Min(px0, Mathf.Min(p0.x, Mathf.Min(p1.x, p2.x))); px1 = Mathf.Max(px1, Mathf.Max(p0.x, Mathf.Max(p1.x, p2.x)));
py0 = Mathf.Min(py0, Mathf.Min(p0.y, Mathf.Min(p1.y, p2.y))); py1 = Mathf.Max(py1, Mathf.Max(p0.y, Mathf.Max(p1.y, p2.y)));
Vector2 q0 = uvs[tris[i]], q1 = uvs[tris[i + 1]], q2 = uvs[tris[i + 2]];
float ucross = Mathf.Abs((q1.x - q0.x) * (q2.y - q0.y) - (q2.x - q0.x) * (q1.y - q0.y));
if (cross > 0f) { sA += cross * 0.5; uA += ucross * 0.5 * 512.0 * 512.0; nf++; }
else { sAb += -cross * 0.5; uAb += ucross * 0.5 * 512.0 * 512.0; nb++; }
}
// 앞면 winding 자동 판별: 그려진 화면 px 수에 더 가까운 쪽
if (Math.Abs(sAb - cnt) < Math.Abs(sA - cnt)) { sA = sAb; uA = uAb; nf = nb; }
// 검증: 투영 bbox 가 실제 렌더 bbox 와 얼마나 맞나
double score = Math.Abs(px0 - a0) + Math.Abs(px1 - a1) + Math.Abs(py0 - b0) + Math.Abs(py1 - b1);
if (score < bestScore) { bestScore = score; bestTag = (mode == 0 ? "TRS(무스케일)" : "localToWorld"); sArea = sA; uArea = uA; nFront = nf; dens = (sA > 0 ? uA / sA : 0); }
}
UnityEngine.Object.DestroyImmediate(mesh);
L(string.Format(" {0,-9}: 그려진 화면 {1,5} px · 앞면삼각 {2,4}개 · 앞면 화면면적 {3,7:F0} px² · UV면적 {4,9:F0} 텍셀²",
target.name, cnt, nFront, sArea, uArea));
L(string.Format(" → 화면 1 px = {0:F0} 텍셀(512²) = {1:F0} 텍셀(2048²) · 🔴 격자 1변 = {2:F1} 텍셀(512²) = {3:F1} 텍셀(2048²) · 투영검증 오차 {4:F0} px [{5}]",
dens, dens * 16.0, Math.Sqrt(dens), Math.Sqrt(dens) * 4.0, bestScore, bestTag));
// ── U/V 축별 이방성 (면적 가중) ──
{
var VP2 = cam.projectionMatrix * cam.worldToCameraMatrix;
var M2 = (bestTag == "TRS(무스케일)") ? mNoScale : mFull;
System.Func<Vector3, Vector2> P2 = (wp) => { var c = VP2 * new Vector4(wp.x, wp.y, wp.z, 1f); if (Mathf.Abs(c.w) > 1e-9f) { c.x /= c.w; c.y /= c.w; } return new Vector2((c.x * 0.5f + 0.5f) * W, (c.y * 0.5f + 0.5f) * H); };
double wu = 0, wv = 0, wsum = 0;
for (int i = 0; i < tris.Length; i += 3)
{
Vector2 p0 = P2(M2.MultiplyPoint3x4(verts[tris[i]])), p1 = P2(M2.MultiplyPoint3x4(verts[tris[i + 1]])), p2 = P2(M2.MultiplyPoint3x4(verts[tris[i + 2]]));
Vector2 q0 = uvs[tris[i]], q1 = uvs[tris[i + 1]], q2 = uvs[tris[i + 2]];
Vector2 e1 = q1 - q0, e2 = q2 - q0; Vector2 f1 = p1 - p0, f2 = p2 - p0;
float det = e1.x * e2.y - e2.x * e1.y; if (Mathf.Abs(det) < 1e-12f) continue;
Vector2 dPdu = (f1 * e2.y - f2 * e1.y) / det, dPdv = (f2 * e1.x - f1 * e2.x) / det;
double ar = Math.Abs(f1.x * f2.y - f2.x * f1.y) * 0.5;
wu += ar * dPdu.magnitude / 512.0; wv += ar * dPdv.magnitude / 512.0; wsum += ar;
}
if (wsum > 0)
L(string.Format(" ↳ 이방성: u 1텍셀(512²) = {0:F3} 화면 px · v 1텍셀 = {1:F3} px → 화면 1 px = u {2:F1} × v {3:F1} 텍셀(512²) = u {4:F0} × v {5:F0} 텍셀(2048²)",
wu / wsum, wv / wsum, wsum / wu, wsum / wv, 4.0 * wsum / wu, 4.0 * wsum / wv));
}
}
foreach (var r in smrs) r.enabled = true;
}
finally
{
foreach (var kv in origLayer) if (kv.Key != null) kv.Key.gameObject.layer = kv.Value;
UnityEngine.Object.DestroyImmediate(go2);
}
}
}