Custom Post-Processing으로 구현하는 VHS/Glitch 사이버펑크 화면 효과

Unity URP 또는 HLSL 기반 커스텀 포스트 프로세싱에서 스캔라인, RGB 채널 분리, 노이즈, 화면 흔들림을 결합해 성능 부담을 제어하며 VHS/Glitch 사이버펑크 화면 효과를 구현하는 방법을 정리합니다.

✓ 복사 완료!
챕터셰이더 프로그래밍 (HLSL/ShaderGraph)
난이도중급
상태● 검토 완료
대상 엔진
Engine Agnostic
최종 업데이트
Custom Post-Processing으로 구현하는 VHS/Glitch 사이버펑크 화면 효과

핵심 요약

VHS/Glitch 화면 효과는 최종 화면 색상에 시간 기반 노이즈, UV 좌표 왜곡, RGB 채널별 샘플 오프셋, 스캔라인을 순서대로 적용하는 커스텀 포스트 프로세싱으로 구현할 수 있다. 핵심은 모든 효과를 항상 최대로 적용하는 것이 아니라 왜곡 마스크와 강도 파라미터로 제어해 읽기 쉬운 화면과 사이버펑크 특유의 불안정한 신호를 함께 유지하는 것이다.

VHS/Glitch 포스트 프로세싱은 왜 화면을 더 사이버펑크답게 만드는가?

사이버펑크 화면은 네온 조명만으로 완성되지 않는다. 감시 카메라, 저품질 디스플레이, 손상된 통신 신호처럼 보이는 불완전한 화면은 세계가 안정적이지 않다는 인상을 준다. VHS/Glitch 효과는 최종 렌더 결과를 직접 변형하므로 특정 오브젝트에 머티리얼을 추가하지 않아도 장면 전체의 분위기를 빠르게 바꿀 수 있다.

다만 포스트 프로세싱은 UI와 텍스트까지 함께 훼손할 수 있다. 게임플레이 화면에는 약한 상시 효과를 사용하고 피해 연출·해킹·장면 전환처럼 짧은 이벤트에서만 강한 글리치를 사용하는 편이 안전하다.

RGB 채널 분리와 수평 신호 왜곡이 적용된 사이버펑크 도시 화면 예시

효과별 역할과 권장 범위

효과구현 방식시각적 역할권장 시작값
스캔라인sin(uv.y * lineCount)CRT/VHS의 수평 주사선강도 0.03~0.10
RGB 채널 분리채널마다 다른 X 좌표 샘플링색수차와 신호 불안정0.5~2.0 px
수평 지터행 단위 노이즈로 uv.x 이동테이프 추적 오차0.001~0.01
블록 글리치시간 단위 마스크로 영역 분리디지털 신호 손상이벤트성 사용
그레인화면 좌표 해시 노이즈저품질 기록 매체 질감0.02~0.08
비네트중심에서 멀수록 감광화면 집중도 강화0.05~0.20

Custom Post-Processing 효과는 어떻게 구성할까?

효과는 다음 순서로 합성하면 제어하기 쉽다. 먼저 UV를 왜곡하고 왜곡된 좌표에서 색상을 샘플링한 뒤 마지막에 스캔라인과 그레인을 곱하거나 더한다. 색상 샘플링 전에 스캔라인을 넣으면 채널 분리 결과까지 더 복잡하게 흔들려 의도하지 않은 깜빡임이 생길 수 있다.

flowchart LR
    A[카메라 컬러 텍스처] --> B[시간 기반 글리치 마스크]
    B --> C[UV 수평 지터 및 블록 왜곡]
    C --> D[RGB 채널별 텍스처 샘플링]
    D --> E[스캔라인·그레인·비네트]
    E --> F[최종 화면 출력]

Step 1. 시간과 화면 좌표로 재현 가능한 노이즈를 만든다

텍스처를 추가로 샘플링하지 않아도 되는 해시 함수는 가벼운 글리치 마스크에 적합하다. 아래 함수는 동일한 입력에 같은 난수를 반환하므로 시간 값을 양자화하면 몇 프레임 동안 유지되는 손상 구간을 만들 수 있다.

float Hash21(float2 p)
{
    p = frac(p * float2(123.34, 456.21));
    p += dot(p, p + 45.32);
    return frac(p.x * p.y);
}

