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

139 lines
6.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ─────────────────────────────────────────────────────────────────────────────
// 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
}