Project_WL/AgentScripts/WL814s_Capture.cs

257 lines
12 KiB
C#

// ─────────────────────────────────────────────────────────────────────────────
// WL814s_Capture.cs — WL_ArenaProto 씬에서 같은 카메라·같은 포즈로 단계별 캡처 (발주 WL-814s §4)
//
// Play 중에 호출한다. 4개 상태(원본 · ①셰이더 · ①+③축소 · ①+③+②평탄화)를
// **같은 프레임·같은 포즈**에서 연속으로 렌더한다 → 비교가 공정하다.
// 🔴 씬·프리팹·머티리얼 파일에 아무 것도 쓰지 않는다(런타임 sharedMaterials 참조만 바꾼다).
// ─────────────────────────────────────────────────────────────────────────────
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
using WL.Look.Character;
public static class WL814s_Capture
{
public const int W = 1080, H = 1920;
static string OutDir()
{
string d = Path.GetFullPath(Path.Combine(Application.dataPath, "../Screenshots_WL/WL814s"));
Directory.CreateDirectory(d);
return d;
}
static GameObject FindPc()
{
var all = UnityEngine.Object.FindObjectsByType<Animator>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
foreach (var a in all)
{
if (a.GetComponentInChildren<SkinnedMeshRenderer>(true) != null) return a.gameObject;
}
return null;
}
static void FreezePose(GameObject pc)
{
var an = pc != null ? pc.GetComponent<Animator>() : null;
if (an == null) return;
an.fireEvents = false;
an.speed = 0f;
an.Play("idle", 0, 0.25f);
an.Update(0f);
}
static void Shot(Camera cam, string file)
{
var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
rt.antiAliasing = 1;
var prevT = cam.targetTexture; var prevActive = RenderTexture.active;
cam.targetTexture = rt;
cam.Render();
RenderTexture.active = rt;
var tex = new Texture2D(W, H, TextureFormat.RGB24, false);
tex.ReadPixels(new Rect(0, 0, W, H), 0, 0);
tex.Apply(false);
File.WriteAllBytes(file, tex.EncodeToPNG());
cam.targetTexture = prevT; RenderTexture.active = prevActive;
UnityEngine.Object.DestroyImmediate(tex);
rt.Release(); UnityEngine.Object.DestroyImmediate(rt);
}
/// <summary>args = ["set", "<prefix>", "<mode0>", "<mode1>", …] · 각 모드를 같은 포즈로 찍는다.</summary>
public static string Cmd(string[] args)
{
try
{
var sb = new StringBuilder();
var cam = Camera.main;
if (cam == null)
{
var cams = UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
if (cams.Length > 0) cam = cams[0];
}
if (cam == null) return "NO CAMERA";
var pc = FindPc();
if (pc == null) return "NO PC";
FreezePose(pc);
string dir = OutDir();
sb.AppendLine("cam=" + cam.name + " ortho=" + cam.orthographic + " size=" + cam.orthographicSize +
" pos=" + cam.transform.position.ToString("F4") + " euler=" + cam.transform.eulerAngles.ToString("F3"));
sb.AppendLine("pc=" + pc.name + " pos=" + pc.transform.position.ToString("F4"));
string cmd = args.Length > 0 ? args[0] : "set";
if (cmd == "set")
{
string prefix = args[1];
for (int i = 2; i < args.Length; i++)
{
int mode = int.Parse(args[i]);
WLCharacterLook.ApplyMode(pc, mode, null);
FreezePose(pc);
string f = Path.Combine(dir, prefix + "_m" + mode + ".png");
Shot(cam, f);
sb.AppendLine("SHOT mode=" + mode + " -> " + f + " · " + WLCharacterLook.LastLog + " · " + DumpMats(pc));
}
}
else if (cmd == "face")
{
// 얼굴 확대 — 같은 각도, 머리 중심, 작은 ortho size
string prefix = args[1];
// 얼굴이 카메라를 보게 돌린다(이 샷 전용 · Play 중이라 파일에 남지 않는다)
var fwd = -cam.transform.forward; fwd.y = 0f;
if (fwd.sqrMagnitude > 0.0001f) pc.transform.rotation = Quaternion.LookRotation(fwd.normalized, Vector3.up);
FreezePose(pc);
var head = FindHead(pc);
var go = new GameObject("WL814s_FaceCam");
var fc = go.AddComponent<Camera>();
fc.CopyFrom(cam);
fc.orthographic = true;
fc.orthographicSize = float.Parse(args[2]);
go.transform.rotation = cam.transform.rotation;
go.transform.position = head - cam.transform.forward * 20f;
fc.targetTexture = null;
for (int i = 3; i < args.Length; i++)
{
int mode = int.Parse(args[i]);
WLCharacterLook.ApplyMode(pc, mode, null);
FreezePose(pc);
string f = Path.Combine(dir, prefix + "_m" + mode + ".png");
Shot(fc, f);
sb.AppendLine("FACE mode=" + mode + " -> " + f);
}
sb.AppendLine("head=" + head.ToString("F4") + " size=" + fc.orthographicSize);
UnityEngine.Object.DestroyImmediate(go);
}
else if (cmd == "suffix")
{
// suffix <prefix> <matSuffix1> <matSuffix2> … · Materials/<KEY><suffix>.mat 을 직접 물린다
// (같은 Play 세션 안에서 찍으므로 구름 그림자·잔디까지 같은 조건 = 공정한 비교)
string prefix = args[1];
for (int i = 2; i < args.Length; i++)
{
ApplySuffix(pc, args[i]);
FreezePose(pc);
string f = Path.Combine(dir, prefix + "_" + args[i].Replace("_", "") + ".png");
Shot(cam, f);
sb.AppendLine("SHOT " + args[i] + " -> " + f + " · " + DumpMats(pc));
}
}
else if (cmd == "suffixface")
{
string prefix = args[1];
var fwd2 = -cam.transform.forward; fwd2.y = 0f;
if (fwd2.sqrMagnitude > 0.0001f) pc.transform.rotation = Quaternion.LookRotation(fwd2.normalized, Vector3.up);
FreezePose(pc);
var head2 = FindHead(pc);
var go2 = new GameObject("WL814s_FaceCam2");
var fc2 = go2.AddComponent<Camera>();
fc2.CopyFrom(cam); fc2.orthographic = true; fc2.orthographicSize = float.Parse(args[2]);
go2.transform.rotation = cam.transform.rotation;
go2.transform.position = head2 - cam.transform.forward * 20f;
for (int i = 3; i < args.Length; i++)
{
ApplySuffix(pc, args[i]);
FreezePose(pc);
Shot(fc2, Path.Combine(dir, prefix + "_" + args[i].Replace("_", "") + ".png"));
sb.AppendLine("FACE " + args[i]);
}
UnityEngine.Object.DestroyImmediate(go2);
}
else if (cmd == "dumponly")
{
// 아무 것도 적용하지 않고 지금 상태만 읽는다 (Awake 가 한 일을 그대로 본다)
var c0 = WLCharacterLookSettings.Instance;
sb.AppendLine("SO enabled_=" + (c0 != null ? c0.enabled_ : -1) + " mode=" + (c0 != null ? c0.mode : -1) +
" ActiveMode=" + WLCharacterLookSettings.ActiveMode +
" · Awake 적용내역=" + WLCharacterLook.LastLog);
sb.AppendLine("RESULT " + DumpMats(pc));
}
else if (cmd == "probe")
{
for (int i = 1; i < args.Length; i++)
{
int mode = int.Parse(args[i]);
WLCharacterLook.ApplyMode(pc, mode, null);
sb.AppendLine("MODE " + mode + " · " + DumpMats(pc));
}
}
else if (cmd == "switch")
{
// SO 가 지시하는 대로 (enabled_ 실측용)
WLCharacterLookSettings.Invalidate();
WLCharacterLook.Apply(pc);
sb.AppendLine("SO enabled_=" + (WLCharacterLookSettings.Instance != null ? WLCharacterLookSettings.Instance.enabled_ : -1) +
" mode=" + (WLCharacterLookSettings.Instance != null ? WLCharacterLookSettings.Instance.mode : -1) +
" ActiveMode=" + WLCharacterLookSettings.ActiveMode);
sb.AppendLine("RESULT " + DumpMats(pc));
}
return sb.ToString();
}
catch (Exception e) { return "EXCEPTION " + e.GetType().Name + " " + e.Message + "\n" + e.StackTrace; }
}
// 비교 전용 — Materials/<KEY><suffix>.mat 을 렌더러에 직접 물린다("_orig" 면 원본으로)
static readonly string[] KEYS = { "M05", "Skin", "Hair05", "face02" };
static void ApplySuffix(GameObject pc, string suffix)
{
var cfg = WLCharacterLookSettings.Instance;
if (cfg == null) return;
if (suffix == "_orig") { WLCharacterLook.ApplyMode(pc, 0, cfg); return; }
var want = new Material[4];
for (int i = 0; i < 4; i++)
{
var m = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(
"Assets/WL/Look/Character/Materials/" + KEYS[i] + suffix + ".mat");
want[i] = m != null ? m : cfg.originals[i];
}
foreach (var r in pc.GetComponentsInChildren<Renderer>(true))
{
var ms = r.sharedMaterials; bool ch = false;
for (int s = 0; s < ms.Length; s++)
{
int idx = -1;
for (int i = 0; i < 4 && idx < 0; i++)
{
if (cfg.originals[i] == ms[s] || cfg.mode1[i] == ms[s] || cfg.mode2[i] == ms[s] || cfg.mode3[i] == ms[s]) idx = i;
else if (ms[s] != null && ms[s].name.StartsWith(KEYS[i])) idx = i;
}
if (idx < 0 || want[idx] == null || ms[s] == want[idx]) continue;
ms[s] = want[idx]; ch = true;
}
if (ch) r.sharedMaterials = ms;
}
}
static Vector3 FindHead(GameObject pc)
{
// 얼굴 머티리얼을 쓰는 렌더러의 바운즈 중심
var rs = pc.GetComponentsInChildren<Renderer>(true);
foreach (var r in rs)
{
var ms = r.sharedMaterials;
foreach (var m in ms)
if (m != null && m.name.StartsWith("face")) return r.bounds.center;
}
var b = new Bounds(pc.transform.position, Vector3.zero);
foreach (var r in rs) if (r.enabled) b.Encapsulate(r.bounds);
return new Vector3(b.center.x, b.max.y - b.size.y * 0.12f, b.center.z);
}
static string DumpMats(GameObject pc)
{
var sb = new StringBuilder();
var rs = pc.GetComponentsInChildren<Renderer>(true);
foreach (var r in rs)
{
var ms = r.sharedMaterials;
for (int i = 0; i < ms.Length; i++)
sb.Append(r.name + "[" + i + "]=" + (ms[i] == null ? "null" : ms[i].name + "/" + ms[i].shader.name) + "; ");
}
return sb.ToString();
}
}