float GlitchMask(float2 uv, float time, float rate, float threshold)
{
    float timeStep = floor(time * rate);
    float row = floor(uv.y * 180.0);
    float randomValue = Hash21(float2(row, timeStep));
    return step(threshold, randomValue);
}

floor(time * rate)가 중요하다. time을 그대로 사용하면 매 픽셀에서 노이즈가 계속 변해 흰색 플리커처럼 보인다. 반면 rate를 8~20 정도로 제한하면 손상 구간이 짧게 유지되어 디지털 글리치에 가까운 리듬이 생긴다.

Step 2. 수평 왜곡과 RGB 채널 분리를 적용한다

VHS 효과의 중심은 Y축 위치에 따라 달라지는 X축 이동이다. 행 단위 난수와 사인파를 함께 사용하면 너무 규칙적이지 않은 수평 흔들림을 만들 수 있다.

uvx′=uvx+m⋅(n(y,⌊tr⌋)−0.5)⋅auv_x' = uv_x + m \cdot \left(n(y, \lfloor tr \rfloor) - 0.5\right) \cdot a

여기서 mm은 글리치 마스크, nn은 난수, rr은 갱신 속도, aa는 왜곡 강도다. 마스크가 0인 영역은 원래 UV를 유지하므로 화면 전체가 지속적으로 흔들리는 문제를 막을 수 있다.

TEXTURE2D_X(_BlitTexture);
SAMPLER(sampler_LinearClamp);

float4 Frag(Varyings input) : SV_Target
{
    float2 uv = input.texcoord;
    float time = _Time.y;

    float mask = GlitchMask(uv, time, _GlitchRate, _GlitchThreshold);
    float rowNoise = Hash21(float2(floor(uv.y * 180.0), floor(time * _GlitchRate)));
    float wave = sin(uv.y * 420.0 + time * 14.0);
    float xOffset = mask * (rowNoise - 0.5 + wave * 0.25) * _JitterStrength;

    float2 distortedUv = saturate(uv + float2(xOffset, 0.0));
    float chromaOffset = _ChromaticAberration * (0.5 + mask);

    float red = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp,
        saturate(distortedUv + float2(chromaOffset, 0.0))).r;
    float green = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, distortedUv).g;
    float blue = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp,
        saturate(distortedUv - float2(chromaOffset, 0.0))).b;

    return float4(red, green, blue, 1.0);
}

_ChromaticAberration은 UV 단위이므로 해상도에 따라 체감 강도가 달라진다. 해상도 독립적인 픽셀 단위 제어가 필요하면 화면 크기 역수를 곱한다.

float2 pixelOffset = float2(_ChromaticPixels, 0.0) * _ScreenSize.zw;

Step 3. 스캔라인과 그레인으로 화면을 마감한다

스캔라인은 색상을 완전히 검게 자르는 방식보다 미세한 밝기 변조가 좋다. 밝은 네온 영역의 계조를 보존하면서도 디스플레이 질감을 추가할 수 있기 때문이다.

float Scanline(float2 uv, float lineCount, float strength)
{
    float line = sin(uv.y * lineCount * 3.14159265);
    return lerp(1.0, 0.5 + 0.5 * line, strength);
}

float Grain(float2 uv, float time)
{
    float2 grainUv = uv * _ScreenSize.xy + floor(time * 24.0);
    return Hash21(grainUv) - 0.5;
}

// RGB 샘플링 이후 적용
float scanline = Scanline(uv, _ScanlineCount, _ScanlineStrength);
float grain = Grain(uv, _Time.y) * _GrainStrength;
color.rgb *= scanline;
color.rgb += grain;

그레인은 장면의 어두운 영역에서 특히 잘 보인다. 톤 매핑 이후에 과하게 더하면 검은색이 회색으로 떠 보일 수 있으므로 color.rgb += grain * luminanceMask처럼 휘도 마스크를 곱해 밝은 영역 위주로 제한하는 방법도 유용하다.

약한 상시 VHS 효과와 강한 이벤트성 글리치 효과의 강도 비교

Unity URP에서 커스텀 포스트 프로세싱을 연결하는 방법

Unity URP에서는 ScriptableRendererFeature와 ScriptableRenderPass로 전체 화면 블릿 패스를 추가할 수 있다. URP 버전에 따라 Render Graph API 사용 여부가 다르므로 프로젝트의 URP 버전에 맞는 렌더 패스 구현을 선택해야 한다. 셰이더 자체는 Blitter.BlitCameraTexture 또는 동등한 전체 화면 블릿 경로에서 사용할 수 있다.

