Shader / URP
Advanced
Unity 2022.3+ / Unity 6 (URP)
3D 픽셀 아트용 컬러 팔레트 양자화(LUT Posterization) 및 1px 도트 외곽선 셰이더
3D 씬의 부드러운 라이팅 그라데이션을 레트로 팔레트로 제한하는 포스터라이제이션과 Depth/Normal 버퍼를 이용한 픽셀 퍼펙트 1px 외곽선 검출.
#Shader #URP #PixelArt #LUT #Posterization #Outline
핵심 개념3D 씬을 저해상도로 렌더링해도 수천만 가지의 부드러운 3D 셰이딩 그라데이션이 남아있으면 '도트 픽셀 아트'가 아니라 '화질 나쁜 3D'처럼 보입니다. 색상을 몇 단계로 끊고 레트로 컬러 팔레트로 매핑하는 컬러 양자화(Color Quantization)와, 물체 간 경계에 1픽셀 검은색 도트 선을 쳐주는 Normal/Depth Edge Outline이 결합되어야 진정한 픽셀 아트 룩이 완성됩니다.
1. 3D 픽셀 아트 종합 포스트 프로세싱 셰이더
// URP Custom Post-Process Pass Fragment Shader
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareNormalsTexture.hlsl"
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex_Point);
float4 _MainTex_TexelSize;
float _ColorSteps; // 색상 양자화 단계 (예: 4.0 ~ 8.0)
float _DepthThreshold; // 외곽선 깊이 민감도 (예: 0.2)
float _NormalThreshold; // 외곽선 노멀 민감도 (예: 0.4)
// 1픽셀 십자(Cross) 외곽선 검출
float DetectOutline(float2 uv)
{
float2 offset = _MainTex_TexelSize.xy;
// 중앙 및 상하좌우 샘플링
float dCenter = SampleSceneDepth(uv);
float dTop = SampleSceneDepth(uv + float2(0, offset.y));
float dBottom = SampleSceneDepth(uv - float2(0, offset.y));
float dLeft = SampleSceneDepth(uv - float2(offset.x, 0));
float dRight = SampleSceneDepth(uv + float2(offset.x, 0));
// 깊이 차이 계산
float depthDiff = (abs(dCenter - dTop) + abs(dCenter - dBottom) +
abs(dCenter - dLeft) + abs(dCenter - dRight));
// 노멀 차이 계산
float3 nCenter = SampleSceneNormals(uv);
float3 nTop = SampleSceneNormals(uv + float2(0, offset.y));
float3 nRight = SampleSceneNormals(uv + float2(offset.x, 0));
float normalDiff = distance(nCenter, nTop) + distance(nCenter, nRight);
// 임계값 초과 시 1.0 (외곽선 검정)
return (depthDiff > _DepthThreshold || normalDiff > _NormalThreshold) ? 1.0 : 0.0;
}
half4 Frag(Varyings input) : SV_Target
{
// 1. 원본 컬러 샘플링
half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex_Point, input.uv);
// 2. 색상 단계 양자화 (Posterization)
col.rgb = floor(col.rgb * _ColorSteps) / _ColorSteps;
// 3. 1픽셀 도트 외곽선 합성
float isEdge = DetectOutline(input.uv);
if (isEdge > 0.5)
{
col.rgb = half3(0.05, 0.05, 0.08); // 도트 검은색 아웃라인
}
return col;
}
2. 레트로 무드 연출 팁
- 감마 코렉션 일치:
_ColorSteps로 색상을 끊을 때 선형(Linear) 공간에서 계산하면 암부가 뭉개질 수 있으므로, sRGB 공간에서 양자화한 뒤 선형으로 되돌리는 것이 좋습니다. - 디더링(Dithering) 추가 결합: 포스터라이제이션으로 끊긴 명암 경계에 4×4 베이어 디더링 패턴을 얇게 얹어주면 게임보이나 PC-98 고전 게임 특유의 점묘화 명암이 완벽히 재현됩니다.