Shader code



Texture Set 1 Matcap Texture ( 256 px ) 1 Color Mask(R) + Normalmap(G,A) + Emissive Mask(B) (Packed) ( 512 px ) 1 Dissolve Noise Texture ( 32 px )
Matcap
Normal Pack
Build TBN matrix and convert Tangent space → World space
half3 tangentWS = normalize(input.tangentWS.xyz);
half tangentSign = input.tangentWS.w; // generate bitangent directly in the pixel via cross
half3 bitangentWS = normalize(cross(normalWS, tangentWS) * tangentSign);
half3x3 TBN = half3x3(tangentWS, bitangentWS, normalWS);
half3 finalNormalWS = normalize(mul(normalTS, TBN)); // World space normal
Typical TBN = compute the vertex bitangent and pass it to the pixel ( incurs Fetch Cost )
On mobile, fetch cost outweighs compute cost.
Optimized code = to reduce fetch cost, compute bitangent via cross in the pixel shader
World → View space normal conversion
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 samples lighting effects from a matcap texture by using the view-space normal direction as UV coordinates, without performing any real lighting calculation.
The reason MikkTSpace is not computed directly in the shader is that Unity already injects MikkTSpace-based tangents at the import / FBX stage.
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
float3 normalOS : NORMAL; // Normal from the FBX, based on MikkTSpace
float4 tangentOS : TANGENT; // Tangent from the FBX, based on MikkTSpace (xyz+sign)
};
Building the TBN from tangentOS and normalOS in the Unity shader keeps everything unified under MikkTSpace through normal-map baking and FBX import.
Same quality as a Mikkelsen-baked normal map
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-normal direction based AO — bounce light ( Y AO ) implemented using only the normal, no AO texture required (optimized feature)
Apply bounce light ( Y AO ) color


Dissolve 기능
if (_Dissolve > 0.0h)
{
half noiseValue = SAMPLE_TEXTURE2D(_DissolveNoise, sampler_DissolveNoise, input.uv).r;
clip(noiseValue - _Dissolve);
}
When _Dissolve is 0 (not used at all) → the noise texture is not even sampled (performance saving)
When Dissolve > 0 → wherever the per-pixel noise value (0~1) minus Dissolve goes negative, clip is invoked, producing the Dissolve effect (the animation of parts disappearing along the noise pattern)

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;
Rim light implementation unaffected by the normal map ( the rim forms along the silhouette )
half3 normalWS = normalize(input.normalWS);
The input.normalWS here is computed in the vertex shader as VertexNormalInputs normalInputs = GetVertexNormalInputs(input.normalOS, input.tangentOS) and passed to the pixel shader.
That is, the "vertex normal" imported from the modeling/FBX (=the normal baked into the mesh, which defines the silhouette).
Computed with normalWS → per-vertex world normal (normal map / detail not applied)
