[WL-814x2] 화면 색 실측(Play) + 캐릭터 전용 광원 색조 상쇄 (#814)
측정 지점을 알베도가 아니라 **Play 화면 픽셀**로 옮겨 다시 쟀다(편집 모드 렌더는 무의미).
아레나 근접(직교 0.95) · 캐릭터 격리 마스크 · 덩어리(팔레트 칸) 지도 기준.
원인(실측 · 추정 아님)
ⓐ 소스 텍스처 = 무죄. *_WL.png 는 Suriyun 원본과 MD5 동일.
ⓑ 그림자색(_ShadowDiffuseColor) = 영향 0.2° 미만 — 중성/따뜻/어둡게 4종 비교.
ⓒ 포스트 그레이딩 Volume = 영향 0.1° 미만 — weight 0 으로 꺼도 색이 그대로.
🔴 진범 = 무드의 **조명**(키라이트 (1,0.78,0.46)x1.45 + 보라 앰비언트).
평면 색은 디테일이 없어 빛의 색조가 그대로 드러난다.
mood 0.30 -> 0 이면 금발·흰옷이 돌아오지만 고유색 132 -> 906(평탄함이 깨진다).
이번 조치 = 캐릭터 전용 휘도보존 역이득 _DiffuseColor (0.994, 1.024, 0.894)
씬·조명·814t 설정·ProjectSettings 는 하나도 안 건드린다.
유채색 가중 dHue 25.1° -> 17.0° · 머리칼 -16.9° -> -7.0° · 흰 튜닉 -74.1° -> -10.4°
붉은 바지 -7.7° -> +0.7° · 게임 크기 고유색 132 -> 121 (4.1 %)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
4af9356a1d
commit
885f83fa03
|
|
@ -0,0 +1,38 @@
|
||||||
|
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.994, 1.024, 0.894, 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.994, 1.024, 0.894, 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.994, 1.024, 0.894, 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.994, 1.024, 0.894, 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.994, 1.024, 0.894, 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.28, 0.17, 0.40, 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
|
||||||
|
mood shot m30 mood=0.30 killWB=False killSMH=False
|
||||||
|
mood shot m15 mood=0.15 killWB=False killSMH=False
|
||||||
|
mood shot m00 mood=0.00 killWB=False killSMH=False
|
||||||
|
mood shot n_novol30 mood=0.30 killWB=True killSMH=False
|
||||||
|
mood shot n_novol15 mood=0.15 killWB=True killSMH=False
|
||||||
|
DONE
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
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(1.000, 1.000, 1.000, 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(1.000, 1.000, 1.000, 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(1.000, 1.000, 1.000, 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(1.000, 1.000, 1.000, 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(1.000, 1.000, 1.000, 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.28, 0.17, 0.40, 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
|
||||||
|
mask Arm_m0510 @10.00 = 333 px
|
||||||
|
mask Body_m0510 @10.00 = 973 px
|
||||||
|
mask Face10 @10.00 = 65 px
|
||||||
|
mask Hair0510 @10.00 = 1147 px
|
||||||
|
mask Head10 @10.00 = 1045 px
|
||||||
|
mask Leg_M0510 @10.00 = 726 px
|
||||||
|
PAL 0 id=9,240,40 rgb=192,193,140
|
||||||
|
PAL 1 id=24,229,47 rgb=98,86,48
|
||||||
|
PAL 2 id=39,218,54 rgb=169,220,164
|
||||||
|
PAL 3 id=54,207,61 rgb=107,63,44
|
||||||
|
PAL 4 id=69,196,68 rgb=176,191,107
|
||||||
|
PAL 5 id=84,185,75 rgb=139,80,55
|
||||||
|
PAL 6 id=99,174,82 rgb=157,145,80
|
||||||
|
PAL 7 id=114,163,89 rgb=110,135,97
|
||||||
|
PAL 8 id=129,152,96 rgb=183,206,115
|
||||||
|
PAL 9 id=144,141,103 rgb=165,178,108
|
||||||
|
PAL 10 id=159,130,110 rgb=98,101,64
|
||||||
|
PAL 11 id=174,119,117 rgb=113,93,60
|
||||||
|
PAL 12 id=189,108,124 rgb=190,227,127
|
||||||
|
PAL 13 id=204,97,131 rgb=129,136,85
|
||||||
|
PAL 14 id=219,86,138 rgb=150,191,142
|
||||||
|
PAL 15 id=234,75,145 rgb=80,67,37
|
||||||
|
PAL 0 id=9,240,40 rgb=192,193,140
|
||||||
|
PAL 1 id=24,229,47 rgb=98,86,48
|
||||||
|
PAL 2 id=39,218,54 rgb=169,220,164
|
||||||
|
PAL 3 id=54,207,61 rgb=107,63,44
|
||||||
|
PAL 4 id=69,196,68 rgb=176,191,107
|
||||||
|
PAL 5 id=84,185,75 rgb=139,80,55
|
||||||
|
PAL 6 id=99,174,82 rgb=157,145,80
|
||||||
|
PAL 7 id=114,163,89 rgb=110,135,97
|
||||||
|
PAL 8 id=129,152,96 rgb=183,206,115
|
||||||
|
PAL 9 id=144,141,103 rgb=165,178,108
|
||||||
|
PAL 10 id=159,130,110 rgb=98,101,64
|
||||||
|
PAL 11 id=174,119,117 rgb=113,93,60
|
||||||
|
PAL 12 id=189,108,124 rgb=190,227,127
|
||||||
|
PAL 13 id=204,97,131 rgb=129,136,85
|
||||||
|
PAL 14 id=219,86,138 rgb=150,191,142
|
||||||
|
PAL 15 id=234,75,145 rgb=80,67,37
|
||||||
|
shot a_now shadow=RGBA(0.216, 0.209, 0.371, 0.000) shades=3 mood=0.30 tintAmt=0.30 · Ⓐ136 Ⓑ14mat/md0.12 Ⓒ0.30 Ⓓoff
|
||||||
|
shot b_neutral shadow=RGBA(0.450, 0.450, 0.450, 0.000) shades=3 mood=0.30 tintAmt=0.30 · Ⓐ136 Ⓑ14mat/md0.12 Ⓒ0.30 Ⓓoff
|
||||||
|
shot c_neutral_notint shadow=RGBA(0.450, 0.450, 0.450, 0.000) shades=3 mood=0.30 tintAmt=0.00 · Ⓐ136 Ⓑ14mat/md0.12 Ⓒ0.30 Ⓓoff
|
||||||
|
shot d_neutral_notint_mood0 shadow=RGBA(0.450, 0.450, 0.450, 0.000) shades=3 mood=0.00 tintAmt=0.00 · Ⓐ136 Ⓑ14mat/md0.12 Ⓒoff Ⓓoff
|
||||||
|
shot e_warm_notint shadow=RGBA(0.520, 0.450, 0.400, 0.000) shades=3 mood=0.30 tintAmt=0.00 · Ⓐ136 Ⓑ14mat/md0.12 Ⓒ0.30 Ⓓoff
|
||||||
|
shot f_neutral_notint_s7 shadow=RGBA(0.450, 0.450, 0.450, 0.000) shades=7 mood=0.30 tintAmt=0.00 · Ⓐ136 Ⓑ14mat/md0.12 Ⓒ0.30 Ⓓoff
|
||||||
|
shot g_dark_notint shadow=RGBA(0.320, 0.320, 0.340, 0.000) shades=3 mood=0.30 tintAmt=0.00 · Ⓐ136 Ⓑ14mat/md0.12 Ⓒ0.30 Ⓓoff
|
||||||
|
DONE
|
||||||
|
|
@ -0,0 +1,325 @@
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
# WL-814x2 팔레트 「최종 화면 색」 역보정 (닫힌 루프)
|
||||||
|
# 측정 지점을 알베도가 아니라 **Play 화면 픽셀**로 옮긴다.
|
||||||
|
# 칸마다 원본색 O(부위 보정 역산) · 화면색 S(측정) 를 비교해
|
||||||
|
# 목표 T = O × (밝기 S / 밝기 O) 가 되도록 팔레트 칸 색에 채널별 이득을 곱한다.
|
||||||
|
# python AgentScripts/WL814x2_calib.py <측정에쓸 렌더이름> [apply]
|
||||||
|
import sys, os, json
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
RAW = 'Screenshots_WL/WL814x/2nd/raw'
|
||||||
|
PAL = 'Assets/WL/Look/Character/Textures/WLPalette_FINAL.png'
|
||||||
|
TGT = 'AgentScripts/WL814x2_target.json'
|
||||||
|
GAIN = {'M05': (0.80, 1.00), 'Skin': (0.86, 0.93), 'Hair': (0.85, 0.93)}
|
||||||
|
PARTMAP = {'Body_m05': 'M05', 'Arm_m05': 'M05', 'Leg_M05': 'M05', 'Head': 'Skin', 'Hair05': 'Hair'}
|
||||||
|
REN = sys.argv[1] if len(sys.argv) > 1 else 'a_now'
|
||||||
|
APPLY = len(sys.argv) > 2 and sys.argv[2] == 'apply'
|
||||||
|
|
||||||
|
|
||||||
|
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 dh(a, b):
|
||||||
|
return (b - a + 540) % 360 - 180
|
||||||
|
|
||||||
|
|
||||||
|
idm = L('idmap'); ma = L('mask_all')[:, :, 0] > 127
|
||||||
|
palimg = np.array(Image.open(PAL).convert('RGB'), dtype=np.uint8)
|
||||||
|
S = palimg.shape[0]; G = S // 8
|
||||||
|
idref = np.array([[9 + j * 15, 240 - j * 11, 40 + j * 7] for j in range(G * G)], np.float32)
|
||||||
|
flat = idm.reshape(-1, 3).astype(np.float32)
|
||||||
|
d2 = ((flat[:, None, :] - idref[None, :, :]) ** 2).sum(2)
|
||||||
|
near = d2.argmin(1).reshape(idm.shape[:2])
|
||||||
|
kid = np.where((d2.min(1).reshape(idm.shape[:2]) < 400) & (idm.sum(2) > 12), near, -1)
|
||||||
|
|
||||||
|
masks = {}
|
||||||
|
for p in PARTMAP:
|
||||||
|
f = RAW + '/mask_' + p + '.png'
|
||||||
|
if os.path.exists(f):
|
||||||
|
masks[p] = L('mask_' + p)[:, :, 0] > 127
|
||||||
|
|
||||||
|
# 목표색(원본) — 최초 1회만 저장하고 이후 회차에서는 고정해 쓴다
|
||||||
|
if os.path.exists(TGT):
|
||||||
|
target = {int(k): v for k, v in json.load(open(TGT)).items()}
|
||||||
|
else:
|
||||||
|
target = {}
|
||||||
|
for j in range(G * G):
|
||||||
|
gx, gy = j % G, j // G
|
||||||
|
p = palimg[gy * 8 + 4, gx * 8 + 4].astype(np.float32)
|
||||||
|
sel = ma & (kid == j)
|
||||||
|
if sel.sum() < 200:
|
||||||
|
target[j] = [float(x) for x in p]; continue
|
||||||
|
part = max(((k, int((sel & v).sum())) for k, v in masks.items()), key=lambda t: t[1])[0]
|
||||||
|
gs, gv = GAIN[PARTMAP[part]]
|
||||||
|
mx = p.max() / 255.0; mn = p.min() / 255.0
|
||||||
|
s = (mx - mn) / mx if mx > 1e-6 else 0
|
||||||
|
s2 = min(s / gs, 1.0); v2 = min(mx / gv, 1.0)
|
||||||
|
# H 유지 · S,V 만 역보정
|
||||||
|
import colorsys
|
||||||
|
h = hue(p) / 360.0
|
||||||
|
r, g, b = colorsys.hsv_to_rgb(h, s2, v2)
|
||||||
|
target[j] = [float(r * 255), float(g * 255), float(b * 255)]
|
||||||
|
json.dump({str(k): v for k, v in target.items()}, open(TGT, 'w'))
|
||||||
|
|
||||||
|
img = L(REN)
|
||||||
|
newpal = palimg.copy()
|
||||||
|
print('%3s %-10s %7s | %-16s %-16s %-16s | %-16s %6s -> %6s' %
|
||||||
|
('칸', '부위', 'px', '원본 O', '팔레트 P', '화면 S', '새 팔레트 P\'', 'dHue', 'dHue예상'))
|
||||||
|
rows = []
|
||||||
|
for j in range(G * G):
|
||||||
|
gx, gy = j % G, j // G
|
||||||
|
p = palimg[gy * 8 + 4, gx * 8 + 4].astype(np.float32)
|
||||||
|
sel = ma & (kid == j)
|
||||||
|
n = int(sel.sum())
|
||||||
|
if n < 200:
|
||||||
|
continue
|
||||||
|
part = max(((k, int((sel & v).sum())) for k, v in masks.items()), key=lambda t: t[1])[0]
|
||||||
|
o = np.array(target[j], np.float32)
|
||||||
|
s = img[sel].mean(0).astype(np.float32)
|
||||||
|
t = o * (max(lum(s), 1.0) / max(lum(o), 1.0)) # 같은 밝기에서의 「원본 색조」
|
||||||
|
gain = np.clip(t / np.maximum(s, 4.0), 0.55, 1.8)
|
||||||
|
np_ = np.clip(p * gain, 0, 255)
|
||||||
|
rows.append((j, part, n, o, p, s, t, np_))
|
||||||
|
print('%3d %-10s %7d | %-16s %-16s %-16s | %-16s %+6.1f -> %+6.1f' % (
|
||||||
|
j, part, 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(t))))
|
||||||
|
if APPLY:
|
||||||
|
newpal[gy * 8:gy * 8 + 8, gx * 8:gx * 8 + 8] = np.round(np_).astype(np.uint8)
|
||||||
|
|
||||||
|
err = [abs(dh(hue(r[3]), hue(r[5]))) for r in rows]
|
||||||
|
w = np.array([r[2] for r in rows], np.float64)
|
||||||
|
print('화면 Hue 오차: 면적가중 평균 %.1f° · 단순평균 %.1f° · 최대 %.1f°' %
|
||||||
|
((np.array(err) * w).sum() / w.sum(), np.mean(err), np.max(err)))
|
||||||
|
if APPLY:
|
||||||
|
Image.fromarray(newpal).save(PAL)
|
||||||
|
print('팔레트 갱신 →', PAL)
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
# WL-814x2 색 측정기 — Play 모드 렌더에서 「부위(팔레트 칸)별 원본 RGB → 화면 RGB → Hue 오차」
|
||||||
|
# python AgentScripts/WL814x2_color.py [변이이름 ...]
|
||||||
|
import sys, os, glob
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
RAW = 'Screenshots_WL/WL814x/2nd/raw'
|
||||||
|
PAL = 'Assets/WL/Look/Character/Textures/WLPalette_FINAL.png'
|
||||||
|
# 굽기 때 쓴 부위별 보정 계수(역산해 「원본 색」을 복원한다)
|
||||||
|
GAIN = {'M05': (0.80, 1.00), 'Skin': (0.86, 0.93), 'Hair': (0.85, 0.93)}
|
||||||
|
PARTMAP = {'Body_m05': 'M05', 'Arm_m05': 'M05', 'Leg_M05': 'M05', 'Head': 'Skin', 'Hair05': 'Hair'}
|
||||||
|
|
||||||
|
|
||||||
|
def L(n):
|
||||||
|
return np.asarray(Image.open(RAW + '/' + n + '.png').convert('RGB'), dtype=np.uint8)
|
||||||
|
|
||||||
|
|
||||||
|
def hsv1(c):
|
||||||
|
r, g, b = [x / 255.0 for x in c]
|
||||||
|
mx, mn = max(r, g, b), min(r, g, b)
|
||||||
|
d = mx - mn
|
||||||
|
if d < 1e-6:
|
||||||
|
h = 0.0
|
||||||
|
elif 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, (d / mx if mx > 1e-6 else 0.0), mx
|
||||||
|
|
||||||
|
|
||||||
|
def hsv2rgb(h, s, v):
|
||||||
|
import colorsys
|
||||||
|
r, g, b = colorsys.hsv_to_rgb((h % 360) / 360.0, min(max(s, 0), 1), min(max(v, 0), 1))
|
||||||
|
return (round(r * 255), round(g * 255), round(b * 255))
|
||||||
|
|
||||||
|
|
||||||
|
def dh(a, b):
|
||||||
|
return (b - a + 540) % 360 - 180
|
||||||
|
|
||||||
|
|
||||||
|
idm = L('idmap')
|
||||||
|
masks = {}
|
||||||
|
for f in glob.glob(RAW + '/mask_*.png'):
|
||||||
|
n = os.path.basename(f)[5:-4]
|
||||||
|
if n.endswith('10'):
|
||||||
|
continue
|
||||||
|
masks[n] = L(n and 'mask_' + n)[:, :, 0] > 127
|
||||||
|
|
||||||
|
ma = masks['all']
|
||||||
|
kid = (idm[:, :, 0].astype(np.int32) << 16) | (idm[:, :, 1].astype(np.int32) << 8) | idm[:, :, 2].astype(np.int32)
|
||||||
|
palimg = np.asarray(Image.open(PAL).convert('RGB'), dtype=np.uint8)
|
||||||
|
S = palimg.shape[0]; G = S // 8
|
||||||
|
cellcol = {}
|
||||||
|
for j in range(G * G):
|
||||||
|
gx, gy = j % G, j // G
|
||||||
|
key = ((9 + j * 15) << 16) | ((240 - j * 11) << 8) | (40 + j * 7)
|
||||||
|
cellcol[key] = (j, tuple(int(v) for v in palimg[gy * 8 + 4, gx * 8 + 4]))
|
||||||
|
|
||||||
|
# 🔴 idmap 도 포스트(그레이딩/블룸)를 거치므로 색이 그대로 나오지 않는다 → 최근접 인덱스로 되찾는다.
|
||||||
|
idref = np.array([[9 + j * 15, 240 - j * 11, 40 + j * 7] for j in range(G * G)], np.float32)
|
||||||
|
flat = idm.reshape(-1, 3).astype(np.float32)
|
||||||
|
d = ((flat[:, None, :] - idref[None, :, :]) ** 2).sum(2)
|
||||||
|
near = d.argmin(1).reshape(idm.shape[:2])
|
||||||
|
ok = (d.min(1).reshape(idm.shape[:2]) < 3600)
|
||||||
|
kid = np.where(ok, near, -1)
|
||||||
|
|
||||||
|
ids, cnt = np.unique(kid[ma], return_counts=True)
|
||||||
|
cells = []
|
||||||
|
for i, c in zip(ids, cnt):
|
||||||
|
if int(i) < 0 or c < 400:
|
||||||
|
continue
|
||||||
|
j = int(i)
|
||||||
|
gx, gy = j % G, j // G
|
||||||
|
pcol = tuple(int(v) for v in palimg[gy * 8 + 4, gx * 8 + 4])
|
||||||
|
sel = ma & (kid == i)
|
||||||
|
part = max(((k, (sel & v).sum()) for k, v in masks.items() if k not in ('all',)), key=lambda t: t[1])[0]
|
||||||
|
cells.append((j, int(c), pcol, part, sel))
|
||||||
|
cells.sort(key=lambda t: -t[1])
|
||||||
|
|
||||||
|
variants = sys.argv[1:] or sorted(set(os.path.basename(f)[:-4] for f in glob.glob(RAW + '/*.png')
|
||||||
|
if not os.path.basename(f).startswith(('mask_', 'idmap'))
|
||||||
|
and not os.path.basename(f).endswith('_g')))
|
||||||
|
print('%-24s %-10s %6s | %-16s %-16s %-16s | %6s %6s' %
|
||||||
|
('변이', '부위', 'px', '원본 RGB(역보정)', '팔레트 RGB', '화면 RGB(주색)', 'dHue', 'dHue평균'))
|
||||||
|
print('-' * 122)
|
||||||
|
for v in variants:
|
||||||
|
if not os.path.exists(RAW + '/' + v + '.png'):
|
||||||
|
continue
|
||||||
|
im = L(v)
|
||||||
|
tot = []
|
||||||
|
for j, c, pcol, part, sel in cells[:7]:
|
||||||
|
grp = PARTMAP.get(part, 'M05')
|
||||||
|
gs, gv = GAIN[grp]
|
||||||
|
ph, ps, pv = hsv1(pcol)
|
||||||
|
ocol = hsv2rgb(ph, ps / gs, pv / gv)
|
||||||
|
px = im[sel]
|
||||||
|
k = (px[:, 0].astype(np.int32) << 16) | (px[:, 1].astype(np.int32) << 8) | px[:, 2].astype(np.int32)
|
||||||
|
u, uc = np.unique(k, return_counts=True)
|
||||||
|
dom = int(u[np.argmax(uc)])
|
||||||
|
dcol = ((dom >> 16) & 255, (dom >> 8) & 255, dom & 255)
|
||||||
|
mcol = tuple(int(round(x)) for x in px.mean(0))
|
||||||
|
dhd = dh(hsv1(ocol)[0], hsv1(dcol)[0])
|
||||||
|
dhm = dh(hsv1(ocol)[0], hsv1(mcol)[0])
|
||||||
|
tot.append(abs(dhm))
|
||||||
|
print('%-24s %-10s %6d | %-16s %-16s %-16s | %+6.1f %+6.1f (평균색 %s)' %
|
||||||
|
(v, part, c, str(ocol), str(pcol), str(dcol), dhd, dhm, str(mcol)))
|
||||||
|
print('%-24s %s 부위 dHue(주색기준 아님·평균색기준) 평균 %.1f° 최대 %.1f°' % (v, ' ' * 18, np.mean(tot), np.max(tot)))
|
||||||
|
print()
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"0": [240.86021423339844, 217.10427856445312, 152.08804321289062], "1": [152.0, 115.75, 90.75], "2": [177.0, 172.0, 170.75001525878906], "3": [94.0, 52.75, 32.750003814697266], "4": [231.18280029296875, 194.49716186523438, 135.04112243652344], "5": [194.0, 152.75001525878906, 115.25001525878906], "6": [124.7311782836914, 94.37065887451172, 76.66033935546875], "7": [133.0, 84.0, 73.0], "8": [222.5806427001953, 179.5699005126953, 123.90892791748047], "9": [164.0, 50.2500114440918, 42.75000762939453], "10": [185.0, 117.5, 75.0], "11": [130.0, 120.0, 114.99999237060547], "12": [243.0107421875, 178.49461364746094, 170.9044952392578], "13": [115.0, 68.75000762939453, 43.750003814697266], "14": [199.0, 199.0, 197.74998474121094], "15": [126.0, 39.750003814697266, 36.000003814697266]}
|
||||||
|
|
@ -80,7 +80,7 @@ Material:
|
||||||
m_Colors:
|
m_Colors:
|
||||||
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
|
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
|
||||||
- _Cloud_Step: {r: 13, g: 17, b: 0, a: 0}
|
- _Cloud_Step: {r: 13, g: 17, b: 0, a: 0}
|
||||||
- _DiffuseColor: {r: 1, g: 1, b: 1, a: 1}
|
- _DiffuseColor: {r: 0.994, g: 1.024, b: 0.894, a: 1}
|
||||||
- _NormalBias: {r: 1, g: 1, b: 1, a: 0}
|
- _NormalBias: {r: 1, g: 1, b: 1, a: 0}
|
||||||
- _Outline: {r: 0, g: 0, b: 0, a: 0}
|
- _Outline: {r: 0, g: 0, b: 0, a: 0}
|
||||||
- _ShadowDiffuseColor: {r: 0.2164, g: 0.2092, b: 0.3713, a: 0}
|
- _ShadowDiffuseColor: {r: 0.2164, g: 0.2092, b: 0.3713, a: 0}
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ Material:
|
||||||
m_Colors:
|
m_Colors:
|
||||||
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
|
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
|
||||||
- _Cloud_Step: {r: 13, g: 17, b: 0, a: 0}
|
- _Cloud_Step: {r: 13, g: 17, b: 0, a: 0}
|
||||||
- _DiffuseColor: {r: 1, g: 1, b: 1, a: 1}
|
- _DiffuseColor: {r: 0.994, g: 1.024, b: 0.894, a: 1}
|
||||||
- _NormalBias: {r: 1, g: 1, b: 1, a: 0}
|
- _NormalBias: {r: 1, g: 1, b: 1, a: 0}
|
||||||
- _Outline: {r: 0, g: 0, b: 0, a: 0}
|
- _Outline: {r: 0, g: 0, b: 0, a: 0}
|
||||||
- _ShadowDiffuseColor: {r: 0.2164, g: 0.2092, b: 0.3713, a: 0}
|
- _ShadowDiffuseColor: {r: 0.2164, g: 0.2092, b: 0.3713, a: 0}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ Material:
|
||||||
m_Colors:
|
m_Colors:
|
||||||
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
|
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
|
||||||
- _Cloud_Step: {r: 13, g: 17, b: 0, a: 0}
|
- _Cloud_Step: {r: 13, g: 17, b: 0, a: 0}
|
||||||
- _DiffuseColor: {r: 1, g: 1, b: 1, a: 1}
|
- _DiffuseColor: {r: 0.994, g: 1.024, b: 0.894, a: 1}
|
||||||
- _NormalBias: {r: 1, g: 1, b: 1, a: 0}
|
- _NormalBias: {r: 1, g: 1, b: 1, a: 0}
|
||||||
- _Outline: {r: 0, g: 0, b: 0, a: 0}
|
- _Outline: {r: 0, g: 0, b: 0, a: 0}
|
||||||
- _ShadowDiffuseColor: {r: 0.2164, g: 0.2092, b: 0.3713, a: 0}
|
- _ShadowDiffuseColor: {r: 0.2164, g: 0.2092, b: 0.3713, a: 0}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue