TOOLS GAMES DEVLOG DOCS ABOUT CONTACT
HOME > DOCS > Shader / URP > 3D 픽셀 아트용 컬러 팔레트 양자화(LUT Posterization) 및 1px 도트 외곽선 셰이더
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 픽셀 아트 종합 포스트 프로세싱 셰이더

PixelArtPostProcess.hlsl
// 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 고전 게임 특유의 점묘화 명암이 완벽히 재현됩니다.