본문으로 건너뛰기
CHOI HONGSU
1 min read

셰이더 코드

 

Texture Set Matcap Texture 1장 ( 256px ) Color Mask(R) + Normalmap(G,A) + Emissive Mask(B) (Packed) 1장 ( 512px ) Dissolve Noise Texture 1장 ( 32px )

Matcap

Normal Pack

TBN 행렬 생성 및 Tangent space → World space 변환

half3 tangentWS = normalize(input.tangentWS.xyz);
half tangentSign = input.tangentWS.w; // bitangent를 픽셀에서 cross로 직접 생성
half3 bitangentWS = normalize(cross(normalWS, tangentWS) * tangentSign);
half3x3 TBN = half3x3(tangentWS, bitangentWS, normalWS);
half3 finalNormalWS = normalize(mul(normalTS, TBN)); // World space 노말

일반적인 TBN = Vertex bitangent 계산해 Pixel 로 넘긴다 ( Fetch Cost 발생 )

모바일에서는 연산량보다 fetch cost가 더 크다.

최적화된 코드 = Fetch Cost를 줄이기 위해 → 픽셀 셰이더에서 cross로 bitangent 계산


World → View space 노멀 변환

half3 viewNormal = normalize(mul((float3x3)UNITY_MATRIX_V, finalNormalWS));

Matcap

half2 matcapUV = viewNormal.xy * 0.5h + 0.5h;
half4 matcapColor = SAMPLE_TEXTURE2D(_Matcap, sampler_Matcap, matcapUV);

Matcap 실제 라이팅 계산 없이, 카메라에서 바라본 노멀 방향을 UV 좌표로 사용하여 메캡 텍스처에서 조명 효과를 샘플링함

MikkTSpace을 셰이더에서 직접계산 안하는 이유 이미 Unity가 임포트/FBX단에서 MikkTSpace 기반으로 계산된 tangent를 넣어줌.

struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
float3 normalOS : NORMAL; // FBX에서 온, MikkTSpace 기준의 Normal
float4 tangentOS : TANGENT; // FBX에서 온, MikkTSpace 기준의 Tangent (xyz+sign)
};

Unity 셰이더에서 tangentOS, normalOS 기반으로 TBN을 만들면, 노멀맵 베이크/FBX 임포트 과정에서 MikkTSpace로 통일.

미켈슨 노멀맵과 같은 퀄리티

Shader Graph로 코드 시각화

Unit Color Mask ( R Chanel )

Unit Color Mask ( R Chanel )

half stepEdgeSub = step(0.72h, maskR); half edgeSubtract = maskR (1.0h - stepEdgeSub); half stepEdgeMain = step(0.02h, edgeSubtract); half oneminusMain = 1.0h - stepEdgeMain; half4 maskBaseColor = half4(maskR, maskR, maskR, 1.0h); half4 colorMasked = maskBaseColor (1.0h - oneminusMain) + MainColor oneminusMain; half4 finalColor = colorMasked (1.0h - stepEdgeSub) + SubColor * stepEdgeSub;

R - Mask ( MainMask - 0 // SubMask - 1 ) ( Black - 0.05 // Gray - 0.08 // White - 0.2 )

Shader Graph로 코드 시각화

반사광 구현 ( Y AO )

half yNormal = finalNormalWS.y;
half aoFactor = 1.0h - smootherstep(_AOYThreshold - 0.5h, AOYThreshold + 0.5h, yNormal);
half4 aoTinted = matcapColor +
(BG_UnitAOColor aoFactor _AOTintStrength);

Y 노멀 방향 기반 AO AO용 텍스처 없이 노멀만으로 반사광 ( Y AO ) 구현 최적화된 기능

반사광 ( Y AO ) 색 적용

Dissolve 기능

if (_Dissolve > 0.0h)
{
half noiseValue = SAMPLE_TEXTURE2D(_DissolveNoise, sampler_DissolveNoise, input.uv).r;
clip(noiseValue - _Dissolve);
}

_Dissolve가 0이면 (아예 안 쓰면) → 노이즈 텍스처 샘플조차 하지 않음 (퍼포먼스 절약)

Dissolve > 0이 되면 → 각 픽셀의 노이즈값(0~1)에서 Dissolve를 뺀 결과가 음수면 → clip이 호출되어 Dissolve 효과 (노이즈 패턴을 따라, 일부분이 사라지는 애니메이션)가 나옴

Rim Light

half3 viewSpaceNormal;
viewSpaceNormal.x = dot(UNITY_MATRIX_V[0].xyz, normalWS);
viewSpaceNormal.y = dot(UNITY_MATRIX_V[1].xyz, normalWS);
viewSpaceNormal.z = dot(UNITY_MATRIX_V[2].xyz, normalWS);

half rimFactor = dot(viewSpaceNormal, RimLightDirection);
half rimIntensity = step(rimFactor, -5.0h) *
UseRimLight;
finalColor += rimIntensity;

노말맵에 영향없는 림라이트 구현 ( 실루엣대로 림이 형성됨 )

half3 normalWS = normalize(input.normalWS);

여기서의 input.normalWS는 버텍스 셰이더에서 VertexNormalInputs normalInputs = GetVertexNormalInputs(input.normalOS, input.tangentOS) 로 계산되어 픽셀 셰이더로 전달됨

즉, 모델링/FBX에서 임포트된 "버텍스 노멀" (=메시에 baked된 노멀, 실루엣을 정의)

normalWS로 계산 → 버텍스 단위 월드 노말로 계산(노멀맵/디테일 적용 X)

셰이더 코드 · FlashGambit · Choi Hongsu · Hongsu