설정값은 Volume 컴포넌트 또는 ScriptableObject로 노출한다. 게임 중 자주 바뀌는 값은 Volume 파라미터가 편하고 특정 카메라 전용 연출은 Renderer Feature의 머티리얼 파라미터가 단순하다.

파라미터의미게임플레이 권장값이벤트 연출 권장값
_JitterStrength수평 UV 이동량0.001~0.0030.01 이상
_GlitchRate마스크 갱신 빈도4~1012~24
_GlitchThreshold글리치가 생기는 기준값0.90 이상0.65~0.85
_ChromaticPixels채널 분리 픽셀 수0.5~1.02.0~6.0
_ScanlineStrength스캔라인 명암 변화0.03~0.080.10~0.20

VHS 셰이더가 UI와 성능을 망치지 않으려면?

UI는 별도 카메라 또는 별도 렌더 단계로 분리한다

HUD, 자막, 조준점은 정보 전달이 목적이므로 강한 채널 분리와 흔들림을 적용하면 안 된다. 가장 단순한 방법은 UI를 Screen Space Overlay Canvas로 두어 카메라 포스트 프로세싱 이후에 그리는 것이다. 월드 공간 UI까지 효과에서 제외해야 한다면 UI 전용 카메라를 사용하거나 효과 적용 전후의 렌더 순서를 명확히 분리한다.

한 패스와 최소 샘플링을 우선한다

RGB 분리에 필요한 기본 샘플 수는 3회다. 여기에 블러, 색 보정, 별도 노이즈 텍스처를 무심코 더하면 모바일이나 Steam Deck급 기기에서 후처리 비용이 빠르게 늘어난다.

  1. 기본 컬러 텍스처는 R/G/B 채널 분리를 위해 최대 3회만 샘플링한다.
  2. 노이즈는 가능하면 수학적 해시로 만들고 고품질 필름 그레인이 필요할 때만 작은 타일 텍스처를 쓴다.
  3. 블록 글리치, 강한 흔들림, 프레임 홀드는 상시 효과가 아니라 이벤트 시간에만 활성화한다.
  4. 프로파일러에서 GPU 프레임 시간과 전체 화면 패스 수를 함께 확인한다.

해상도와 업스케일러를 고려한다

TAA, FSR, DLSS 같은 업스케일링 또는 시간 축적 기법 이후에 매 프레임 무작위 글리치를 강하게 적용하면 잔상이나 깜빡임이 부각될 수 있다. 시간적으로 완전히 랜덤한 노이즈보다 양자화된 시간 스텝, 낮은 강도, 짧은 이벤트 버스트가 안정적이다.

자주 묻는 질문 (FAQ)

VHS 효과는 Bloom이나 Color Grading보다 앞에 적용해야 할까?

대체로 색 보정과 Bloom이 끝난 최종 색상에 VHS 효과를 적용하는 편이 직관적이다. 다만 글리치 중에도 Bloom이 색 채널을 따라 번지는 연출이 필요하면 채널 분리만 Bloom 이전에 두는 별도 실험이 필요하다.

RGB 채널 분리는 왜 화면 가장자리에서 더 거슬리는가?

UV 오프셋이 화면 밖으로 나가면 Clamp 샘플러가 가장자리 색을 늘려 보이게 한다. saturate()로 좌표를 제한하고 가장자리에서 오프셋을 감쇠하는 마스크를 적용하면 완화된다.

모든 글리치를 랜덤으로 만들면 더 자연스럽지 않은가?

그렇지 않다. 무작위 변화가 너무 빠르면 신호 오류보다 압축 노이즈나 화면 깜빡임처럼 보인다. 시간 스텝과 행 또는 블록 단위 마스크를 사용해 변화의 지속 시간을 만들면 훨씬 읽기 쉬운 글리치가 된다.

정리

좋은 VHS/Glitch 포스트 프로세싱은 효과를 많이 쌓는 것이 아니라 수평 왜곡·RGB 채널 분리·스캔라인의 역할을 분리하고 상황별 강도를 조절하는 데서 시작한다. 먼저 약한 상시 프로파일을 만든 뒤 해킹·피격·전환 연출에만 글리치 마스크와 채널 오프셋을 크게 올리면 분위기와 플레이 가독성을 함께 지킬 수 있다.