[WL-814t] 레퍼런스 느낌(외곽선·대비·색조·림라이트) 옵션화 + 종합안 (#814)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
416228c815
commit
9b8f7e968b
|
|
@ -0,0 +1,192 @@
|
|||
// WL-814t 캡처 — 레퍼런스 느낌 4요소 단독/단계/종합 (Play 중 한 프레임 안에서 전부 렌더)
|
||||
// 한 프레임 안에서 찍는 이유: 구름 그림자(_Time)·포즈가 프레임마다 움직여 세션 간 비교가 노이즈투성이가 된다(814s 미확인⑤).
|
||||
// run_script --file AgentScripts/WL814t_Capture.cs --entry WL814t_Capture.Run
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using WL.Look.Arena;
|
||||
|
||||
public static class WL814t_Capture
|
||||
{
|
||||
const string Dir = @"E:\NerdNavis\WL_wt\gameplay\Screenshots_WL\WL814t";
|
||||
const int W = 1080, H = 1920;
|
||||
const float Close = 3.4f; // 도트A 프리셋과 같은 직교 반높이 = 비교 기준
|
||||
const float Wide = 10f; // 아레나 씬의 실제 카메라
|
||||
|
||||
static Camera s_cam;
|
||||
static Vector3 s_camPos, s_charCenter;
|
||||
static float s_camSize;
|
||||
static System.Text.StringBuilder s_log;
|
||||
|
||||
public static string Run()
|
||||
{
|
||||
s_log = new System.Text.StringBuilder();
|
||||
System.IO.Directory.CreateDirectory(Dir);
|
||||
|
||||
s_cam = Camera.main;
|
||||
if (s_cam == null) { var cs = Object.FindObjectsByType<Camera>(FindObjectsSortMode.None); if (cs.Length > 0) s_cam = cs[0]; }
|
||||
if (s_cam == null) return "no camera";
|
||||
s_camPos = s_cam.transform.position; s_camSize = s_cam.orthographicSize;
|
||||
|
||||
var pc = GameObject.Find("PC_LH_M05");
|
||||
var rs = pc != null ? pc.GetComponentsInChildren<Renderer>(false) : new Renderer[0];
|
||||
Bounds b = new Bounds(pc != null ? pc.transform.position : Vector3.zero, Vector3.zero);
|
||||
bool first = true;
|
||||
foreach (var r in rs) { if (r == null || !r.enabled) continue; if (first) { b = r.bounds; first = false; } else b.Encapsulate(r.bounds); }
|
||||
s_charCenter = b.center;
|
||||
s_log.AppendLine("charCenter=" + s_charCenter.ToString("F3") + " bodyH=" + b.size.y.ToString("F3")
|
||||
+ " · 화면 캐릭터 키 @ortho3.4 = " + (b.size.y / (2f * Close) * H).ToString("F0") + " px · @ortho10 = " + (b.size.y / (2f * Wide) * H).ToString("F0") + " px");
|
||||
|
||||
var cfg = WLReferenceLookSettings.Instance;
|
||||
if (cfg == null) return "no settings asset";
|
||||
|
||||
// ── ⓐ 지금(814s) — 아무것도 안 얹은 상태
|
||||
Shot2("a_now", Nothing(cfg), Close, 0);
|
||||
Shot2("a_now_wide", Nothing(cfg), Wide, 0);
|
||||
Shot2("a_now_pixel", Nothing(cfg), Close, 240);
|
||||
|
||||
// ── 4요소 단독
|
||||
Shot2("b_outline_only", Only(cfg, true, false, false, false), Close, 0);
|
||||
Shot2("c_contrast_only", Only(cfg, false, true, false, false), Close, 0);
|
||||
Shot2("d_mood_only", Only(cfg, false, false, true, false), Close, 0);
|
||||
Shot2("e_rim_only", Only(cfg, false, false, false, true), Close, 0);
|
||||
|
||||
// ── Ⓐ 외곽선 4단 (PD: 굵게 · 상한을 올린 2/4/6/8 px)
|
||||
float[] ws = { 2f, 4f, 6f, 8f };
|
||||
for (int i = 0; i < ws.Length; i++)
|
||||
{
|
||||
var o = Only(cfg, true, false, false, false);
|
||||
o.outlineWidth = ws[i]; o.outlineWidthBackground = Mathf.Max(1f, ws[i] * 0.45f);
|
||||
Shot2("o" + (i + 1) + "_outline_" + ws[i].ToString("F0") + "px", o, Close, 0);
|
||||
}
|
||||
// 외곽선 색 — 순검정 vs 아주 어두운 보라
|
||||
{
|
||||
var o = Only(cfg, true, false, false, false); o.outlineWidth = 6f; o.outlineWidthBackground = 2.7f;
|
||||
o.outlineColor = Color.black; Shot2("o5_color_pureblack", o, Close, 0);
|
||||
o.outlineColor = new Color(0.043f, 0.031f, 0.067f, 1f); Shot2("o6_color_darkviolet", o, Close, 0);
|
||||
}
|
||||
|
||||
// ── Ⓑ 대비 3단
|
||||
float[] mds = { 0.10f, 0.05f, 0.0f };
|
||||
for (int i = 0; i < mds.Length; i++)
|
||||
{
|
||||
var o = Only(cfg, false, true, false, false); o.minimumDarkness = mds[i]; o.charShades = 0;
|
||||
Shot2("c" + (i + 1) + "_darkness_" + mds[i].ToString("F2"), o, Close, 0);
|
||||
}
|
||||
|
||||
// ── Ⓑ2 캐릭터 색단계 7 / 4 / 3 (레퍼런스2 = 부위당 2~3단)
|
||||
int[] shades = { 0, 4, 3 };
|
||||
for (int i = 0; i < shades.Length; i++)
|
||||
{
|
||||
var o = Only(cfg, false, true, false, false); o.charShades = shades[i];
|
||||
Shot2("s" + (i + 1) + "_shades_" + (shades[i] == 0 ? 7 : shades[i]), o, Close, 0);
|
||||
}
|
||||
|
||||
// ── Ⓒ 분위기 3단
|
||||
float[] ms = { 0.35f, 0.65f, 1.0f };
|
||||
for (int i = 0; i < ms.Length; i++)
|
||||
{
|
||||
var o = Only(cfg, false, false, true, false); o.moodStrength = ms[i];
|
||||
Shot2("m" + (i + 1) + "_mood_" + ms[i].ToString("F2"), o, Close, 0);
|
||||
}
|
||||
|
||||
// ── Ⓓ 림 3단
|
||||
float[] rss = { 0.6f, 1.25f, 2.2f };
|
||||
for (int i = 0; i < rss.Length; i++)
|
||||
{
|
||||
var o = Only(cfg, false, false, false, true); o.rimStrength = rss[i];
|
||||
Shot2("r" + (i + 1) + "_rim_" + rss[i].ToString("F2"), o, Close, 0);
|
||||
}
|
||||
|
||||
// ── ⓕ 종합안 (비픽셀)
|
||||
var fin = WLReferenceLook.Opt.FromSettings(cfg);
|
||||
Shot2("f_final", fin, Close, 0);
|
||||
Shot2("f_final_wide", fin, Wide, 0);
|
||||
|
||||
// ── ⓕ2 종합안 + 도트 — 도트A 프리셋(orthoSize 3.4 · pixelHeight 240)
|
||||
var fp = fin; fp.outlineWidth = cfg.outlineWidthPixelMode; fp.outlineWidthBackground = Mathf.Max(0.5f, cfg.outlineWidthPixelMode * 0.5f);
|
||||
Shot2("f_final_pixel", fp, Close, 240);
|
||||
var fp2 = fp; fp2.outlineWidth = cfg.outlineWidthPixelMode * 2f; fp2.outlineWidthBackground = cfg.outlineWidthPixelMode;
|
||||
Shot2("f_final_pixel_w2", fp2, Close, 240);
|
||||
Shot2("f_final_pixel_thick", fin, Close, 240); // 비픽셀 두께 그대로 → 저해상도에서 뭉개지는지
|
||||
|
||||
// ── 원복 확인
|
||||
Shot2("z_restored_check", Nothing(cfg), Close, 0);
|
||||
s_cam.transform.position = s_camPos; s_cam.orthographicSize = s_camSize;
|
||||
|
||||
s_log.AppendLine("DONE");
|
||||
return s_log.ToString();
|
||||
}
|
||||
|
||||
static WLReferenceLook.Opt Nothing(WLReferenceLookSettings cfg)
|
||||
{
|
||||
var o = WLReferenceLook.Opt.FromSettings(cfg);
|
||||
o.outline = false; o.contrast = false; o.mood = false; o.rim = false;
|
||||
return o;
|
||||
}
|
||||
|
||||
static WLReferenceLook.Opt Only(WLReferenceLookSettings cfg, bool a, bool b, bool c, bool d)
|
||||
{
|
||||
var o = WLReferenceLook.Opt.FromSettings(cfg);
|
||||
o.outline = a; o.contrast = b; o.mood = c; o.rim = d;
|
||||
return o;
|
||||
}
|
||||
|
||||
/// <summary>카메라를 먼저 맞춘 뒤(줌 비례 두께가 카메라를 읽는다) 적용 → 렌더.</summary>
|
||||
static void Shot2(string name, WLReferenceLook.Opt o, float orthoSize, int pixelHeight)
|
||||
{
|
||||
s_cam.orthographicSize = orthoSize;
|
||||
s_cam.transform.position = s_charCenter - s_cam.transform.forward * 30f;
|
||||
WLReferenceLook.Apply(o, WLReferenceLookSettings.Instance);
|
||||
Shot(name, pixelHeight);
|
||||
}
|
||||
|
||||
/// <summary>한 장 렌더. pixelHeight 0 = 1080×1920 그대로, >0 = 저해상도 RT 로 찍어 점보간 확대(도트).</summary>
|
||||
static void Shot(string name, int pixelHeight)
|
||||
{
|
||||
int rw = W, rh = H;
|
||||
if (pixelHeight > 0) { rh = pixelHeight; rw = Mathf.RoundToInt(pixelHeight * (float)W / H); }
|
||||
|
||||
var rt = new RenderTexture(rw, rh, 24, RenderTextureFormat.ARGB32);
|
||||
rt.antiAliasing = 1;
|
||||
rt.filterMode = pixelHeight > 0 ? FilterMode.Point : FilterMode.Bilinear;
|
||||
rt.Create();
|
||||
|
||||
var prevTarget = s_cam.targetTexture;
|
||||
s_cam.targetTexture = rt;
|
||||
s_cam.Render();
|
||||
s_cam.targetTexture = prevTarget;
|
||||
|
||||
var prevActive = RenderTexture.active;
|
||||
RenderTexture.active = rt;
|
||||
var src = new Texture2D(rw, rh, TextureFormat.RGB24, false);
|
||||
src.ReadPixels(new Rect(0, 0, rw, rh), 0, 0);
|
||||
src.Apply();
|
||||
RenderTexture.active = prevActive;
|
||||
|
||||
Texture2D outTex = src;
|
||||
if (pixelHeight > 0)
|
||||
{
|
||||
outTex = new Texture2D(W, H, TextureFormat.RGB24, false);
|
||||
var sp = src.GetPixels32();
|
||||
var dp = new Color32[W * H];
|
||||
for (int y = 0; y < H; y++)
|
||||
{
|
||||
int sy = Mathf.Min(rh - 1, y * rh / H);
|
||||
for (int x = 0; x < W; x++)
|
||||
{
|
||||
int sx = Mathf.Min(rw - 1, x * rw / W);
|
||||
dp[y * W + x] = sp[sy * rw + sx];
|
||||
}
|
||||
}
|
||||
outTex.SetPixels32(dp); outTex.Apply();
|
||||
}
|
||||
|
||||
System.IO.File.WriteAllBytes(System.IO.Path.Combine(Dir, name + ".png"), outTex.EncodeToPNG());
|
||||
s_log.AppendLine("shot " + name + " " + rw + "x" + rh + " · ortho " + s_cam.orthographicSize.ToString("F2") + " · " + WLReferenceLook.LastLog);
|
||||
|
||||
if (outTex != src) Object.Destroy(outTex);
|
||||
Object.Destroy(src);
|
||||
rt.Release(); Object.Destroy(rt);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 269ec43cd1eb2794e9f12763ccdb9af5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-7716237022740010770
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 558a8e2b6826cf840aae193990ba9f2e, type: 3}
|
||||
m_Name: ShadowsMidtonesHighlights
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.ShadowsMidtonesHighlights
|
||||
active: 1
|
||||
shadows:
|
||||
m_OverrideState: 1
|
||||
m_Value: {x: 0.78, y: 0.58, z: 1.25, w: -0.06}
|
||||
midtones:
|
||||
m_OverrideState: 1
|
||||
m_Value: {x: 1, y: 0.96, z: 1.05, w: 0}
|
||||
highlights:
|
||||
m_OverrideState: 1
|
||||
m_Value: {x: 1.14, y: 1.02, z: 0.74, w: 0.03}
|
||||
shadowsStart:
|
||||
m_OverrideState: 0
|
||||
m_Value: 0
|
||||
shadowsEnd:
|
||||
m_OverrideState: 0
|
||||
m_Value: 0.3
|
||||
highlightsStart:
|
||||
m_OverrideState: 0
|
||||
m_Value: 0.55
|
||||
highlightsEnd:
|
||||
m_OverrideState: 0
|
||||
m_Value: 1
|
||||
--- !u!114 &-6414152611911942192
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0b2db86121404754db890f4c8dfe81b2, type: 3}
|
||||
m_Name: Bloom
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.Bloom
|
||||
active: 1
|
||||
skipIterations:
|
||||
m_OverrideState: 0
|
||||
m_Value: 1
|
||||
threshold:
|
||||
m_OverrideState: 1
|
||||
m_Value: 1.05
|
||||
intensity:
|
||||
m_OverrideState: 1
|
||||
m_Value: 0.55
|
||||
scatter:
|
||||
m_OverrideState: 1
|
||||
m_Value: 0.6
|
||||
clamp:
|
||||
m_OverrideState: 0
|
||||
m_Value: 65472
|
||||
tint:
|
||||
m_OverrideState: 1
|
||||
m_Value: {r: 1, g: 0.86, b: 0.6, a: 1}
|
||||
highQualityFiltering:
|
||||
m_OverrideState: 0
|
||||
m_Value: 0
|
||||
filter:
|
||||
m_OverrideState: 0
|
||||
m_Value: 0
|
||||
downscale:
|
||||
m_OverrideState: 0
|
||||
m_Value: 0
|
||||
maxIterations:
|
||||
m_OverrideState: 0
|
||||
m_Value: 6
|
||||
dirtTexture:
|
||||
m_OverrideState: 0
|
||||
m_Value: {fileID: 0}
|
||||
dimension: 1
|
||||
dirtIntensity:
|
||||
m_OverrideState: 0
|
||||
m_Value: 0
|
||||
--- !u!114 &-2512526436937386102
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 221518ef91623a7438a71fef23660601, type: 3}
|
||||
m_Name: WhiteBalance
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.WhiteBalance
|
||||
active: 1
|
||||
temperature:
|
||||
m_OverrideState: 1
|
||||
m_Value: -22
|
||||
tint:
|
||||
m_OverrideState: 1
|
||||
m_Value: 26
|
||||
--- !u!114 &-875615482179838212
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 899c54efeace73346a0a16faa3afe726, type: 3}
|
||||
m_Name: Vignette
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.Vignette
|
||||
active: 1
|
||||
color:
|
||||
m_OverrideState: 1
|
||||
m_Value: {r: 0.09, g: 0.03, b: 0.14, a: 1}
|
||||
center:
|
||||
m_OverrideState: 0
|
||||
m_Value: {x: 0.5, y: 0.5}
|
||||
intensity:
|
||||
m_OverrideState: 1
|
||||
m_Value: 0.28
|
||||
smoothness:
|
||||
m_OverrideState: 1
|
||||
m_Value: 0.6
|
||||
rounded:
|
||||
m_OverrideState: 0
|
||||
m_Value: 0
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d7fd9488000d3734a9e00ee676215985, type: 3}
|
||||
m_Name: WL_ArenaMood
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Core.Runtime::UnityEngine.Rendering.VolumeProfile
|
||||
components:
|
||||
- {fileID: 1813646129596036978}
|
||||
- {fileID: -2512526436937386102}
|
||||
- {fileID: -7716237022740010770}
|
||||
- {fileID: -6414152611911942192}
|
||||
- {fileID: -875615482179838212}
|
||||
--- !u!114 &1813646129596036978
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 66f335fb1ffd8684294ad653bf1c7564, type: 3}
|
||||
m_Name: ColorAdjustments
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.ColorAdjustments
|
||||
active: 1
|
||||
postExposure:
|
||||
m_OverrideState: 1
|
||||
m_Value: 0.05
|
||||
contrast:
|
||||
m_OverrideState: 1
|
||||
m_Value: 30
|
||||
colorFilter:
|
||||
m_OverrideState: 1
|
||||
m_Value: {r: 0.95, g: 0.8, b: 1.08, a: 1}
|
||||
hueShift:
|
||||
m_OverrideState: 0
|
||||
m_Value: 0
|
||||
saturation:
|
||||
m_OverrideState: 1
|
||||
m_Value: 12
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 534beeaf638a948459a24ec36906d7f6
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7b6cfdda832d05f478e86a49991bde8f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5e200478fef3667448d80f66592afd9a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: af081c9d37b39dd40aa4c58f0397f23c, type: 3}
|
||||
m_Name: WLReferenceLookSettings
|
||||
m_EditorClassIdentifier: Assembly-CSharp::WL.Look.Arena.WLReferenceLookSettings
|
||||
enabled_: 1
|
||||
verboseLog: 0
|
||||
outlineOn: 1
|
||||
outlineWidth: 6
|
||||
outlineWidthPixelMode: 1
|
||||
outlineColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
outlineOnBackground: 1
|
||||
outlineWidthBackground: 2.5
|
||||
outlineScaleWithZoom: 1
|
||||
outlineRefOrthoSize: 3.4
|
||||
outlineDepthBias: 0.06
|
||||
contrastOn: 1
|
||||
minimumDarkness: 0.12
|
||||
shadowColorScale: 0.85
|
||||
shadowTintAmount: 0.3
|
||||
shadowTint: {r: 0.243, g: 0.106, b: 0.353, a: 1}
|
||||
charShades: 0
|
||||
moodOn: 1
|
||||
moodStrength: 0.55
|
||||
profile:
|
||||
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}
|
||||
rimOn: 1
|
||||
rimStrength: 1.6
|
||||
rimPower: 3
|
||||
rimUpBias: 0.35
|
||||
rimColor: {r: 1, g: 0.82, b: 0.5, a: 1}
|
||||
rimOnBackground: 0
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f662bae23dfde7b42a3b5966d68fbcf0
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1098,6 +1098,52 @@ Transform:
|
|||
type: 3}
|
||||
m_PrefabInstance: {fileID: 206788312}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1 &259214373
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 259214375}
|
||||
- component: {fileID: 259214374}
|
||||
m_Layer: 0
|
||||
m_Name: WL_ReferenceLook
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &259214374
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 259214373}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4fd112583fc74eb40bbc4ba98ea96331, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::WL.Look.Arena.WLReferenceLook
|
||||
characterRoot: {fileID: 711662063}
|
||||
characterRootName: PC_LH_M05
|
||||
--- !u!4 &259214375
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 259214373}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1001 &266873449
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
@ -14773,3 +14819,4 @@ SceneRoots:
|
|||
- {fileID: 918041734}
|
||||
- {fileID: 1166334010}
|
||||
- {fileID: 2042308456}
|
||||
- {fileID: 259214375}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,343 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// WLReferenceLook.cs — 레퍼런스 느낌 4요소를 씬에 얹는다 (WL-814t · #814)
|
||||
//
|
||||
// Ⓐ 외곽선(inverted hull · 화면 픽셀 두께) Ⓑ 명암 대비(Toon 램프) Ⓒ 색조·분위기(Volume+라이트)
|
||||
// Ⓓ 림라이트(가산 패스)
|
||||
//
|
||||
// ■ 원칙
|
||||
// · 에셋(머티리얼·그래프·프리팹·원본 텍스처)을 **한 글자도 안 바꾼다**.
|
||||
// 전부 런타임 인스턴스(머티리얼 복제 · 머티리얼 배열 뒤에 덧붙이기 · 임시 Volume)로만 한다.
|
||||
// · `enabled_ = 0` 이면 아무 것도 하지 않는다 = WL-814s 상태 100 %(C8).
|
||||
// · 되돌릴 수 있게 바꾸기 전 값을 전부 저장한다(OnDisable 에서 복구).
|
||||
//
|
||||
// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다(813z 규칙).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WL.Look.Arena
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class WLReferenceLook : MonoBehaviour
|
||||
{
|
||||
// ── 진단(프로브가 읽는다)
|
||||
public static int OutlinedRenderers, RimRenderers, ContrastMaterials;
|
||||
public static string LastLog = "";
|
||||
public static bool IsApplied { get { return s_applied; } }
|
||||
|
||||
[Tooltip("캐릭터 루트(비우면 이름으로 찾는다).")]
|
||||
public GameObject characterRoot;
|
||||
|
||||
[Tooltip("캐릭터 루트를 못 찾을 때 쓰는 이름.")]
|
||||
public string characterRootName = "PC_LH_M05";
|
||||
|
||||
/// <summary>요소별 on/off + 세기. 캡처 스크립트가 단독 요소 비교에 쓴다.</summary>
|
||||
public struct Opt
|
||||
{
|
||||
public bool outline, contrast, mood, rim;
|
||||
public float outlineWidth, outlineWidthBackground;
|
||||
public Color outlineColor;
|
||||
public bool outlineOnBackground;
|
||||
public bool outlineScaleWithZoom;
|
||||
public float outlineRefOrthoSize, outlineDepthBias;
|
||||
public float minimumDarkness, shadowColorScale, shadowTintAmount;
|
||||
public Color shadowTint;
|
||||
public int charShades;
|
||||
public float moodStrength;
|
||||
public float rimStrength, rimPower, rimUpBias;
|
||||
public Color rimColor;
|
||||
public bool rimOnBackground;
|
||||
|
||||
public static Opt FromSettings(WLReferenceLookSettings c)
|
||||
{
|
||||
var o = new Opt();
|
||||
if (c == null) return o;
|
||||
o.outline = c.outlineOn; o.contrast = c.contrastOn; o.mood = c.moodOn; o.rim = c.rimOn;
|
||||
o.outlineWidth = c.outlineWidth; o.outlineWidthBackground = c.outlineWidthBackground;
|
||||
o.outlineColor = c.outlineColor; o.outlineOnBackground = c.outlineOnBackground;
|
||||
o.outlineScaleWithZoom = c.outlineScaleWithZoom; o.outlineRefOrthoSize = c.outlineRefOrthoSize;
|
||||
o.outlineDepthBias = c.outlineDepthBias;
|
||||
o.minimumDarkness = c.minimumDarkness; o.shadowColorScale = c.shadowColorScale;
|
||||
o.shadowTintAmount = c.shadowTintAmount; o.shadowTint = c.shadowTint;
|
||||
o.charShades = c.charShades;
|
||||
o.moodStrength = c.moodStrength;
|
||||
o.rimStrength = c.rimStrength; o.rimPower = c.rimPower; o.rimUpBias = c.rimUpBias;
|
||||
o.rimColor = c.rimColor; o.rimOnBackground = c.rimOnBackground;
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 저장된 원래 상태 ────────────────────────────────────────────────
|
||||
struct RendState { public Renderer r; public Material[] mats; }
|
||||
struct LightState { public Light l; public Color c; public float i; }
|
||||
|
||||
static readonly List<RendState> s_rends = new List<RendState>(200);
|
||||
static readonly List<LightState> s_lights = new List<LightState>(8);
|
||||
static readonly List<Material> s_created = new List<Material>(32);
|
||||
static GameObject s_volumeGo;
|
||||
static bool s_applied;
|
||||
static Color s_ambient; static AmbientMode s_ambMode; static float s_ambIntensity;
|
||||
static Terrain s_terrain; static Material s_terrainMat;
|
||||
static Camera s_cam; static bool s_camPost; static bool s_camPostSaved;
|
||||
static readonly Dictionary<Material, Material> s_clones = new Dictionary<Material, Material>();
|
||||
|
||||
const string kToon = "Shader Graphs/Toon";
|
||||
const string kToonTerrain = "Shader Graphs/ToonTerrain";
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
var cfg = WLReferenceLookSettings.Instance;
|
||||
if (cfg == null || cfg.enabled_ == 0) { LastLog = "enabled_=0 → 적용 0(814s 상태)"; return; }
|
||||
Apply(Opt.FromSettings(cfg), cfg);
|
||||
}
|
||||
|
||||
void OnDisable() { Restore(); }
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
/// <summary>4요소를 지정한 세기로 얹는다. 이미 얹혀 있으면 먼저 되돌린다(멱등).</summary>
|
||||
public static void Apply(Opt o, WLReferenceLookSettings cfg)
|
||||
{
|
||||
Restore();
|
||||
if (cfg == null) cfg = WLReferenceLookSettings.Instance;
|
||||
|
||||
var charRoot = FindCharacterRoot();
|
||||
var rends = Object.FindObjectsByType<Renderer>(FindObjectsSortMode.None);
|
||||
|
||||
OutlinedRenderers = 0; RimRenderers = 0; ContrastMaterials = 0;
|
||||
|
||||
Material outlineChar = null, outlineBg = null, rimMat = null;
|
||||
if (o.outline)
|
||||
{
|
||||
var sh = Shader.Find("WL/HullOutline");
|
||||
if (sh != null)
|
||||
{
|
||||
// 줌 비례 — 직교 크기가 커지면(멀리서 보면) 화면 픽셀 두께를 같은 비율로 줄인다.
|
||||
// 그래야 「캐릭터 키 대비 굵기」가 프레이밍과 무관하게 일정하다(2D 스프라이트처럼).
|
||||
float zoom = 1f;
|
||||
var c0 = Camera.main;
|
||||
if (c0 == null) { var cs0 = Object.FindObjectsByType<Camera>(FindObjectsSortMode.None); if (cs0.Length > 0) c0 = cs0[0]; }
|
||||
if (o.outlineScaleWithZoom && c0 != null && c0.orthographic && c0.orthographicSize > 0.001f && o.outlineRefOrthoSize > 0.001f)
|
||||
zoom = o.outlineRefOrthoSize / c0.orthographicSize;
|
||||
|
||||
outlineChar = Make(sh); outlineChar.SetColor("_OutlineColor", o.outlineColor);
|
||||
outlineChar.SetFloat("_OutlineWidth", o.outlineWidth * zoom);
|
||||
outlineChar.SetFloat("_OutlineDepthBias", o.outlineDepthBias);
|
||||
outlineBg = Make(sh); outlineBg.SetColor("_OutlineColor", o.outlineColor);
|
||||
outlineBg.SetFloat("_OutlineWidth", o.outlineWidthBackground * zoom);
|
||||
outlineBg.SetFloat("_OutlineDepthBias", o.outlineDepthBias);
|
||||
}
|
||||
}
|
||||
if (o.rim)
|
||||
{
|
||||
var sh = Shader.Find("WL/RimAdd");
|
||||
if (sh != null)
|
||||
{
|
||||
rimMat = Make(sh);
|
||||
rimMat.SetColor("_RimColor", o.rimColor);
|
||||
rimMat.SetFloat("_RimPower", o.rimPower);
|
||||
rimMat.SetFloat("_RimStrength", o.rimStrength);
|
||||
rimMat.SetFloat("_RimUpBias", o.rimUpBias);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < rends.Length; i++)
|
||||
{
|
||||
var r = rends[i];
|
||||
if (r == null || r is ParticleSystemRenderer) continue;
|
||||
var mats = r.sharedMaterials;
|
||||
if (mats == null || mats.Length == 0 || mats[0] == null) continue;
|
||||
bool isChar = charRoot != null && r.transform.IsChildOf(charRoot.transform);
|
||||
|
||||
// Toon 계열 불투명만 대상(얼굴 Unlit/Transparent · 물은 제외)
|
||||
bool toon = false;
|
||||
for (int m = 0; m < mats.Length; m++)
|
||||
if (mats[m] != null && mats[m].shader != null && mats[m].shader.name == kToon) toon = true;
|
||||
if (!toon) continue;
|
||||
|
||||
var newMats = mats;
|
||||
bool touched = false;
|
||||
|
||||
// Ⓑ 대비 — 머티리얼 복제본으로 교체
|
||||
if (o.contrast)
|
||||
{
|
||||
newMats = (Material[])mats.Clone();
|
||||
for (int m = 0; m < newMats.Length; m++)
|
||||
{
|
||||
if (newMats[m] == null || newMats[m].shader == null || newMats[m].shader.name != kToon) continue;
|
||||
newMats[m] = CloneForContrast(newMats[m], o, isChar);
|
||||
touched = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Ⓐ 외곽선 · Ⓓ 림 — 머티리얼 배열 뒤에 덧붙인다(서브메시 1개면 한 번 더 그린다)
|
||||
var extra = new List<Material>(2);
|
||||
if (o.outline && (isChar || o.outlineOnBackground)) extra.Add(isChar ? outlineChar : outlineBg);
|
||||
if (o.rim && rimMat != null && (isChar || o.rimOnBackground)) extra.Add(rimMat);
|
||||
if (extra.Count > 0 && extra[0] != null)
|
||||
{
|
||||
if (!touched) newMats = (Material[])mats.Clone();
|
||||
var list = new List<Material>(newMats);
|
||||
for (int e = 0; e < extra.Count; e++) if (extra[e] != null) list.Add(extra[e]);
|
||||
newMats = list.ToArray();
|
||||
touched = true;
|
||||
if (o.outline && (isChar || o.outlineOnBackground)) OutlinedRenderers++;
|
||||
if (o.rim && rimMat != null && (isChar || o.rimOnBackground)) RimRenderers++;
|
||||
}
|
||||
|
||||
if (touched)
|
||||
{
|
||||
s_rends.Add(new RendState { r = r, mats = mats });
|
||||
r.sharedMaterials = newMats;
|
||||
}
|
||||
}
|
||||
|
||||
// Ⓑ — 터레인(화면 면적의 대부분)
|
||||
if (o.contrast)
|
||||
{
|
||||
var t = Terrain.activeTerrain;
|
||||
if (t != null && t.materialTemplate != null && t.materialTemplate.shader != null
|
||||
&& t.materialTemplate.shader.name == kToonTerrain)
|
||||
{
|
||||
s_terrain = t; s_terrainMat = t.materialTemplate;
|
||||
t.materialTemplate = CloneForContrast(t.materialTemplate, o, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Ⓒ — 라이트 · 앰비언트 · Volume
|
||||
if (o.mood && o.moodStrength > 0f)
|
||||
{
|
||||
float k = Mathf.Clamp01(o.moodStrength);
|
||||
s_ambMode = RenderSettings.ambientMode;
|
||||
s_ambient = RenderSettings.ambientLight;
|
||||
s_ambIntensity = RenderSettings.ambientIntensity;
|
||||
|
||||
var lights = Object.FindObjectsByType<Light>(FindObjectsSortMode.None);
|
||||
for (int i = 0; i < lights.Length; i++)
|
||||
{
|
||||
var l = lights[i];
|
||||
if (l == null) continue;
|
||||
s_lights.Add(new LightState { l = l, c = l.color, i = l.intensity });
|
||||
if (l.type == LightType.Directional)
|
||||
{
|
||||
l.color = Color.Lerp(l.color, cfg != null ? cfg.keyLightColor : new Color(1f, .76f, .42f), k);
|
||||
l.intensity = Mathf.Lerp(l.intensity, cfg != null ? cfg.keyLightIntensity : 1.15f, k);
|
||||
}
|
||||
}
|
||||
|
||||
RenderSettings.ambientMode = AmbientMode.Flat;
|
||||
RenderSettings.ambientLight = Color.Lerp(
|
||||
s_ambMode == AmbientMode.Flat ? s_ambient : RenderSettings.ambientSkyColor,
|
||||
cfg != null ? cfg.ambientColor : new Color(.259f, .165f, .373f), k);
|
||||
|
||||
if (cfg != null && cfg.profile != null && cfg.profile.asset != null)
|
||||
{
|
||||
s_volumeGo = new GameObject("WL814t_ReferenceLook_Volume");
|
||||
s_volumeGo.hideFlags = HideFlags.DontSave;
|
||||
var v = s_volumeGo.AddComponent<Volume>();
|
||||
v.isGlobal = true; v.priority = 100f;
|
||||
v.sharedProfile = cfg.profile.asset;
|
||||
v.weight = k;
|
||||
|
||||
s_cam = Camera.main;
|
||||
if (s_cam == null) { var cs = Object.FindObjectsByType<Camera>(FindObjectsSortMode.None); if (cs.Length > 0) s_cam = cs[0]; }
|
||||
if (s_cam != null)
|
||||
{
|
||||
var ac = s_cam.GetComponent<UnityEngine.Rendering.Universal.UniversalAdditionalCameraData>();
|
||||
if (ac != null) { s_camPost = ac.renderPostProcessing; s_camPostSaved = true; ac.renderPostProcessing = true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s_applied = true;
|
||||
LastLog = "Ⓐ" + (o.outline ? OutlinedRenderers.ToString() : "off")
|
||||
+ " Ⓑ" + (o.contrast ? ContrastMaterials + "mat/md" + o.minimumDarkness.ToString("F2") : "off")
|
||||
+ " Ⓒ" + (o.mood ? o.moodStrength.ToString("F2") : "off")
|
||||
+ " Ⓓ" + (o.rim ? RimRenderers.ToString() : "off");
|
||||
if (cfg != null && cfg.verboseLog != 0) Debug.Log("[WL814t ReferenceLook] " + LastLog);
|
||||
}
|
||||
|
||||
/// <summary>바꾼 것을 전부 원래대로. 몇 번 불러도 안전.</summary>
|
||||
public static void Restore()
|
||||
{
|
||||
for (int i = 0; i < s_rends.Count; i++)
|
||||
if (s_rends[i].r != null) s_rends[i].r.sharedMaterials = s_rends[i].mats;
|
||||
s_rends.Clear();
|
||||
|
||||
if (s_terrain != null && s_terrainMat != null) s_terrain.materialTemplate = s_terrainMat;
|
||||
s_terrain = null; s_terrainMat = null;
|
||||
|
||||
for (int i = 0; i < s_lights.Count; i++)
|
||||
if (s_lights[i].l != null) { s_lights[i].l.color = s_lights[i].c; s_lights[i].l.intensity = s_lights[i].i; }
|
||||
if (s_lights.Count > 0)
|
||||
{
|
||||
RenderSettings.ambientMode = s_ambMode;
|
||||
RenderSettings.ambientLight = s_ambient;
|
||||
RenderSettings.ambientIntensity = s_ambIntensity;
|
||||
}
|
||||
s_lights.Clear();
|
||||
|
||||
if (s_volumeGo != null) { DestroyAny(s_volumeGo); s_volumeGo = null; }
|
||||
if (s_camPostSaved && s_cam != null)
|
||||
{
|
||||
var ac = s_cam.GetComponent<UnityEngine.Rendering.Universal.UniversalAdditionalCameraData>();
|
||||
if (ac != null) ac.renderPostProcessing = s_camPost;
|
||||
}
|
||||
s_camPostSaved = false; s_cam = null;
|
||||
|
||||
for (int i = 0; i < s_created.Count; i++) if (s_created[i] != null) DestroyAny(s_created[i]);
|
||||
s_created.Clear();
|
||||
s_clones.Clear();
|
||||
OutlinedRenderers = 0; RimRenderers = 0; ContrastMaterials = 0;
|
||||
if (s_applied) LastLog = "restored(814s 상태)";
|
||||
s_applied = false;
|
||||
}
|
||||
|
||||
// ── helpers ──────────────────────────────────────────────────────
|
||||
static GameObject FindCharacterRoot()
|
||||
{
|
||||
var go = GameObject.Find("PC_LH_M05");
|
||||
if (go != null) return go;
|
||||
var anim = Object.FindFirstObjectByType<Animator>();
|
||||
return anim != null ? anim.gameObject : null;
|
||||
}
|
||||
|
||||
static Material Make(Shader sh)
|
||||
{
|
||||
var m = new Material(sh);
|
||||
m.hideFlags = HideFlags.HideAndDontSave;
|
||||
s_created.Add(m);
|
||||
return m;
|
||||
}
|
||||
|
||||
static Material CloneForContrast(Material src, Opt o, bool isChar)
|
||||
{
|
||||
Material c;
|
||||
// 캐릭터/배경이 같은 머티리얼을 공유하는 경우가 없어 키 하나로 충분하다
|
||||
if (s_clones.TryGetValue(src, out c) && c != null) return c;
|
||||
c = new Material(src);
|
||||
c.hideFlags = HideFlags.HideAndDontSave;
|
||||
c.name = src.name + "_814t";
|
||||
if (c.HasProperty("_MinimumDarkness")) c.SetFloat("_MinimumDarkness", o.minimumDarkness);
|
||||
if (c.HasProperty("_ShadowDiffuseColor"))
|
||||
{
|
||||
var s = c.GetColor("_ShadowDiffuseColor");
|
||||
s = new Color(s.r * o.shadowColorScale, s.g * o.shadowColorScale, s.b * o.shadowColorScale, s.a);
|
||||
if (o.shadowTintAmount > 0f) s = Color.Lerp(s, new Color(s.grayscale, s.grayscale, s.grayscale, s.a) * o.shadowTint, o.shadowTintAmount);
|
||||
c.SetColor("_ShadowDiffuseColor", s);
|
||||
}
|
||||
if (isChar && o.charShades > 0 && c.HasProperty("_Shades")) c.SetFloat("_Shades", o.charShades);
|
||||
s_clones[src] = c;
|
||||
s_created.Add(c);
|
||||
ContrastMaterials++;
|
||||
return c;
|
||||
}
|
||||
|
||||
static void DestroyAny(Object o)
|
||||
{
|
||||
if (o == null) return;
|
||||
if (Application.isPlaying) Destroy(o); else DestroyImmediate(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4fd112583fc74eb40bbc4ba98ea96331
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// WLReferenceLookSettings.cs — 「레퍼런스 느낌」 4요소의 값 단일 출처 (WL-814t · #814)
|
||||
//
|
||||
// PD 지시(2026-09-12) 「레퍼런스 게임 같은 느낌을 살릴 수 없을까?」 · 발주서 WL-814t §2-3.
|
||||
// 레퍼런스 특징 = ⓐ 굵은 검은 외곽선 ⓑ 매우 어두운 그림자(강한 대비)
|
||||
// ⓒ 보라~자홍 배경 + 금색/주황 조명 ⓓ 윤곽의 밝은 테(림라이트)
|
||||
//
|
||||
// ■ C45 — 값은 코드 상수가 아니라 이 에셋(`Resources/WL/WLReferenceLookSettings.asset`)
|
||||
// ■ C8 — `enabled_ = 0` 이면 `WLReferenceLook` 이 **아무것도 하지 않는다** = 814s 상태 100 %
|
||||
//
|
||||
// 🔴 어셈블리 주의: Assets/WL/Look/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WL.Look.Arena
|
||||
{
|
||||
[CreateAssetMenu(fileName = "WLReferenceLookSettings", menuName = "WL/Reference Look Settings", order = 37)]
|
||||
public sealed class WLReferenceLookSettings : ScriptableObject
|
||||
{
|
||||
public const string ResourcesPath = "WL/WLReferenceLookSettings";
|
||||
|
||||
private static WLReferenceLookSettings s_instance;
|
||||
private static bool s_tried;
|
||||
|
||||
/// <summary>에셋이 없으면 null — 호출부는 전부 건너뛴다(C8 롤백).</summary>
|
||||
public static WLReferenceLookSettings Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_instance == null && !s_tried)
|
||||
{
|
||||
s_tried = true;
|
||||
s_instance = Resources.Load<WLReferenceLookSettings>(ResourcesPath);
|
||||
}
|
||||
return s_instance;
|
||||
}
|
||||
}
|
||||
|
||||
[Header("마스터 (C8)")]
|
||||
[Tooltip("0 이면 이 기능 전체가 꺼진다 = WL-814s 상태 100 %.")]
|
||||
public int enabled_ = 1;
|
||||
|
||||
[Tooltip("1 이면 적용 결과를 콘솔에 1줄 남긴다.")]
|
||||
public int verboseLog = 0;
|
||||
|
||||
// ── Ⓐ 외곽선 ────────────────────────────────────────────────────────
|
||||
[Header("Ⓐ 외곽선 (inverted hull · 두께 단위 = 화면 픽셀 @1080×1920)")]
|
||||
public bool outlineOn = true;
|
||||
|
||||
[Tooltip("캐릭터 외곽선 두께(px @직교 3.4). PD 지시 = 굵게. 레퍼런스2 비율 = 캐릭터 키의 1/80~1/60 ≈ 4.3~5.7 px.")]
|
||||
public float outlineWidth = 6.0f;
|
||||
|
||||
[Tooltip("도트(픽셀) 모드에서 쓰는 캐릭터 외곽선 두께(px · 저해상도 RT 기준).")]
|
||||
public float outlineWidthPixelMode = 1.0f;
|
||||
|
||||
[Tooltip("외곽선 색. 순검정보다 아주 어두운 중성/보라가 레퍼런스에 가깝다.")]
|
||||
public Color outlineColor = new Color(0.043f, 0.031f, 0.067f, 1f);
|
||||
|
||||
[Tooltip("배경(바위·나무·건물)에도 외곽선을 넣을지.")]
|
||||
public bool outlineOnBackground = true;
|
||||
|
||||
[Tooltip("배경 외곽선 두께(px). 저폴리 하드에지라 캐릭터보다 얇게 잡는다.")]
|
||||
public float outlineWidthBackground = 2.5f;
|
||||
|
||||
[Tooltip("켜면 두께를 카메라 줌(직교 크기)에 맞춰 비례시킨다 = 캐릭터 대비 굵기가 항상 같다(2D 스프라이트처럼).")]
|
||||
public bool outlineScaleWithZoom = true;
|
||||
|
||||
[Tooltip("위 비례의 기준 직교 반높이. 이 크기일 때 두께가 위 px 값 그대로다.")]
|
||||
public float outlineRefOrthoSize = 3.4f;
|
||||
|
||||
[Tooltip("껍데기를 카메라에서 뒤로 미는 양(m). 저폴리 하드에지의 실루엣 안쪽 얼룩을 없앤다.")]
|
||||
public float outlineDepthBias = 0.06f;
|
||||
|
||||
// ── Ⓑ 명암 대비 ─────────────────────────────────────────────────────
|
||||
[Header("Ⓑ 명암 대비 (Toon 램프)")]
|
||||
public bool contrastOn = true;
|
||||
|
||||
[Tooltip("`_MinimumDarkness` — 그림자 바닥 밝기. 기본 0.2 · 낮을수록 어둡다.")]
|
||||
[Range(0f, 0.5f)] public float minimumDarkness = 0.08f;
|
||||
|
||||
[Tooltip("`_ShadowDiffuseColor` 에 곱하는 계수. 1 미만이면 그림자가 더 진해진다.")]
|
||||
[Range(0.2f, 1.5f)] public float shadowColorScale = 0.72f;
|
||||
|
||||
[Tooltip("그림자에 섞을 색조(보라). 0 이면 안 섞는다.")]
|
||||
[Range(0f, 1f)] public float shadowTintAmount = 0.35f;
|
||||
public Color shadowTint = new Color(0.243f, 0.106f, 0.353f, 1f);
|
||||
|
||||
[Tooltip("캐릭터 `_Shades`(색 단계 수). 0 이면 원래 값(배경과 동일 7) 유지. 레퍼런스2 = 부위당 2~3단.")]
|
||||
public int charShades = 0;
|
||||
|
||||
// ── Ⓒ 색조·분위기 ───────────────────────────────────────────────────
|
||||
[Header("Ⓒ 색조·분위기 (Volume + 라이트)")]
|
||||
public bool moodOn = true;
|
||||
|
||||
[Tooltip("0=끔 · 0.5=은은 · 1=강함. Volume weight 와 라이트 보간에 함께 쓴다.")]
|
||||
[Range(0f, 1f)] public float moodStrength = 0.75f;
|
||||
|
||||
[Tooltip("씬에 붙일 포스트 프로파일(없으면 Ⓒ 는 라이트만 바꾼다).")]
|
||||
public VolumeProfileRef profile = new VolumeProfileRef();
|
||||
|
||||
[Tooltip("키라이트(Directional) 목표 색 — 레퍼런스의 금색/주황.")]
|
||||
public Color keyLightColor = new Color(1f, 0.76f, 0.42f, 1f);
|
||||
|
||||
[Tooltip("키라이트 목표 세기.")]
|
||||
public float keyLightIntensity = 1.15f;
|
||||
|
||||
[Tooltip("앰비언트 목표 색 — 레퍼런스의 보라~자홍.")]
|
||||
public Color ambientColor = new Color(0.259f, 0.165f, 0.373f, 1f);
|
||||
|
||||
// ── Ⓓ 림라이트 ──────────────────────────────────────────────────────
|
||||
[Header("Ⓓ 림라이트 (캐릭터 윤곽의 밝은 테)")]
|
||||
public bool rimOn = true;
|
||||
|
||||
[Range(0f, 4f)] public float rimStrength = 1.25f;
|
||||
[Range(0.5f, 8f)] public float rimPower = 3.0f;
|
||||
[Range(0f, 1f)] public float rimUpBias = 0.35f;
|
||||
public Color rimColor = new Color(1f, 0.82f, 0.5f, 1f);
|
||||
|
||||
[Tooltip("배경에도 림을 넣을지(레퍼런스는 캐릭터만).")]
|
||||
public bool rimOnBackground = false;
|
||||
|
||||
/// <summary>인스펙터에서 프로파일을 물릴 수 있게 하는 얇은 래퍼(Volume 패키지 타입 직접 노출).</summary>
|
||||
[System.Serializable]
|
||||
public sealed class VolumeProfileRef
|
||||
{
|
||||
public UnityEngine.Rendering.VolumeProfile asset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: af081c9d37b39dd40aa4c58f0397f23c
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5cded1f5a214b5e46aec36dd921a61a6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// WLHullOutline.shader — 화면 픽셀 단위 두께의 「굵은 검은 외곽선」 (WL-814t · #814)
|
||||
//
|
||||
// ■ 왜 새로 만드나 (§1 실측 결론)
|
||||
// 원본 `Shader Graphs/Toon` 의 `_Outline`/`_OUTLINESENABLED` 는 **머티리얼 안에 박힌
|
||||
// 화면공간 에지검출 후처리**다(Depth+Normals 1텍셀 비교 → Overlay 블렌드).
|
||||
// · 두께 = `_CameraNormalsTexture` **1 텍셀 고정** — 머티리얼로 못 굵게 한다.
|
||||
// · `_Outline` 의 **알파는 무시**된다(Blend 노드가 float3 로 해석) — RGB 만 쓴다.
|
||||
// · `PixelOutlineSetup` 렌더러 피처가 꺼져 있으면(현재 `m_Active: 0`) 아무것도 안 나온다.
|
||||
// · 켜도 나오는 것은 「면 경계의 얇은 실선」이지 레퍼런스의 굵은 실루엣이 아니다.
|
||||
// → 레퍼런스(2D 도트)의 굵은 외곽선은 **메시 노멀 확장(inverted hull)** 으로만 나온다.
|
||||
//
|
||||
// ■ 무엇을 하나
|
||||
// 앞면을 버리고(Cull Front) 뒷면을 화면 XY 로 `_OutlineWidth` **픽셀**만큼 밀어 그린다.
|
||||
// 두께가 화면 픽셀 단위라 거리·직교크기와 무관하게 일정하다(= 2D 도트 느낌).
|
||||
//
|
||||
// ■ 어떻게 쓰나
|
||||
// 원본 머티리얼을 **건드리지 않는다**. 렌더러의 머티리얼 배열 뒤에 이 머티리얼을 한 장
|
||||
// 더 붙이면(서브메시가 1개면 마지막 서브메시를 한 번 더 그린다) 외곽선이 된다.
|
||||
// 붙이고 떼는 것은 `WLReferenceLook` 이 런타임에만 한다(에셋·씬 무변경).
|
||||
//
|
||||
// ■ 모바일 비용
|
||||
// 대상 렌더러당 드로우콜 +1(정점만 한 번 더). 픽셀 부하는 실루엣 띠 면적뿐.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
Shader "WL/HullOutline"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[HDR] _OutlineColor ("Outline Color", Color) = (0, 0, 0, 1)
|
||||
_OutlineWidth ("Outline Width (screen px)", Float) = 2.0
|
||||
_OutlineDepthBias ("Depth Bias (m · 하드에지 저폴리의 내부 얼룩 제거)", Float) = 0.06
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags { "RenderPipeline" = "UniversalPipeline" "RenderType" = "Opaque" "Queue" = "Geometry-1" }
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "WLHullOutline"
|
||||
Tags { "LightMode" = "SRPDefaultUnlit" }
|
||||
|
||||
Cull Front
|
||||
ZWrite On
|
||||
ZTest LEqual
|
||||
ColorMask RGB
|
||||
|
||||
HLSLPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma multi_compile_instancing
|
||||
#pragma target 3.0
|
||||
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
||||
|
||||
CBUFFER_START(UnityPerMaterial)
|
||||
float4 _OutlineColor;
|
||||
float _OutlineWidth;
|
||||
float _OutlineDepthBias;
|
||||
CBUFFER_END
|
||||
|
||||
struct Attributes
|
||||
{
|
||||
float4 positionOS : POSITION;
|
||||
float3 normalOS : NORMAL;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct Varyings
|
||||
{
|
||||
float4 positionCS : SV_POSITION;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
Varyings vert(Attributes input)
|
||||
{
|
||||
Varyings o = (Varyings)0;
|
||||
UNITY_SETUP_INSTANCE_ID(input);
|
||||
UNITY_TRANSFER_INSTANCE_ID(input, o);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
|
||||
|
||||
// 🔴 깊이 바이어스 — 껍데기를 카메라에서 조금 더 멀리 밀어 둔다.
|
||||
// 저폴리 하드에지 메시(머리카락·옷)는 같은 위치에서 노멀이 쪼개져 있어
|
||||
// 그냥 밀면 실루엣 **안쪽**에 뒷면이 튀어나와 얼룩이 된다(실측).
|
||||
// 본체 면보다 항상 뒤에 있게 만들면 안쪽 얼룩만 깔끔히 사라진다.
|
||||
float3 posWS = TransformObjectToWorld(input.positionOS.xyz);
|
||||
float3 posVS = TransformWorldToView(posWS);
|
||||
posVS.z -= _OutlineDepthBias; // 뷰공간은 -Z 가 앞 → 빼면 멀어진다
|
||||
float4 posCS = TransformWViewToHClip(posVS);
|
||||
|
||||
float3 nWS = TransformObjectToWorldNormal(input.normalOS);
|
||||
float3 nVS = TransformWorldToViewDir(nWS, true);
|
||||
// 투영까지 태워야 직교/원근 모두에서 화면 방향이 맞는다
|
||||
float2 nCS = mul((float3x3)UNITY_MATRIX_P, nVS).xy;
|
||||
|
||||
// 화면(픽셀) 공간에서 정규화 → 방향이 대각이어도 두께가 일정하다
|
||||
float2 dirPx = nCS * float2(_ScreenParams.x, _ScreenParams.y);
|
||||
float len = length(dirPx);
|
||||
dirPx = (len > 1e-5) ? dirPx / len : float2(0.0, 0.0);
|
||||
|
||||
// 픽셀 → NDC(가로 2.0 이 화면 폭) → 클립(× w)
|
||||
float2 ndcPerPx = float2(2.0 / _ScreenParams.x, 2.0 / _ScreenParams.y);
|
||||
posCS.xy += dirPx * ndcPerPx * _OutlineWidth * posCS.w;
|
||||
|
||||
o.positionCS = posCS;
|
||||
return o;
|
||||
}
|
||||
|
||||
half4 frag(Varyings input) : SV_Target
|
||||
{
|
||||
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
|
||||
return half4(_OutlineColor.rgb, 1.0);
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
|
||||
Fallback Off
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c1901a3913628b24590d96fb77efd166
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// WLRimAdd.shader — 캐릭터 윤곽의 밝은 테(림라이트) (WL-814t · #814 · 요소 Ⓓ)
|
||||
//
|
||||
// ■ 왜 별도 패스인가
|
||||
// 원본 `Toon.shadergraph` 에는 Fresnel 입력이 없고, 원본(`Assets/3DPixelArtEnvironment/**`)
|
||||
// 수정은 금지다. 그래프 **복사본**을 만들어 Fresnel 을 더하는 대신, 같은 메시를 한 번 더
|
||||
// 가산(Blend One One)으로 덧그리는 패스를 택했다 — 머티리얼/그래프를 하나도 안 건드리고
|
||||
// 런타임에 켜고 끌 수 있고, 세기를 슬라이더 하나로 준다.
|
||||
//
|
||||
// ■ 무엇을 하나
|
||||
// rim = pow(1 - saturate(dot(N, V)), _RimPower) — 시선과 수직인 가장자리에서 1.
|
||||
// `_RimUpBias` 로 위쪽(레퍼런스의 무대조명) 윤곽만 더 밝게 줄 수 있다.
|
||||
//
|
||||
// ■ 주의
|
||||
// Transparent 큐 · ZWrite Off · ZTest LEqual + Offset 으로 원본 면 위에 얹는다.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
Shader "WL/RimAdd"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[HDR] _RimColor ("Rim Color", Color) = (1, 0.85, 0.55, 1)
|
||||
_RimPower ("Rim Power", Float) = 3.0
|
||||
_RimStrength ("Rim Strength", Float) = 1.0
|
||||
_RimUpBias ("Rim Up Bias (0=균일, 1=위쪽만)", Range(0, 1)) = 0.35
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags { "RenderPipeline" = "UniversalPipeline" "RenderType" = "Transparent" "Queue" = "Transparent" }
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "WLRimAdd"
|
||||
Tags { "LightMode" = "SRPDefaultUnlit" }
|
||||
|
||||
Cull Back
|
||||
ZWrite Off
|
||||
ZTest LEqual
|
||||
Offset -1, -1
|
||||
Blend One One
|
||||
|
||||
HLSLPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma multi_compile_instancing
|
||||
#pragma target 3.0
|
||||
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
||||
|
||||
CBUFFER_START(UnityPerMaterial)
|
||||
float4 _RimColor;
|
||||
float _RimPower;
|
||||
float _RimStrength;
|
||||
float _RimUpBias;
|
||||
CBUFFER_END
|
||||
|
||||
struct Attributes
|
||||
{
|
||||
float4 positionOS : POSITION;
|
||||
float3 normalOS : NORMAL;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct Varyings
|
||||
{
|
||||
float4 positionCS : SV_POSITION;
|
||||
float3 normalWS : TEXCOORD0;
|
||||
float3 positionWS : TEXCOORD1;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
Varyings vert(Attributes input)
|
||||
{
|
||||
Varyings o = (Varyings)0;
|
||||
UNITY_SETUP_INSTANCE_ID(input);
|
||||
UNITY_TRANSFER_INSTANCE_ID(input, o);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
|
||||
o.positionWS = TransformObjectToWorld(input.positionOS.xyz);
|
||||
o.positionCS = TransformWorldToHClip(o.positionWS);
|
||||
o.normalWS = TransformObjectToWorldNormal(input.normalOS);
|
||||
return o;
|
||||
}
|
||||
|
||||
half4 frag(Varyings input) : SV_Target
|
||||
{
|
||||
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
|
||||
float3 n = normalize(input.normalWS);
|
||||
float3 v = normalize(GetWorldSpaceViewDir(input.positionWS));
|
||||
float rim = 1.0 - saturate(dot(n, v));
|
||||
rim = pow(saturate(rim), max(_RimPower, 0.01));
|
||||
// 위쪽 윤곽 가중(무대 조명 느낌)
|
||||
float up = saturate(n.y * 0.5 + 0.5);
|
||||
rim *= lerp(1.0, up, saturate(_RimUpBias));
|
||||
return half4(_RimColor.rgb * (rim * _RimStrength), 0.0);
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
|
||||
Fallback Off
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e22defdb5476d4b4f93db440d73c4233
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in New Issue