Project_WL/Assets/WL/Look/Character/Shaders/WLHullOutline.shader

139 lines
6.9 KiB
Plaintext
Raw Permalink Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// 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
// ── 색 연동 외곽선(레퍼런스는 부위마다 선 색이 다르다: 머리칼=짙은 갈색 · 흰 올빼미=짙은 남색)
_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
{
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"
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
};
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;
o.uv = TRANSFORM_TEX(input.uv, _BaseMap);
return o;
}
half4 frag(Varyings input) : SV_Target
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
// 색 연동 — 원본 머티리얼의 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
}
}
Fallback Off
}