Project_WL/AgentScripts/WL814x2_Play.cs

326 lines
16 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.

// WL-814x2 — 🔴 **Play 모드에서만** 재는 색 측정/촬영 하니스
// 편집 모드 렌더는 스킨 메시가 갱신되지 않아 무의미하다(814x 에서 실측).
// run_script --file AgentScripts/WL814x2_Play.cs --entry WL814x2.Run (Play 중에 부른다)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
using WL.Look.Arena;
using WL.Look.Character;
public static class WL814x2
{
public const string RAW = "Screenshots_WL/WL814x/2nd/raw/";
const int W = 1080, H = 1920;
static readonly StringBuilder sb = new StringBuilder();
static void L(string s) { sb.AppendLine(s); }
static GameObject s_root;
static Renderer[] s_rends;
static Material[][] s_orig;
static Camera s_cam;
static Vector3 s_camPos, s_center, s_pos;
static Quaternion s_rot, s_camRot;
static Animator s_an;
static float s_camSize, s_ts;
static void Setup()
{
s_root = GameObject.Find("PC_LH_M05");
if (s_root == null)
foreach (var g in UnityEngine.Object.FindObjectsByType<Animator>(FindObjectsSortMode.None))
if (g.name.Contains("LH_M05")) { s_root = g.gameObject; break; }
// 🔴 아레나에는 캐릭터를 걷게 하는 리그가 있다 — 샷마다 위치/포즈가 달라지면 색 비교가 무의미하다.
s_ts = Time.timeScale; Time.timeScale = 0f;
s_an = s_root.GetComponentInChildren<Animator>();
if (s_an != null) { s_an.speed = 0f; try { s_an.Play("idle", 0, 0.25f); s_an.Update(0f); } catch { } }
s_cam = Camera.main ?? UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsSortMode.None).First(c => c.orthographic);
s_camPos = s_cam.transform.position; s_camSize = s_cam.orthographicSize; s_camRot = s_cam.transform.rotation;
s_root.transform.rotation = Quaternion.Euler(0, s_cam.transform.eulerAngles.y + 180f, 0);
s_pos = s_root.transform.position; s_rot = s_root.transform.rotation;
s_rends = s_root.GetComponentsInChildren<Renderer>(true).Where(r => r.enabled && r.gameObject.activeInHierarchy).ToArray();
s_orig = s_rends.Select(r => r.sharedMaterials).ToArray();
var b = new Bounds(s_root.transform.position, Vector3.zero);
foreach (var r in s_rends) b.Encapsulate(r.bounds);
s_center = b.center;
L("renderers " + string.Join(",", s_rends.Select(r => r.name)) + " · center " + s_center.ToString("F2") + " · h " + b.size.y.ToString("F3"));
foreach (var r in s_rends)
{
var m = r.sharedMaterial;
L(" " + r.name + " mat=" + m.name + " shader=" + m.shader.name
+ " base=" + (m.HasProperty("_BaseMap") && m.GetTexture("_BaseMap") ? m.GetTexture("_BaseMap").name : "-")
+ " diff=" + (m.HasProperty("_DiffuseColor") ? m.GetColor("_DiffuseColor").ToString("F3") : "-")
+ " shadow=" + (m.HasProperty("_ShadowDiffuseColor") ? m.GetColor("_ShadowDiffuseColor").ToString("F4") : "-")
+ " shades=" + (m.HasProperty("_Shades") ? m.GetFloat("_Shades").ToString("F0") : "-")
+ " minDark=" + (m.HasProperty("_MinimumDarkness") ? m.GetFloat("_MinimumDarkness").ToString("F2") : "-"));
}
var rc = WLReferenceLookSettings.Instance;
L("ReferenceLook: rimOn=" + rc.rimOn + " moodOn=" + rc.moodOn + " mood=" + rc.moodStrength.ToString("F2")
+ " contrast=" + rc.contrastOn + " minDark=" + rc.minimumDarkness.ToString("F2")
+ " shadowScale=" + rc.shadowColorScale.ToString("F2") + " shadowTintAmt=" + rc.shadowTintAmount.ToString("F2")
+ " shadowTint=" + rc.shadowTint.ToString("F3") + " charShades=" + rc.charShades
+ " key=" + rc.keyLightColor.ToString("F2") + "×" + rc.keyLightIntensity.ToString("F2") + " amb=" + rc.ambientColor.ToString("F2"));
}
static void Teardown()
{
for (int i = 0; i < s_rends.Length; i++) { s_rends[i].sharedMaterials = s_orig[i]; s_rends[i].enabled = true; }
s_cam.transform.SetPositionAndRotation(s_camPos, s_camRot); s_cam.orthographicSize = s_camSize;
Time.timeScale = s_ts;
}
static int[] s_layers; static int s_cull; static CameraClearFlags s_clear; static Color s_bg; static bool s_post, s_postSaved;
/// <summary>캐릭터만 남기고 배경·그림자·포스트를 모두 끈 렌더 상태(마스크·덩어리지도 전용).</summary>
static void Isolate(bool on)
{
var ts = s_root.GetComponentsInChildren<Transform>(true);
var ucd = s_cam.GetComponent<UnityEngine.Rendering.Universal.UniversalAdditionalCameraData>();
if (on)
{
s_layers = ts.Select(t => t.gameObject.layer).ToArray();
foreach (var t in ts) t.gameObject.layer = 31;
s_cull = s_cam.cullingMask; s_clear = s_cam.clearFlags; s_bg = s_cam.backgroundColor;
s_cam.cullingMask = 1 << 31; s_cam.clearFlags = CameraClearFlags.SolidColor; s_cam.backgroundColor = Color.black;
if (ucd != null) { s_post = ucd.renderPostProcessing; s_postSaved = true; ucd.renderPostProcessing = false; }
}
else
{
for (int i = 0; i < ts.Length && i < s_layers.Length; i++) ts[i].gameObject.layer = s_layers[i];
s_cam.cullingMask = s_cull; s_cam.clearFlags = s_clear; s_cam.backgroundColor = s_bg;
if (ucd != null && s_postSaved) { ucd.renderPostProcessing = s_post; s_postSaved = false; }
}
}
class Shot { public Color32[] px; }
static Shot Render(float ortho)
{
// 매 렌더마다 같은 위치·같은 포즈로 고정한다
s_root.transform.SetPositionAndRotation(s_pos, s_rot);
if (s_an != null) { s_an.speed = 0f; try { s_an.Play("idle", 0, 0.25f); s_an.Update(0f); } catch { } }
// 🔴 카메라도 매 프레임 따라다니는 스크립트가 있다 — 회전까지 고정해야 프레이밍이 같다.
s_cam.orthographicSize = ortho;
s_cam.transform.SetPositionAndRotation(s_center - (s_camRot * Vector3.forward) * 30f, s_camRot);
var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
rt.antiAliasing = 1; rt.filterMode = FilterMode.Point; rt.Create();
var prev = s_cam.targetTexture; s_cam.targetTexture = rt; s_cam.Render(); s_cam.targetTexture = prev;
var pa = RenderTexture.active; RenderTexture.active = rt;
var t = new Texture2D(W, H, TextureFormat.RGBA32, false); t.ReadPixels(new Rect(0, 0, W, H), 0, 0); t.Apply();
RenderTexture.active = pa;
var s = new Shot { px = t.GetPixels32() };
UnityEngine.Object.Destroy(t); rt.Release(); UnityEngine.Object.Destroy(rt);
return s;
}
static void Save(Shot s, string name)
{
var t = new Texture2D(W, H, TextureFormat.RGBA32, false); t.SetPixels32(s.px); t.Apply();
Directory.CreateDirectory(RAW); File.WriteAllBytes(RAW + name + ".png", t.EncodeToPNG());
UnityEngine.Object.Destroy(t);
}
static void Shoot(string name, float ortho) { Render(ortho); Save(Render(ortho), name); }
static void SaveMask(string name, float ortho, params Renderer[] on)
{
Isolate(true);
foreach (var r in s_rends) r.enabled = on.Contains(r);
var a = Render(ortho);
foreach (var r in s_rends) r.enabled = true;
Isolate(false);
var t = new Texture2D(W, H, TextureFormat.RGBA32, false);
var px = new Color32[W * H]; int n = 0;
for (int i = 0; i < px.Length; i++)
{
int d = a.px[i].r + a.px[i].g + a.px[i].b;
bool hit = d > 12; if (hit) n++;
px[i] = hit ? new Color32(255, 255, 255, 255) : new Color32(0, 0, 0, 255);
}
t.SetPixels32(px); t.Apply();
Directory.CreateDirectory(RAW); File.WriteAllBytes(RAW + "mask_" + name + ".png", t.EncodeToPNG());
UnityEngine.Object.Destroy(t);
L("mask " + name + " @" + ortho.ToString("F2") + " = " + n + " px");
}
/// <summary>덩어리(팔레트 칸) 지도 — 빛 0. 부위별 색을 재기 위한 마스크.</summary>
static void ShootIdMap(string name, float ortho)
{
var pal = UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/WL/Look/Character/Textures/WLPalette_FINAL.png");
int S = pal.width, G = S / 8;
var t = new Texture2D(S, S, TextureFormat.RGBA32, false) { filterMode = FilterMode.Point, wrapMode = TextureWrapMode.Clamp };
var px = new Color32[S * S];
for (int i = 0; i < px.Length; i++) px[i] = new Color32(0, 0, 0, 255);
for (int j = 0; j < G * G; j++)
{
int gx = j % G, gy = j / G;
var c = new Color32((byte)(9 + j * 15), (byte)(240 - j * 11), (byte)(40 + j * 7), 255);
for (int y = 0; y < 8; y++) for (int x = 0; x < 8; x++) px[(gy * 8 + y) * S + gx * 8 + x] = c;
}
t.SetPixels32(px); t.Apply();
Isolate(true);
var sh = Shader.Find("Unlit/Texture");
var keep = new List<Renderer>();
for (int i = 0; i < s_rends.Length; i++)
{
if (s_rends[i].name == "Face") { s_rends[i].enabled = false; keep.Add(s_rends[i]); continue; }
var m = new Material(sh); m.SetTexture("_MainTex", t);
s_rends[i].sharedMaterials = Enumerable.Repeat(m, s_orig[i].Length).ToArray();
}
Shoot(name, ortho);
foreach (var r in keep) r.enabled = true;
for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i];
Isolate(false);
UnityEngine.Object.Destroy(t);
// 팔레트 칸 색도 적어 둔다(인덱스 → 팔레트 RGB)
var cols = pal.GetPixels32();
for (int j = 0; j < G * G; j++)
{
int gx = j % G, gy = j / G;
var c = cols[(gy * 8 + 4) * S + gx * 8 + 4];
L(string.Format("PAL {0,2} id={1},{2},{3} rgb={4},{5},{6}", j, 9 + j * 15, 240 - j * 11, 40 + j * 7, c.r, c.g, c.b));
}
}
/// <summary>ToonP 머티리얼의 그림자색만 바꾼 임시 복제본으로 교체.</summary>
static void SetShadow(Color shadow, float shades)
{
for (int i = 0; i < s_rends.Length; i++)
{
var mats = (Material[])s_orig[i].Clone();
for (int m = 0; m < mats.Length; m++)
{
if (mats[m] == null || mats[m].shader == null || mats[m].shader.name != "Shader Graphs/Toon") continue;
var c = new Material(mats[m]) { name = "TMP_" + mats[m].name };
c.SetColor("_ShadowDiffuseColor", shadow);
if (shades > 0) c.SetFloat("_Shades", shades);
mats[m] = c;
}
s_rends[i].sharedMaterials = mats;
}
}
static void ResetMats() { for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i]; }
static void Look(float mood, float shadowTintAmount, int rim)
{
var cfg = WLReferenceLookSettings.Instance;
var o = WLReferenceLook.Opt.FromSettings(cfg);
o.mood = mood > 0f; o.moodStrength = mood;
o.shadowTintAmount = shadowTintAmount;
o.rim = rim != 0;
WLReferenceLook.Apply(o, cfg);
}
public static string Run()
{
sb.Length = 0;
try
{
Setup();
SaveMask("all", 0.95f, s_rends);
foreach (var r in s_rends) SaveMask(r.name, 0.95f, r);
SaveMask("all10", 10f, s_rends);
foreach (var r in s_rends) SaveMask(r.name + "10", 10f, r);
ShootIdMap("idmap", 0.95f);
ShootIdMap("idmap10", 10f);
var cfg = WLReferenceLookSettings.Instance;
float mood0 = cfg.moodStrength, tint0 = cfg.shadowTintAmount;
Action<string, Color, float, float, float, int> V = (name, shadow, shades, mood, tintAmt, rim) =>
{
ResetMats();
WLReferenceLook.Restore();
if (shadow.a >= 0f && shadow != Color.clear) SetShadow(shadow, shades);
Look(mood, tintAmt, rim);
Shoot(name, 0.95f);
Shoot(name + "_g", 10f);
L("shot " + name + " shadow=" + shadow.ToString("F3") + " shades=" + shades + " mood=" + mood.ToString("F2") + " tintAmt=" + tintAmt.ToString("F2") + " · " + WLReferenceLook.LastLog);
};
var purple = new Color(0.2164f, 0.2092f, 0.3713f, 0f);
V("a_now", purple, 3f, mood0, tint0, 0);
V("b_neutral", new Color(0.45f, 0.45f, 0.45f, 0f), 3f, mood0, tint0, 0);
V("c_neutral_notint", new Color(0.45f, 0.45f, 0.45f, 0f), 3f, mood0, 0f, 0);
V("d_neutral_notint_mood0", new Color(0.45f, 0.45f, 0.45f, 0f), 3f, 0f, 0f, 0);
V("e_warm_notint", new Color(0.52f, 0.45f, 0.40f, 0f), 3f, mood0, 0f, 0);
V("f_neutral_notint_s7", new Color(0.45f, 0.45f, 0.45f, 0f), 7f, mood0, 0f, 0);
V("g_dark_notint", new Color(0.32f, 0.32f, 0.34f, 0f), 3f, mood0, 0f, 0);
ResetMats(); WLReferenceLook.Restore(); Look(mood0, tint0, 0);
L("DONE");
}
catch (Exception e) { L("EXCEPTION " + e); }
finally { try { Teardown(); } catch { } Directory.CreateDirectory("AgentScripts"); File.WriteAllText("AgentScripts/WL814x2_PLAY.txt", sb.ToString(), new UTF8Encoding(true)); }
return sb.ToString();
}
/// <summary>아레나 씬을 연다(Play 진입 전 · 편집 모드에서 1회).</summary>
public static string Open()
{
UnityEditor.SceneManagement.EditorSceneManager.OpenScene("Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity", UnityEditor.SceneManagement.OpenSceneMode.Single);
return "opened " + UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
}
/// <summary>무드(포스트 그레이딩·앰비언트·키라이트)가 화면 색을 얼마나 틀어놓는지 — 에셋 수정 0(런타임 프로파일 인스턴스).</summary>
public static string Mood()
{
sb.Length = 0;
try
{
Setup();
SaveMask("all", 0.95f, s_rends);
foreach (var r in s_rends) SaveMask(r.name, 0.95f, r);
SaveMask("all10", 10f, s_rends);
ShootIdMap("idmap", 0.95f);
var cfg = WLReferenceLookSettings.Instance;
Action<string, float, bool, bool> V = (name, mood, killWB, killSMH) =>
{
WLReferenceLook.Restore();
var o = WLReferenceLook.Opt.FromSettings(cfg);
o.rim = false; o.mood = mood > 0f; o.moodStrength = mood;
WLReferenceLook.Apply(o, cfg);
var vgo = GameObject.Find("WL814t_ReferenceLook_Volume");
// killWB = 포스트 그레이딩 Volume 의 weight 만 0 으로 (조명 무드는 그대로 둔다)
if (vgo != null && killWB) vgo.GetComponent<UnityEngine.Rendering.Volume>().weight = 0f;
Shoot(name, 0.95f); Shoot(name + "_g", 10f);
L("mood shot " + name + " mood=" + mood.ToString("F2") + " killWB=" + killWB + " killSMH=" + killSMH);
};
V("m30", 0.30f, false, false);
V("m15", 0.15f, false, false);
V("m00", 0.00f, false, false);
V("n_novol30", 0.30f, true, false);
V("n_novol15", 0.15f, true, false);
WLReferenceLook.Restore();
var o2 = WLReferenceLook.Opt.FromSettings(cfg); o2.rim = false;
WLReferenceLook.Apply(o2, cfg);
L("DONE");
}
catch (Exception e) { L("EXCEPTION " + e); }
finally { try { Teardown(); } catch { } File.WriteAllText("AgentScripts/WL814x2_MOOD.txt", sb.ToString(), new UTF8Encoding(true)); }
return sb.ToString();
}
/// <summary>🔴 캐릭터 전용 광원 색조 상쇄 — _DiffuseColor 에 휘도보존 역이득을 넣는다.
/// 씬·조명·814t 설정·ProjectSettings 는 하나도 안 건드린다.</summary>
public static string SetTint()
{
var g = new Color(0.994f, 1.024f, 0.894f, 1f);
var sb2 = new StringBuilder();
foreach (var n in new[] { "M05_ToonP", "Skin_ToonP", "Hair05_ToonP" })
{
var p = "Assets/WL/Look/Character/Materials/" + n + ".mat";
var m = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(p);
if (m == null) { sb2.AppendLine("!! " + p); continue; }
m.SetColor("_DiffuseColor", g);
UnityEditor.EditorUtility.SetDirty(m);
sb2.AppendLine(n + " _DiffuseColor = " + g.ToString("F3"));
}
UnityEditor.AssetDatabase.SaveAssets();
return sb2.ToString();
}
}