257 lines
12 KiB
C#
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();
|
|
}
|
|
}
|