Project_WL/AgentScripts/WL814x2_Play.cs

326 lines
16 KiB
C#
Raw Permalink Normal View History

// 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();
}
}