TOOLS GAMES DEVLOG DOCS ABOUT CONTACT
HOME > DOCS > Shader / URP > 3D 픽셀 아트 카메라의 픽셀 떨림(Pixel Creep) 방지와 서브픽셀 스냅(Texel Snapping)
Shader / URP Advanced Unity 2022.3+ / Unity 6 (URP)

3D 픽셀 아트 카메라의 픽셀 떨림(Pixel Creep) 방지와 서브픽셀 스냅(Texel Snapping)

저해상도 픽셀 렌더링에서 카메라 이동 시 도트 외곽선이 울렁이는 Shimmering 문제를 월드 텍셀 그리드 스냅과 디스플레이 UV 미세 오프셋으로 해결하는 t3ssel8r 핵심 기법.

#Shader #URP #PixelArt #TexelSnapping #CameraSmoothing #t3ssel8r
핵심 개념3D를 저해상도 RenderTexture로 렌더링할 때 가장 큰 문제는 '픽셀 기어오름(Pixel Creep / Shimmering)'입니다. 카메라가 0.1픽셀 단위로 이동하면 도트 래스터라이저가 3D 기하학을 해석할 때 픽셀이 찌그러지며 화면 전체가 자글거리게 됩니다. 이를 해결하기 위해 카메라 월드 위치는 정확한 텍셀 단위로 절삭(Snap)하고, 잘려 나간 소수점 오프셋만큼 최종 화면의 UV를 반대로 미세 이동시키는 것이 *t3ssel8r*가 정립한 마스터클래스 기법입니다.

1. 텍셀 스냅 + 서브픽셀 오프셋 카메라 스크립트

TexelSnappedCamera.cs
using UnityEngine;
using UnityEngine.UI;

public class TexelSnappedCamera : MonoBehaviour
{
    [SerializeField] private Transform followTarget;
    [SerializeField] private Material fullScreenBlitMaterial;
    [SerializeField] private float pixelsPerUnit = 16f;

    private Camera _cam;
    private static readonly int SubpixelOffsetProperty = Shader.PropertyToID("_SubpixelOffset");

    private void Awake()
    {
        _cam = GetComponent<Camera>();
    }

    private void LateUpdate()
    {
        if (followTarget == null) return;

        Vector3 realPosition = followTarget.position;
        float unitsPerPixel = 1f / pixelsPerUnit;

        // 1. 카메라의 실제 목표 위치를 텍셀 그리드로 스냅(절삭)
        float snappedX = Mathf.Floor(realPosition.x / unitsPerPixel) * unitsPerPixel;
        float snappedY = Mathf.Floor(realPosition.y / unitsPerPixel) * unitsPerPixel;

        // 2. 절삭 과정에서 버려진 서브픽셀(0.0 ~ 1.0 텍셀) 오프셋 산출
        float subpixelX = (realPosition.x - snappedX) / unitsPerPixel;
        float subpixelY = (realPosition.y - snappedY) / unitsPerPixel;

        // 3. 카메라는 도트 형태가 깨지지 않도록 스냅된 위치에 배치
        transform.position = new Vector3(snappedX, snappedY, transform.position.z);

        // 4. 잘려 나간 부드러운 소수점 오프셋을 최종 풀스크린 셰이더로 전달 (60fps 부드러움 달성)
        if (fullScreenBlitMaterial != null)
        {
            fullScreenBlitMaterial.SetVector(SubpixelOffsetProperty, new Vector4(subpixelX, subpixelY, 0, 0));
        }
    }
}

2. 서브픽셀 UV 오프셋 보정 풀스크린 셰이더

SubpixelBlit.shader
Shader "Custom/SubpixelBlit"
{
    Properties
    {
        _MainTex ("Source Low-Res Texture", 2D) = "white" {}
        _SubpixelOffset ("Subpixel Offset (X, Y)", Vector) = (0, 0, 0, 0)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
        ZWrite Off ZTest Always Cull Off

        Pass
        {
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment Frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex_Point);
            float4 _MainTex_TexelSize;
            float4 _SubpixelOffset;

            Varyings Vert(Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                output.uv = input.uv;
                return output;
            }

            half4 Frag(Varyings input) : SV_Target
            {
                // 소수점 오프셋만큼 UV를 역방향으로 부드럽게 시프트
                float2 uvOffset = _SubpixelOffset.xy * _MainTex_TexelSize.xy;
                float2 smoothedUV = input.uv + uvOffset;

                // 포인트 필터링된 저해상도 도트 컬러 샘플링
                half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex_Point, smoothedUV);
                return col;
            }
            ENDHLSL
        }
    }
}

3. 결과 및 비교

  • 스냅 미적용: 카메라 이동 시 3D 모델의 1픽셀짜리 칼날이나 눈 코 입이 굵어졌다 얇아졌다 떨리는 극심한 Shimmering 발생.
  • 단순 위치 스냅만 적용: 떨림은 잡히지만, 카메라가 1텍셀(1/16 유닛) 단위로 뚝뚝 끊기면서 점프하여 스크롤이 심하게 덜덜거림.
  • 스냅 + 서브픽셀 UV 보정: 도트 내부 왜곡을 100% 억제하면서도 모니터 주사율에 맞춰 실크처럼 부드러운 스크롤을 양립.

참고자료 & 관련 링크