[WL-814t] 외곽선 1px 상한(814u 면적 실측) + 표면색 연동 외곽선 + 면적 비교 캡처 (#814)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
9b8f7e968b
commit
db34b0171b
|
|
@ -51,21 +51,25 @@ public static class WL814t_Capture
|
|||
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 };
|
||||
// ── Ⓐ 외곽선 4단 — 두께는 **아레나 실제 카메라(직교 10)** 기준 px (814u 실측 기준과 같은 축)
|
||||
float[] ws = { 1f, 2f, 3f, 4f };
|
||||
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);
|
||||
o.outlineWidth = ws[i]; o.outlineWidthBackground = Mathf.Max(1f, ws[i] * 2.5f);
|
||||
Shot2("o" + (i + 1) + "_outline_" + ws[i].ToString("F0") + "px", o, Close, 0);
|
||||
}
|
||||
// 외곽선 색 — 순검정 vs 아주 어두운 보라
|
||||
// 외곽선 색 — 균일 검정 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);
|
||||
var o = Only(cfg, true, false, false, false); o.outlineWidth = 1f;
|
||||
o.outlineColor = Color.black; o.outlineTintAmount = 0f; Shot2("o5_color_uniformblack", o, Close, 0);
|
||||
o.outlineTintAmount = 1f; Shot2("o6_color_surfacetint", o, Close, 0);
|
||||
o.outlineColor = new Color(0.043f, 0.031f, 0.067f, 1f); o.outlineTintAmount = 0f; Shot2("o7_color_darkviolet", o, Close, 0);
|
||||
}
|
||||
|
||||
// ── Ⓐ 면적 실측용 (직교 10 = 인게임 크기 · 외곽선을 마젠타로 칠해 픽셀을 센다)
|
||||
MeasureOutlineArea(cfg, pc);
|
||||
|
||||
// ── Ⓑ 대비 3단
|
||||
float[] mds = { 0.10f, 0.05f, 0.0f };
|
||||
for (int i = 0; i < mds.Length; i++)
|
||||
|
|
@ -118,6 +122,34 @@ public static class WL814t_Capture
|
|||
return s_log.ToString();
|
||||
}
|
||||
|
||||
/// <summary>외곽선 띠가 캐릭터 면적의 몇 %를 먹는지 재기 위한 렌더 묶음(파이썬이 픽셀을 센다).</summary>
|
||||
static void MeasureOutlineArea(WLReferenceLookSettings cfg, GameObject pc)
|
||||
{
|
||||
var rs = pc != null ? pc.GetComponentsInChildren<Renderer>(true) : new Renderer[0];
|
||||
var on = new List<Renderer>();
|
||||
foreach (var r in rs) if (r != null && r.enabled) on.Add(r);
|
||||
|
||||
// 배경만(캐릭터 끔)
|
||||
WLReferenceLook.Restore();
|
||||
foreach (var r in on) r.enabled = false;
|
||||
s_cam.orthographicSize = Wide; s_cam.transform.position = s_charCenter - s_cam.transform.forward * 30f;
|
||||
Shot("mx_bg", 0);
|
||||
foreach (var r in on) r.enabled = true;
|
||||
|
||||
// 외곽선 없음
|
||||
Shot2("mx_w0", Nothing(cfg), Wide, 0);
|
||||
|
||||
// 마젠타 외곽선 1~4 px (배경엔 안 넣는다 = 캐릭터만 센다)
|
||||
float[] ws = { 1f, 2f, 3f, 4f };
|
||||
for (int i = 0; i < ws.Length; i++)
|
||||
{
|
||||
var o = Only(cfg, true, false, false, false);
|
||||
o.outlineWidth = ws[i]; o.outlineOnBackground = false;
|
||||
o.outlineColor = new Color(1f, 0f, 1f, 1f); o.outlineTintAmount = 0f;
|
||||
Shot2("mx_w" + ws[i].ToString("F0"), o, Wide, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static WLReferenceLook.Opt Nothing(WLReferenceLookSettings cfg)
|
||||
{
|
||||
var o = WLReferenceLook.Opt.FromSettings(cfg);
|
||||
|
|
|
|||
|
|
@ -15,13 +15,15 @@ MonoBehaviour:
|
|||
enabled_: 1
|
||||
verboseLog: 0
|
||||
outlineOn: 1
|
||||
outlineWidth: 6
|
||||
outlineWidth: 1
|
||||
outlineWidthPixelMode: 1
|
||||
outlineColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
outlineOnBackground: 1
|
||||
outlineWidthBackground: 2.5
|
||||
outlineScaleWithZoom: 1
|
||||
outlineRefOrthoSize: 3.4
|
||||
outlineRefOrthoSize: 10
|
||||
outlineTintAmount: 0
|
||||
outlineTintDarkness: 0.28
|
||||
outlineDepthBias: 0.06
|
||||
contrastOn: 1
|
||||
minimumDarkness: 0.12
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ namespace WL.Look.Arena
|
|||
public bool outlineOnBackground;
|
||||
public bool outlineScaleWithZoom;
|
||||
public float outlineRefOrthoSize, outlineDepthBias;
|
||||
public float outlineTintAmount, outlineTintDarkness;
|
||||
public float minimumDarkness, shadowColorScale, shadowTintAmount;
|
||||
public Color shadowTint;
|
||||
public int charShades;
|
||||
|
|
@ -59,6 +60,7 @@ namespace WL.Look.Arena
|
|||
o.outlineColor = c.outlineColor; o.outlineOnBackground = c.outlineOnBackground;
|
||||
o.outlineScaleWithZoom = c.outlineScaleWithZoom; o.outlineRefOrthoSize = c.outlineRefOrthoSize;
|
||||
o.outlineDepthBias = c.outlineDepthBias;
|
||||
o.outlineTintAmount = c.outlineTintAmount; o.outlineTintDarkness = c.outlineTintDarkness;
|
||||
o.minimumDarkness = c.minimumDarkness; o.shadowColorScale = c.shadowColorScale;
|
||||
o.shadowTintAmount = c.shadowTintAmount; o.shadowTint = c.shadowTint;
|
||||
o.charShades = c.charShades;
|
||||
|
|
@ -107,28 +109,21 @@ namespace WL.Look.Arena
|
|||
|
||||
OutlinedRenderers = 0; RimRenderers = 0; ContrastMaterials = 0;
|
||||
|
||||
Material outlineChar = null, outlineBg = null, rimMat = null;
|
||||
if (o.outline)
|
||||
Shader outlineShader = o.outline ? Shader.Find("WL/HullOutline") : null;
|
||||
// 줌 비례 — 직교 크기가 커지면(멀리서 보면) 화면 픽셀 두께를 같은 비율로 줄인다.
|
||||
// 그래야 「캐릭터 키 대비 굵기」가 프레이밍과 무관하게 일정하다(2D 스프라이트처럼).
|
||||
// 기준 = 아레나 실제 카메라(직교 10) → SO 의 px 값이 곧 「인게임에서 보이는 px」다.
|
||||
float zoom = 1f;
|
||||
{
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
s_outlineShader = outlineShader; s_zoom = zoom; s_opt = o;
|
||||
s_outlines.Clear();
|
||||
|
||||
Material rimMat = null;
|
||||
if (o.rim)
|
||||
{
|
||||
var sh = Shader.Find("WL/RimAdd");
|
||||
|
|
@ -173,7 +168,7 @@ namespace WL.Look.Arena
|
|||
|
||||
// Ⓐ 외곽선 · Ⓓ 림 — 머티리얼 배열 뒤에 덧붙인다(서브메시 1개면 한 번 더 그린다)
|
||||
var extra = new List<Material>(2);
|
||||
if (o.outline && (isChar || o.outlineOnBackground)) extra.Add(isChar ? outlineChar : outlineBg);
|
||||
if (o.outline && (isChar || o.outlineOnBackground)) extra.Add(OutlineFor(mats[0], isChar));
|
||||
if (o.rim && rimMat != null && (isChar || o.rimOnBackground)) extra.Add(rimMat);
|
||||
if (extra.Count > 0 && extra[0] != null)
|
||||
{
|
||||
|
|
@ -289,6 +284,7 @@ namespace WL.Look.Arena
|
|||
for (int i = 0; i < s_created.Count; i++) if (s_created[i] != null) DestroyAny(s_created[i]);
|
||||
s_created.Clear();
|
||||
s_clones.Clear();
|
||||
s_outlines.Clear();
|
||||
OutlinedRenderers = 0; RimRenderers = 0; ContrastMaterials = 0;
|
||||
if (s_applied) LastLog = "restored(814s 상태)";
|
||||
s_applied = false;
|
||||
|
|
@ -303,6 +299,32 @@ namespace WL.Look.Arena
|
|||
return anim != null ? anim.gameObject : null;
|
||||
}
|
||||
|
||||
static Shader s_outlineShader;
|
||||
static float s_zoom = 1f;
|
||||
static Opt s_opt;
|
||||
static readonly Dictionary<Material, Material> s_outlines = new Dictionary<Material, Material>();
|
||||
|
||||
/// <summary>원본 머티리얼 1종당 외곽선 머티리얼 1장(색 연동을 하려면 BaseMap·DiffuseColor 가 원본별로 달라야 한다).</summary>
|
||||
static Material OutlineFor(Material src, bool isChar)
|
||||
{
|
||||
if (s_outlineShader == null) return null;
|
||||
Material m;
|
||||
if (s_outlines.TryGetValue(src, out m) && m != null) return m;
|
||||
m = Make(s_outlineShader);
|
||||
m.SetColor("_OutlineColor", s_opt.outlineColor);
|
||||
m.SetFloat("_OutlineWidth", (isChar ? s_opt.outlineWidth : s_opt.outlineWidthBackground) * s_zoom);
|
||||
m.SetFloat("_OutlineDepthBias", s_opt.outlineDepthBias);
|
||||
m.SetFloat("_TintAmount", s_opt.outlineTintAmount);
|
||||
m.SetFloat("_TintDarkness", s_opt.outlineTintDarkness);
|
||||
if (src != null)
|
||||
{
|
||||
if (src.HasProperty("_BaseMap")) { var t = src.GetTexture("_BaseMap"); if (t != null) m.SetTexture("_BaseMap", t); }
|
||||
if (src.HasProperty("_DiffuseColor")) m.SetColor("_TintColor", src.GetColor("_DiffuseColor"));
|
||||
}
|
||||
s_outlines[src] = m;
|
||||
return m;
|
||||
}
|
||||
|
||||
static Material Make(Shader sh)
|
||||
{
|
||||
var m = new Material(sh);
|
||||
|
|
|
|||
|
|
@ -48,8 +48,9 @@ namespace WL.Look.Arena
|
|||
[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 · 아레나 실제 카메라 직교 10 = 캐릭터 키 98~117 px 기준). " +
|
||||
"🔴 814u 실측: 이 크기에서 1 px 띠가 이미 캐릭터 면적의 27 %, 3 px 이면 76 % 를 먹는다 → 상한 1(비교용 2).")]
|
||||
public float outlineWidth = 1.0f;
|
||||
|
||||
[Tooltip("도트(픽셀) 모드에서 쓰는 캐릭터 외곽선 두께(px · 저해상도 RT 기준).")]
|
||||
public float outlineWidthPixelMode = 1.0f;
|
||||
|
|
@ -60,14 +61,20 @@ namespace WL.Look.Arena
|
|||
[Tooltip("배경(바위·나무·건물)에도 외곽선을 넣을지.")]
|
||||
public bool outlineOnBackground = true;
|
||||
|
||||
[Tooltip("배경 외곽선 두께(px). 저폴리 하드에지라 캐릭터보다 얇게 잡는다.")]
|
||||
[Tooltip("배경 외곽선 두께(px @직교 10). 배경 물체는 화면에서 크므로 캐릭터보다 굵어도 된다.")]
|
||||
public float outlineWidthBackground = 2.5f;
|
||||
|
||||
[Tooltip("켜면 두께를 카메라 줌(직교 크기)에 맞춰 비례시킨다 = 캐릭터 대비 굵기가 항상 같다(2D 스프라이트처럼).")]
|
||||
public bool outlineScaleWithZoom = true;
|
||||
|
||||
[Tooltip("위 비례의 기준 직교 반높이. 이 크기일 때 두께가 위 px 값 그대로다.")]
|
||||
public float outlineRefOrthoSize = 3.4f;
|
||||
[Tooltip("위 비례의 기준 직교 반높이 = 아레나 실제 카메라(10). 이 크기일 때 두께가 위 px 값 그대로다.")]
|
||||
public float outlineRefOrthoSize = 10f;
|
||||
|
||||
[Tooltip("0 = 균일 색(검정) · 1 = 표면색 연동(머리칼=짙은 갈색, 옷=짙은 빨강 …). 레퍼런스는 부위마다 선 색이 다르다.")]
|
||||
[Range(0f, 1f)] public float outlineTintAmount = 0f;
|
||||
|
||||
[Tooltip("색 연동일 때 표면색을 얼마나 어둡게 할지(작을수록 진하다).")]
|
||||
[Range(0f, 1f)] public float outlineTintDarkness = 0.28f;
|
||||
|
||||
[Tooltip("껍데기를 카메라에서 뒤로 미는 양(m). 저폴리 하드에지의 실루엣 안쪽 얼룩을 없앤다.")]
|
||||
public float outlineDepthBias = 0.06f;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,11 @@ Shader "WL/HullOutline"
|
|||
[HDR] _OutlineColor ("Outline Color", Color) = (0, 0, 0, 1)
|
||||
_OutlineWidth ("Outline Width (screen px)", Float) = 2.0
|
||||
_OutlineDepthBias ("Depth Bias (m · 하드에지 저폴리의 내부 얼룩 제거)", Float) = 0.06
|
||||
// ── 색 연동 외곽선(레퍼런스는 부위마다 선 색이 다르다: 머리칼=짙은 갈색 · 흰 올빼미=짙은 남색)
|
||||
_BaseMap ("Base Map (원본 머티리얼에서 복사)", 2D) = "white" {}
|
||||
_TintColor ("Tint Color (원본 _DiffuseColor)", Color) = (1, 1, 1, 1)
|
||||
_TintDarkness ("Tint Darkness", Range(0, 1)) = 0.28
|
||||
_TintAmount ("0 = 균일 색 · 1 = 표면색 연동", Range(0, 1)) = 0.0
|
||||
}
|
||||
|
||||
SubShader
|
||||
|
|
@ -53,22 +58,30 @@ Shader "WL/HullOutline"
|
|||
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
||||
|
||||
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
|
||||
|
||||
CBUFFER_START(UnityPerMaterial)
|
||||
float4 _OutlineColor;
|
||||
float _OutlineWidth;
|
||||
float _OutlineDepthBias;
|
||||
float4 _BaseMap_ST;
|
||||
float4 _TintColor;
|
||||
float _TintDarkness;
|
||||
float _TintAmount;
|
||||
CBUFFER_END
|
||||
|
||||
struct Attributes
|
||||
{
|
||||
float4 positionOS : POSITION;
|
||||
float3 normalOS : NORMAL;
|
||||
float2 uv : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct Varyings
|
||||
{
|
||||
float4 positionCS : SV_POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
|
@ -104,13 +117,18 @@ Shader "WL/HullOutline"
|
|||
posCS.xy += dirPx * ndcPerPx * _OutlineWidth * posCS.w;
|
||||
|
||||
o.positionCS = posCS;
|
||||
o.uv = TRANSFORM_TEX(input.uv, _BaseMap);
|
||||
return o;
|
||||
}
|
||||
|
||||
half4 frag(Varyings input) : SV_Target
|
||||
{
|
||||
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
|
||||
return half4(_OutlineColor.rgb, 1.0);
|
||||
// 색 연동 — 원본 머티리얼의 BaseMap×DiffuseColor 를 어둡게 깐다.
|
||||
// BaseMap 이 없는 배경 머티리얼은 흰색이 바인딩되어 _TintColor(=_DiffuseColor) 만 남는다.
|
||||
half3 tinted = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv).rgb * _TintColor.rgb * _TintDarkness;
|
||||
half3 col = lerp(_OutlineColor.rgb, tinted, saturate(_TintAmount));
|
||||
return half4(col, 1.0);
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue