[WL-814x3] 앰비언트 중성화 + 캐릭터 색조 재보정 — 화면 dHue 24.7° -> 14.5° (#814)
전부 인게임 Play(포스트 포함·게임 크기) 기준 실측.
바꾼 것 (2개 값)
· WLReferenceLookSettings.ambientColor 보라 (0.28,0.17,0.40) -> 중성 (0.229,0.229,0.229)
밝기(휘도 0.229)와 Flat ambient(평탄함의 원천)는 그대로 유지
· 캐릭터 팔레트 머티리얼 3종 _DiffuseColor = (0.993, 1.028, 0.874) (휘도보존 역이득)
결과 (유채색 칸 면적가중)
화면 dHue 24.7° -> 14.5° (최대 60.6 -> 36.9)
흰 튜닉 -74.2° -> +10.5° 머리칼 -16.3° -> -4.6° 붉은 바지 -7.9° -> +4.3°
벨트 +38.0° -> -3.7° 가죽 -31.1° -> -16.0°
게임 크기 고유색 159 -> 129 (4.4 %) · 서로 다른 색 4.4 %
남는 것 = 피부 -36.9° / -30.9° (어두운 면)
원인 규명
· 그림자 알베도(_ShadowDiffuseColor) = 영향 0.1° (5종 비교) — 레버가 아니다
· 남은 피부 오차는 WL_ArenaMood 의 ShadowsMidtonesHighlights shadows (0.78,0.58,1.25) 가
어두운 면의 초록을 42 % 깎고 파랑을 25 % 올리기 때문 — 814t 프로파일(PD 승인 룩) 소관
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
4863a6bd79
commit
6196706528
|
|
@ -0,0 +1,43 @@
|
|||
renderers Arm_m05,Body_m05,Face,Hair05,Head,Leg_M05 · center (-0.01, 0.57, 0.00) · h 1.220
|
||||
Arm_m05 mat=M05_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
Body_m05 mat=M05_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
Face mat=face02_ToonP shader=Unlit/Transparent base=- diff=- shadow=- shades=- minDark=-
|
||||
Hair05 mat=Hair05_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
Head mat=Skin_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
Leg_M05 mat=M05_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
ReferenceLook: rimOn=False moodOn=True mood=0.30 contrast=True minDark=0.12 shadowScale=0.85 shadowTintAmt=0.30 shadowTint=RGBA(0.243, 0.106, 0.353, 1.000) charShades=0 key=RGBA(1.00, 0.78, 0.46, 1.00)×1.45 amb=RGBA(0.23, 0.23, 0.23, 1.00)
|
||||
mask all @0.95 = 330746 px
|
||||
mask Arm_m05 @0.95 = 36600 px
|
||||
mask Body_m05 @0.95 = 107658 px
|
||||
mask Face @0.95 = 7136 px
|
||||
mask Hair05 @0.95 = 127668 px
|
||||
mask Head @0.95 = 116073 px
|
||||
mask Leg_M05 @0.95 = 80860 px
|
||||
mask all10 @10.00 = 2959 px
|
||||
PAL 0 id=9,240,40 rgb=226,175,169
|
||||
PAL 1 id=24,229,47 rgb=115,78,58
|
||||
PAL 2 id=39,218,54 rgb=199,199,198
|
||||
PAL 3 id=54,207,61 rgb=126,57,54
|
||||
PAL 4 id=69,196,68 rgb=207,173,129
|
||||
PAL 5 id=84,185,75 rgb=164,73,67
|
||||
PAL 6 id=99,174,82 rgb=185,131,97
|
||||
PAL 7 id=114,163,89 rgb=130,122,118
|
||||
PAL 8 id=129,152,96 rgb=215,186,139
|
||||
PAL 9 id=144,141,103 rgb=194,161,131
|
||||
PAL 10 id=159,130,110 rgb=116,92,78
|
||||
PAL 11 id=174,119,117 rgb=133,84,73
|
||||
PAL 12 id=189,108,124 rgb=224,205,153
|
||||
PAL 13 id=204,97,131 rgb=152,123,103
|
||||
PAL 14 id=219,86,138 rgb=177,173,172
|
||||
PAL 15 id=234,75,145 rgb=94,61,45
|
||||
amb shot v0_purple ambient=RGBA(0.217, 0.228, 0.250, 1.000) key=RGBA(1.000, 0.934, 0.838, 1.000)
|
||||
amb shot v1_gray ambient=RGBA(0.229, 0.229, 0.229, 1.000) key=RGBA(1.000, 0.934, 0.838, 1.000)
|
||||
amb shot v2_cool ambient=RGBA(0.205, 0.228, 0.262, 1.000) key=RGBA(1.000, 0.934, 0.838, 1.000)
|
||||
amb shot v3_warm ambient=RGBA(0.262, 0.226, 0.193, 1.000) key=RGBA(1.000, 0.934, 0.838, 1.000)
|
||||
amb shot v4_gray_keysoft ambient=RGBA(0.229, 0.229, 0.229, 1.000) key=RGBA(1.000, 0.950, 0.900, 1.000)
|
||||
amb shot v5_gray_keywhite ambient=RGBA(0.229, 0.229, 0.229, 1.000) key=RGBA(1.000, 1.000, 1.000, 1.000)
|
||||
wide w0_purple
|
||||
wide w1_gray
|
||||
wide w4_gray_keysoft
|
||||
wide w5_gray_keywhite
|
||||
DONE
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
renderers Arm_m05,Body_m05,Face,Hair05,Head,Leg_M05 · center (-0.01, 0.57, 0.00) · h 1.220
|
||||
Arm_m05 mat=M05_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
Body_m05 mat=M05_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
Face mat=face02_ToonP shader=Unlit/Transparent base=- diff=- shadow=- shades=- minDark=-
|
||||
Hair05 mat=Hair05_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
Head mat=Skin_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
Leg_M05 mat=M05_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
ReferenceLook: rimOn=False moodOn=True mood=0.30 contrast=True minDark=0.12 shadowScale=0.85 shadowTintAmt=0.30 shadowTint=RGBA(0.243, 0.106, 0.353, 1.000) charShades=0 key=RGBA(1.00, 0.78, 0.46, 1.00)×1.45 amb=RGBA(0.23, 0.23, 0.23, 1.00)
|
||||
ambient(SO)=RGBA(0.229, 0.229, 0.229, 1.000) diffuse=RGBA(0.993, 1.028, 0.874, 1.000)
|
||||
DONE
|
||||
|
|
@ -0,0 +1,518 @@
|
|||
// 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 WL814x3
|
||||
{
|
||||
public const string RAW = "Screenshots_WL/WL814x/3rd/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/WL814x3_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/WL814x3_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();
|
||||
}
|
||||
|
||||
/// <summary>3차 — 앰비언트/키라이트 색조 3+1안. 🔴 SO·씬·에셋은 안 건드린다(런타임 RenderSettings·Light.color 만).</summary>
|
||||
public static string Amb()
|
||||
{
|
||||
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;
|
||||
var lights = UnityEngine.Object.FindObjectsByType<Light>(FindObjectsSortMode.None)
|
||||
.Where(l => l.type == LightType.Directional).ToArray();
|
||||
|
||||
// 측정은 «보정 없는» 상태에서 — _DiffuseColor 를 흰색으로 되돌린 임시 복제본
|
||||
Action white = () =>
|
||||
{
|
||||
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_w" };
|
||||
c.SetColor("_DiffuseColor", Color.white);
|
||||
mats[m] = c;
|
||||
}
|
||||
s_rends[i].sharedMaterials = mats;
|
||||
}
|
||||
};
|
||||
|
||||
Action<string, Color, Color> V = (name, amb, key) =>
|
||||
{
|
||||
for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i];
|
||||
WLReferenceLook.Restore();
|
||||
var o = WLReferenceLook.Opt.FromSettings(cfg); o.rim = false;
|
||||
WLReferenceLook.Apply(o, cfg);
|
||||
if (amb.a > 0f) RenderSettings.ambientLight = new Color(amb.r, amb.g, amb.b, 1f);
|
||||
if (key.a > 0f) foreach (var l in lights) l.color = new Color(key.r, key.g, key.b, 1f);
|
||||
white();
|
||||
Shoot(name, 0.95f);
|
||||
Shoot(name + "_g", 10f);
|
||||
L(string.Format("amb shot {0} ambient={1} key={2}", name, RenderSettings.ambientLight.ToString("F3"),
|
||||
lights.Length > 0 ? lights[0].color.ToString("F3") : "-"));
|
||||
};
|
||||
|
||||
Color no = new Color(0, 0, 0, 0);
|
||||
// 현재 무드가 만드는 앰비언트 = lerp(원래, (0.28,0.17,0.40), 0.30) → 그대로 두면 v0
|
||||
V("v0_purple", no, no);
|
||||
V("v1_gray", new Color(0.229f, 0.229f, 0.229f, 1f), no);
|
||||
V("v2_cool", new Color(0.205f, 0.228f, 0.262f, 1f), no);
|
||||
V("v3_warm", new Color(0.262f, 0.226f, 0.193f, 1f), no);
|
||||
V("v4_gray_keysoft", new Color(0.229f, 0.229f, 0.229f, 1f), new Color(1f, 0.95f, 0.90f, 1f));
|
||||
V("v5_gray_keywhite", new Color(0.229f, 0.229f, 0.229f, 1f), new Color(1f, 1f, 1f, 1f));
|
||||
|
||||
// 배경 포함 넓은 구도(게임 실제 카메라 위치·직교 10) — 배경이 얼마나 바뀌는지
|
||||
for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i];
|
||||
Action<string, Color, Color> WIDE = (name, amb, key) =>
|
||||
{
|
||||
WLReferenceLook.Restore();
|
||||
var o = WLReferenceLook.Opt.FromSettings(cfg); o.rim = false;
|
||||
WLReferenceLook.Apply(o, cfg);
|
||||
if (amb.a > 0f) RenderSettings.ambientLight = new Color(amb.r, amb.g, amb.b, 1f);
|
||||
if (key.a > 0f) foreach (var l in lights) l.color = new Color(key.r, key.g, key.b, 1f);
|
||||
s_cam.orthographicSize = 10f;
|
||||
s_cam.transform.SetPositionAndRotation(s_camPos, s_camRot);
|
||||
var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
|
||||
rt.antiAliasing = 1; rt.filterMode = FilterMode.Point; rt.Create();
|
||||
var pv = s_cam.targetTexture; s_cam.targetTexture = rt; s_cam.Render(); s_cam.Render(); s_cam.targetTexture = pv;
|
||||
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;
|
||||
Directory.CreateDirectory(RAW); File.WriteAllBytes(RAW + name + ".png", t.EncodeToPNG());
|
||||
UnityEngine.Object.Destroy(t); rt.Release(); UnityEngine.Object.Destroy(rt);
|
||||
L("wide " + name);
|
||||
};
|
||||
WIDE("w0_purple", no, no);
|
||||
WIDE("w1_gray", new Color(0.229f, 0.229f, 0.229f, 1f), no);
|
||||
WIDE("w4_gray_keysoft", new Color(0.229f, 0.229f, 0.229f, 1f), new Color(1f, 0.95f, 0.90f, 1f));
|
||||
WIDE("w5_gray_keywhite", new Color(0.229f, 0.229f, 0.229f, 1f), new Color(1f, 1f, 1f, 1f));
|
||||
|
||||
for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i];
|
||||
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/WL814x3_AMB.txt", sb.ToString(), new UTF8Encoding(true)); }
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>3차 채택값 쓰기 — 앰비언트 중성화(814t SO) + 캐릭터 _DiffuseColor 흰색 복귀.</summary>
|
||||
public static string Adopt()
|
||||
{
|
||||
var sb2 = new StringBuilder();
|
||||
var rc = UnityEditor.AssetDatabase.LoadAssetAtPath<WLReferenceLookSettings>("Assets/WL/Look/Arena/Resources/WL/WLReferenceLookSettings.asset");
|
||||
sb2.AppendLine("ambientColor " + rc.ambientColor.ToString("F3") + " → 중성 (0.229,0.229,0.229)");
|
||||
rc.ambientColor = new Color(0.229f, 0.229f, 0.229f, 1f);
|
||||
UnityEditor.EditorUtility.SetDirty(rc);
|
||||
foreach (var n in new[] { "M05_ToonP", "Skin_ToonP", "Hair05_ToonP" })
|
||||
{
|
||||
var m = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>("Assets/WL/Look/Character/Materials/" + n + ".mat");
|
||||
var dc = new Color(0.993f, 1.028f, 0.874f, 1f);
|
||||
m.SetColor("_DiffuseColor", dc);
|
||||
UnityEditor.EditorUtility.SetDirty(m);
|
||||
sb2.AppendLine(n + " _DiffuseColor → " + dc.ToString("F3"));
|
||||
}
|
||||
UnityEditor.AssetDatabase.ImportAsset("Assets/WL/Look/Character/Textures/WLPalette_FINAL.png", UnityEditor.ImportAssetOptions.ForceUpdate);
|
||||
UnityEditor.AssetDatabase.SaveAssets();
|
||||
return sb2.ToString();
|
||||
}
|
||||
|
||||
/// <summary>최종 = 실제 배선된 머티리얼 그대로(보정 포함) 근접·게임크기·넓은 구도.</summary>
|
||||
public static string Final()
|
||||
{
|
||||
sb.Length = 0;
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
var cfg = WLReferenceLookSettings.Instance;
|
||||
L("ambient(SO)=" + cfg.ambientColor.ToString("F3") + " diffuse=" + s_rends[0].sharedMaterial.GetColor("_DiffuseColor").ToString("F3"));
|
||||
WLReferenceLook.Restore();
|
||||
var o = WLReferenceLook.Opt.FromSettings(cfg); o.rim = false;
|
||||
WLReferenceLook.Apply(o, cfg);
|
||||
Shoot("f_final", 0.95f);
|
||||
Shoot("f_final_g", 10f);
|
||||
s_cam.orthographicSize = 10f;
|
||||
s_cam.transform.SetPositionAndRotation(s_camPos, s_camRot);
|
||||
var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
|
||||
rt.antiAliasing = 1; rt.filterMode = FilterMode.Point; rt.Create();
|
||||
var pv = s_cam.targetTexture; s_cam.targetTexture = rt; s_cam.Render(); s_cam.Render(); s_cam.targetTexture = pv;
|
||||
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;
|
||||
Directory.CreateDirectory(RAW); File.WriteAllBytes(RAW + "f_wide.png", t.EncodeToPNG());
|
||||
UnityEngine.Object.Destroy(t); rt.Release(); UnityEngine.Object.Destroy(rt);
|
||||
L("DONE");
|
||||
}
|
||||
catch (Exception e) { L("EXCEPTION " + e); }
|
||||
finally { try { Teardown(); } catch { } File.WriteAllText("AgentScripts/WL814x3_FINAL.txt", sb.ToString(), new UTF8Encoding(true)); }
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>어두운 면(피부)의 잔여 색조 — 그림자 알베도 색을 바꿔 비교(에셋 무수정·임시 복제본).</summary>
|
||||
public static string Shad()
|
||||
{
|
||||
sb.Length = 0;
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
SaveMask("all", 0.95f, s_rends);
|
||||
foreach (var r in s_rends) SaveMask(r.name, 0.95f, r);
|
||||
ShootIdMap("idmap", 0.95f);
|
||||
var cfg = WLReferenceLookSettings.Instance;
|
||||
Action<string, Color> V = (name, sh) =>
|
||||
{
|
||||
for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i];
|
||||
WLReferenceLook.Restore();
|
||||
if (sh.a > 0f)
|
||||
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_s" };
|
||||
c.SetColor("_ShadowDiffuseColor", new Color(sh.r, sh.g, sh.b, 0f));
|
||||
mats[m] = c;
|
||||
}
|
||||
s_rends[i].sharedMaterials = mats;
|
||||
}
|
||||
var o = WLReferenceLook.Opt.FromSettings(cfg); o.rim = false;
|
||||
WLReferenceLook.Apply(o, cfg);
|
||||
Shoot(name, 0.95f); Shoot(name + "_g", 10f);
|
||||
L("shadow shot " + name + " = " + sh.ToString("F3"));
|
||||
};
|
||||
V("s0_now", new Color(0, 0, 0, 0));
|
||||
V("s1_gray", new Color(0.38f, 0.38f, 0.38f, 1f));
|
||||
V("s2_warm", new Color(0.42f, 0.38f, 0.32f, 1f));
|
||||
V("s3_graylight", new Color(0.50f, 0.50f, 0.50f, 1f));
|
||||
V("s4_warmlight", new Color(0.55f, 0.50f, 0.42f, 1f));
|
||||
for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i];
|
||||
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/WL814x3_SHAD.txt", sb.ToString(), new UTF8Encoding(true)); }
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
renderers Arm_m05,Body_m05,Face,Hair05,Head,Leg_M05 · center (-0.01, 0.57, 0.00) · h 1.220
|
||||
Arm_m05 mat=M05_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
Body_m05 mat=M05_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
Face mat=face02_ToonP shader=Unlit/Transparent base=- diff=- shadow=- shades=- minDark=-
|
||||
Hair05 mat=Hair05_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
Head mat=Skin_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
Leg_M05 mat=M05_ToonP_814t shader=Shader Graphs/Toon base=WLPalette_FINAL diff=RGBA(0.993, 1.028, 0.874, 1.000) shadow=RGBA(0.1430, 0.1307, 0.2416, 0.0000) shades=3 minDark=0.12
|
||||
ReferenceLook: rimOn=False moodOn=True mood=0.30 contrast=True minDark=0.12 shadowScale=0.85 shadowTintAmt=0.30 shadowTint=RGBA(0.243, 0.106, 0.353, 1.000) charShades=0 key=RGBA(1.00, 0.78, 0.46, 1.00)×1.45 amb=RGBA(0.23, 0.23, 0.23, 1.00)
|
||||
mask all @0.95 = 330746 px
|
||||
mask Arm_m05 @0.95 = 36600 px
|
||||
mask Body_m05 @0.95 = 107658 px
|
||||
mask Face @0.95 = 7136 px
|
||||
mask Hair05 @0.95 = 127668 px
|
||||
mask Head @0.95 = 116073 px
|
||||
mask Leg_M05 @0.95 = 80860 px
|
||||
PAL 0 id=9,240,40 rgb=226,175,169
|
||||
PAL 1 id=24,229,47 rgb=115,78,58
|
||||
PAL 2 id=39,218,54 rgb=199,199,198
|
||||
PAL 3 id=54,207,61 rgb=126,57,54
|
||||
PAL 4 id=69,196,68 rgb=207,173,129
|
||||
PAL 5 id=84,185,75 rgb=164,73,67
|
||||
PAL 6 id=99,174,82 rgb=185,131,97
|
||||
PAL 7 id=114,163,89 rgb=130,122,118
|
||||
PAL 8 id=129,152,96 rgb=215,186,139
|
||||
PAL 9 id=144,141,103 rgb=194,161,131
|
||||
PAL 10 id=159,130,110 rgb=116,92,78
|
||||
PAL 11 id=174,119,117 rgb=133,84,73
|
||||
PAL 12 id=189,108,124 rgb=224,205,153
|
||||
PAL 13 id=204,97,131 rgb=152,123,103
|
||||
PAL 14 id=219,86,138 rgb=177,173,172
|
||||
PAL 15 id=234,75,145 rgb=94,61,45
|
||||
shadow shot s0_now = RGBA(0.000, 0.000, 0.000, 0.000)
|
||||
shadow shot s1_gray = RGBA(0.380, 0.380, 0.380, 1.000)
|
||||
shadow shot s2_warm = RGBA(0.420, 0.380, 0.320, 1.000)
|
||||
shadow shot s3_graylight = RGBA(0.500, 0.500, 0.500, 1.000)
|
||||
shadow shot s4_warmlight = RGBA(0.550, 0.500, 0.420, 1.000)
|
||||
DONE
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# WL-814x3 — 팔레트 칸을 「최종 화면에서 원하는 색이 나오도록」 역산한다.
|
||||
# 화면색 S = 팔레트 P ⊙ m (조명은 채널별 곱 · 포스트 영향 0.1° 실측) → P' = P ⊙ (T / S)
|
||||
# T = 원본색 O 를 S 의 밝기로 맞춘 것 (색조만 되돌리고 명암은 조명이 정한 대로 둔다)
|
||||
import json, sys
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
RAW='Screenshots_WL/WL814x/3rd/raw'
|
||||
PAL='Assets/WL/Look/Character/Textures/WLPalette_FINAL.png'
|
||||
REN=sys.argv[1]; APPLY=len(sys.argv)>2 and sys.argv[2]=='apply'
|
||||
CLAMP=float(sys.argv[3]) if len(sys.argv)>3 else 1.35
|
||||
DAMP=float(sys.argv[4]) if len(sys.argv)>4 else 0.55 # 감쇠: 모델이 근사라 한 번에 다 가면 발산한다
|
||||
def L(n): return np.asarray(Image.open(RAW+'/'+n+'.png').convert('RGB'),dtype=np.uint8)
|
||||
def lum(c): return 0.299*c[0]+0.587*c[1]+0.114*c[2]
|
||||
def hue(c):
|
||||
r,g,b=[float(x) for x in c]; mx,mn=max(r,g,b),min(r,g,b); d=mx-mn
|
||||
if d<1e-6: return 0.0
|
||||
if mx==r: h=((g-b)/d)%6
|
||||
elif mx==g: h=(b-r)/d+2
|
||||
else: h=(r-g)/d+4
|
||||
return h*60.0
|
||||
def sat(c):
|
||||
mx=max(c); return (mx-min(c))/mx if mx>1e-6 else 0
|
||||
def dh(a,b): return (b-a+540)%360-180
|
||||
idm=L('idmap'); maF=L('mask_Face')[:,:,0]>127; ma=(L('mask_all')[:,:,0]>127)&~maF
|
||||
pal=np.array(Image.open(PAL).convert('RGB'),dtype=np.uint8); G=pal.shape[0]//8
|
||||
idref=np.array([[9+j*15,240-j*11,40+j*7] for j in range(G*G)],np.float32)
|
||||
fl=idm.reshape(-1,3).astype(np.float32); d2=((fl[:,None,:]-idref[None,:,:])**2).sum(2)
|
||||
kid=np.where((d2.min(1).reshape(idm.shape[:2])<400)&(idm.sum(2)>12), d2.argmin(1).reshape(idm.shape[:2]), -1)
|
||||
tgt={int(k):np.array(v,np.float32) for k,v in json.load(open('AgentScripts/WL814x2_target.json')).items()}
|
||||
im=L(REN); newpal=pal.copy()
|
||||
print('%3s %7s | %-16s %-16s %-16s | %-16s %7s %7s'%('칸','px','원본 O','팔레트 P','화면 S','새 팔레트 P\'','dHue','예상'))
|
||||
e0=[];e1=[];w=[]
|
||||
for j in range(G*G):
|
||||
gx,gy=j%G,j//G
|
||||
sel=ma&(kid==j); n=int(sel.sum())
|
||||
if n<800: continue
|
||||
p=pal[gy*8+4,gx*8+4].astype(np.float64); o=tgt[j].astype(np.float64)
|
||||
s=im[sel].mean(0).astype(np.float64)
|
||||
t=o*(max(lum(s),1.0)/max(lum(o),1.0))
|
||||
g=np.clip(t/np.maximum(s,6.0),1.0/CLAMP,CLAMP)
|
||||
g=np.power(g,DAMP) # 감쇠
|
||||
g=g/max(lum(g),1e-6) # 밝기는 유지, 색조만
|
||||
g=np.clip(g,1.0/CLAMP,CLAMP)
|
||||
np_=p*g
|
||||
if np_.max()>248: np_=np_*248.0/np_.max()
|
||||
np_=np.clip(np_,4,255)
|
||||
pred=np.clip(s*(np_/np.maximum(p,1.0)),0,255)
|
||||
print('%3d %7d | %-16s %-16s %-16s | %-16s %+7.1f %+7.1f'%(j,n,tuple(int(x) for x in o),tuple(int(x) for x in p),
|
||||
tuple(int(x) for x in s),tuple(int(x) for x in np_),dh(hue(o),hue(s)),dh(hue(o),hue(pred))))
|
||||
if sat(o)>0.12:
|
||||
e0.append(abs(dh(hue(o),hue(s)))); e1.append(abs(dh(hue(o),hue(pred)))); w.append(n)
|
||||
if APPLY: newpal[gy*8:gy*8+8,gx*8:gx*8+8]=np.round(np_).astype(np.uint8)
|
||||
w=np.array(w,float); e0=np.array(e0); e1=np.array(e1)
|
||||
print('유채색 가중 dHue %.1f°(최대 %.1f) → 예상 %.1f°(최대 %.1f)'%((e0*w).sum()/w.sum(),e0.max(),(e1*w).sum()/w.sum(),e1.max()))
|
||||
if APPLY:
|
||||
Image.fromarray(newpal).save(PAL); print('팔레트 갱신 →',PAL)
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# WL-814x3 평가기 — 안별로 ① 부위 dHue ② 최적 _DiffuseColor 역이득(예측) ③ 게임 크기 고유색
|
||||
import json, sys
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
RAW='Screenshots_WL/WL814x/3rd/raw'
|
||||
def L(n): return np.asarray(Image.open(RAW+'/'+n+'.png').convert('RGB'),dtype=np.uint8)
|
||||
def hue(c):
|
||||
r,g,b=[float(x) for x in c]; mx,mn=max(r,g,b),min(r,g,b); d=mx-mn
|
||||
if d<1e-6: return 0.0
|
||||
if mx==r: h=((g-b)/d)%6
|
||||
elif mx==g: h=(b-r)/d+2
|
||||
else: h=(r-g)/d+4
|
||||
return h*60.0
|
||||
def sat(c):
|
||||
mx=max(c); return (mx-min(c))/mx if mx>1e-6 else 0
|
||||
def dh(a,b): return (b-a+540)%360-180
|
||||
idm=L('idmap'); maF=L('mask_Face')[:,:,0]>127; ma=(L('mask_all')[:,:,0]>127)&~maF
|
||||
G=np.array(Image.open('Assets/WL/Look/Character/Textures/WLPalette_FINAL.png')).shape[0]//8
|
||||
idref=np.array([[9+j*15,240-j*11,40+j*7] for j in range(G*G)],np.float32)
|
||||
fl=idm.reshape(-1,3).astype(np.float32); d2=((fl[:,None,:]-idref[None,:,:])**2).sum(2)
|
||||
kid=np.where((d2.min(1).reshape(idm.shape[:2])<400)&(idm.sum(2)>12), d2.argmin(1).reshape(idm.shape[:2]), -1)
|
||||
tgt={int(k):np.array(v,np.float32) for k,v in json.load(open('AgentScripts/WL814x2_target.json')).items()}
|
||||
NAME={8:'머리칼(주)',4:'머리칼(밝)',2:'흰튜닉',1:'가죽갑옷',15:'붉은바지',0:'머리피부',5:'다리피부',14:'부츠흰면',11:'팔소매',10:'벨트',12:'머리칼(하)',3:'부츠갈색',13:'가죽(어두운)',9:'붉은천'}
|
||||
cells=[]
|
||||
for j in range(G*G):
|
||||
sel=ma&(kid==j); n=int(sel.sum())
|
||||
if n<800: continue
|
||||
cells.append((j,n,sel,tgt[j]))
|
||||
m10=L('mask_all10')[:,:,0]>127
|
||||
def cellmeans(im):
|
||||
return [(j,n,o,im[sel].mean(0).astype(np.float64)) for j,n,sel,o in cells]
|
||||
def errs(cm,g):
|
||||
return [(j,n,o,np.clip(s*g,0,255),dh(hue(o),hue(np.clip(s*g,0,255))),sat(o)) for j,n,o,s in cm]
|
||||
def wavg(out):
|
||||
ch=[(n,abs(e)) for j,n,o,s,e,so in out if so>0.12]
|
||||
w=np.array([c[0] for c in ch],float); e=np.array([c[1] for c in ch])
|
||||
return (e*w).sum()/w.sum(), e.max()
|
||||
print('%-18s %7s %7s | %-22s %7s %7s | %6s %6s'%('안','dHue','최대','최적 _DiffuseColor','dHue*','최대*','고유색','비율%'))
|
||||
best={}
|
||||
for v in sys.argv[1:]:
|
||||
cm=cellmeans(L(v))
|
||||
o0=errs(cm,np.ones(3)); a0,m0=wavg(o0)
|
||||
bb=None
|
||||
for gr in np.arange(0.80,1.25,0.015):
|
||||
for gb in np.arange(0.70,1.35,0.015):
|
||||
gg=(1.0-0.299*gr-0.114*gb)/0.587
|
||||
if gg<0.7 or gg>1.4: continue
|
||||
g=np.array([gr,gg,gb]); a,m=wavg(errs(cm,g))
|
||||
sc=a+0.35*m
|
||||
if bb is None or sc<bb[0]: bb=(sc,g,a,m)
|
||||
g=L(v+'_g')[m10]; k=(g[:,0].astype(np.int32)<<16)|(g[:,1].astype(np.int32)<<8)|g[:,2].astype(np.int32)
|
||||
uc=len(np.unique(k))
|
||||
print('%-18s %7.1f %7.1f | %-22s %7.1f %7.1f | %6d %6.1f'%(v,a0,m0,str(np.round(bb[1],3)),bb[2],bb[3],uc,100.0*uc/m10.sum()))
|
||||
best[v]=bb[1]
|
||||
json.dump({k:[float(x) for x in v] for k,v in best.items()}, open('AgentScripts/WL814x3_gains.json','w'))
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"s0_now": [1.2350000000000003, 0.9385604770017035, 0.7], "s1_gray": [1.2350000000000003, 0.9385604770017035, 0.7], "s2_warm": [1.2350000000000003, 0.9385604770017035, 0.7], "s3_graylight": [1.2350000000000003, 0.9385604770017035, 0.7], "s4_warmlight": [1.2350000000000003, 0.9385604770017035, 0.7]}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# WL-814x3 — 최종 수치표 + 필수 캡처 4종
|
||||
import json
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw
|
||||
RAW='Screenshots_WL/WL814x/3rd/raw'; OUT='Screenshots_WL/WL814x/3rd'
|
||||
def L(n): return np.asarray(Image.open(RAW+'/'+n+'.png').convert('RGB'),dtype=np.uint8)
|
||||
def hue(c):
|
||||
r,g,b=[float(x) for x in c]; mx,mn=max(r,g,b),min(r,g,b); d=mx-mn
|
||||
if d<1e-6: return 0.0
|
||||
if mx==r: h=((g-b)/d)%6
|
||||
elif mx==g: h=(b-r)/d+2
|
||||
else: h=(r-g)/d+4
|
||||
return h*60.0
|
||||
def sat(c):
|
||||
mx=max(c); return (mx-min(c))/mx if mx>1e-6 else 0
|
||||
def dh(a,b): return (b-a+540)%360-180
|
||||
idm=L('idmap'); maF=L('mask_Face')[:,:,0]>127; ma=(L('mask_all')[:,:,0]>127)&~maF
|
||||
G=np.array(Image.open('Assets/WL/Look/Character/Textures/WLPalette_FINAL.png')).shape[0]//8
|
||||
idref=np.array([[9+j*15,240-j*11,40+j*7] for j in range(G*G)],np.float32)
|
||||
fl=idm.reshape(-1,3).astype(np.float32); d2=((fl[:,None,:]-idref[None,:,:])**2).sum(2)
|
||||
kid=np.where((d2.min(1).reshape(idm.shape[:2])<400)&(idm.sum(2)>12), d2.argmin(1).reshape(idm.shape[:2]), -1)
|
||||
tgt={int(k):np.array(v,np.float32) for k,v in json.load(open('AgentScripts/WL814x2_target.json')).items()}
|
||||
NAME={8:'머리칼(주)',4:'머리칼(밝은단)',12:'머리칼(하이라이트)',2:'흰 튜닉',14:'부츠 흰면',1:'가죽 갑옷',13:'가죽(어두운)',3:'부츠 갈색',15:'붉은 바지',9:'붉은 천',10:'벨트',11:'팔 소매',0:'머리 피부',5:'다리 피부'}
|
||||
m10=L('mask_all10')[:,:,0]>127
|
||||
print('%-16s %7s | %-16s %-16s %8s | %-16s %8s'%('부위','px','원본 RGB','2차(보라 앰비언트)','dHue','3차(중성+보정)','dHue'))
|
||||
ch0=[];ch1=[];w=[]
|
||||
for j in sorted(NAME):
|
||||
sel=ma&(kid==j); n=int(sel.sum())
|
||||
if n<800: continue
|
||||
o=tgt[j]; a=L('pre_v0_purple')[sel].mean(0); b=L('f_final')[sel].mean(0)
|
||||
ea=dh(hue(o),hue(a)); eb=dh(hue(o),hue(b))
|
||||
print('%-16s %7d | %-16s %-16s %+8.1f | %-16s %+8.1f'%(NAME[j],n,tuple(int(x) for x in o),
|
||||
tuple(int(x) for x in a),ea,tuple(int(x) for x in b),eb))
|
||||
if sat(o)>0.12: ch0.append(abs(ea)); ch1.append(abs(eb)); w.append(n)
|
||||
w=np.array(w,float); ch0=np.array(ch0); ch1=np.array(ch1)
|
||||
print('유채색 가중 dHue: 2차 %.1f°(최대 %.1f) → 3차 %.1f°(최대 %.1f)'%((ch0*w).sum()/w.sum(),ch0.max(),(ch1*w).sum()/w.sum(),ch1.max()))
|
||||
for v,lbl in [('nc_gray','보정전(중성 앰비언트)'),('s0_now','3차 최종')]:
|
||||
g=L(v+'_g')[m10]; k=(g[:,0].astype(np.int32)<<16)|(g[:,1].astype(np.int32)<<8)|g[:,2].astype(np.int32)
|
||||
u=len(np.unique(k)); print('%s 게임 크기: 캐릭터 %d px · 고유색 %d · 서로 다른 색 %.1f %%'%(lbl,m10.sum(),u,100.0*u/m10.sum()))
|
||||
# 캡처
|
||||
ys,xs=np.where(ma|maF); x0,x1,y0,y1=xs.min()-24,xs.max()+25,ys.min()-24,ys.max()+25
|
||||
def crop(n): return Image.open(RAW+'/'+n+'.png').convert('RGB').crop((x0,y0,x1,y1))
|
||||
def grid(out,items,scale=1.0):
|
||||
ims=[(t,crop(n)) for t,n in items]
|
||||
if scale!=1.0: ims=[(t,im.resize((int(im.width*scale),int(im.height*scale)),Image.NEAREST)) for t,im in ims]
|
||||
W=sum(i.width for _,i in ims)+18*(len(ims)-1); H=ims[0][1].height+26
|
||||
o=Image.new('RGB',(W,H),(22,22,26)); d=ImageDraw.Draw(o); x=0
|
||||
for t,im in ims:
|
||||
o.paste(im,(x,26)); d.text((x+3,7),t,fill=(240,232,190)); x+=im.width+18
|
||||
o.save(OUT+'/'+out); print('saved',out,o.size)
|
||||
grid('d_compare.png',[('2차 (보라 앰비언트)','pre_v0_purple'),('3차 최종 (중성 앰비언트 + 보정)','s0_now')])
|
||||
grid('d_ambient_steps.png',[('보라(기존)','pre_v0_purple'),('중성 회색','pre_v1_gray'),('옅은 청회','v2_cool'),('옅은 난색','v3_warm')],0.72)
|
||||
def wide(out,items):
|
||||
ims=[(t,Image.open(RAW+'/'+n+'.png').convert('RGB').resize((405,720))) for t,n in items]
|
||||
W=sum(i.width for _,i in ims)+16*(len(ims)-1); H=ims[0][1].height+26
|
||||
o=Image.new('RGB',(W,H),(22,22,26)); d=ImageDraw.Draw(o); x=0
|
||||
for t,im in ims:
|
||||
o.paste(im,(x,26)); d.text((x+3,7),t,fill=(240,232,190)); x+=im.width+16
|
||||
o.save(OUT+'/'+out); print('saved',out,o.size)
|
||||
wide('d_key_bg.png',[('기존 키라이트','w1_gray'),('키 (1,.95,.90)','w4_gray_keysoft'),('키 순백 (1,1,1)','w5_gray_keywhite')])
|
||||
Image.open(RAW+'/f_wide.png').convert('RGB').save(OUT+'/d_final_wide.png')
|
||||
print('saved d_final_wide.png')
|
||||
|
|
@ -37,7 +37,7 @@ MonoBehaviour:
|
|||
asset: {fileID: 11400000, guid: 534beeaf638a948459a24ec36906d7f6, type: 2}
|
||||
keyLightColor: {r: 1, g: 0.78, b: 0.46, a: 1}
|
||||
keyLightIntensity: 1.45
|
||||
ambientColor: {r: 0.28, g: 0.17, b: 0.4, a: 1}
|
||||
ambientColor: {r: 0.229, g: 0.229, b: 0.229, a: 1}
|
||||
rimOn: 0
|
||||
rimStrength: 0.6
|
||||
rimPower: 3
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ Material:
|
|||
m_Colors:
|
||||
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
|
||||
- _Cloud_Step: {r: 13, g: 17, b: 0, a: 0}
|
||||
- _DiffuseColor: {r: 0.994, g: 1.024, b: 0.894, a: 1}
|
||||
- _DiffuseColor: {r: 0.993, g: 1.028, b: 0.874, a: 1}
|
||||
- _NormalBias: {r: 1, g: 1, b: 1, a: 0}
|
||||
- _Outline: {r: 0, g: 0, b: 0, a: 0}
|
||||
- _ShadowDiffuseColor: {r: 0.2164, g: 0.2092, b: 0.3713, a: 0}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ Material:
|
|||
m_Colors:
|
||||
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
|
||||
- _Cloud_Step: {r: 13, g: 17, b: 0, a: 0}
|
||||
- _DiffuseColor: {r: 0.994, g: 1.024, b: 0.894, a: 1}
|
||||
- _DiffuseColor: {r: 0.993, g: 1.028, b: 0.874, a: 1}
|
||||
- _NormalBias: {r: 1, g: 1, b: 1, a: 0}
|
||||
- _Outline: {r: 0, g: 0, b: 0, a: 0}
|
||||
- _ShadowDiffuseColor: {r: 0.2164, g: 0.2092, b: 0.3713, a: 0}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ Material:
|
|||
m_Colors:
|
||||
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
|
||||
- _Cloud_Step: {r: 13, g: 17, b: 0, a: 0}
|
||||
- _DiffuseColor: {r: 0.994, g: 1.024, b: 0.894, a: 1}
|
||||
- _DiffuseColor: {r: 0.993, g: 1.028, b: 0.874, a: 1}
|
||||
- _NormalBias: {r: 1, g: 1, b: 1, a: 0}
|
||||
- _Outline: {r: 0, g: 0, b: 0, a: 0}
|
||||
- _ShadowDiffuseColor: {r: 0.2164, g: 0.2092, b: 0.3713, a: 0}
|
||||
|
|
|
|||
Loading…
Reference in New Issue