카툰 아포칼립스 셰이더 연출: HLSL과 Shader Graph로 구현하는 어둡고 그로테스크한 비주얼
셀 셰이딩 기법에 명암 양자화, 노이즈 디더링, 역광 림 라이트를 결합하여 기괴하고 어두운 카툰 아포칼립스 분위기를 연출하는 HLSL 및 Shader Graph 구현법을 소개합니다.

개요
일반적인 카툰 렌더링(Cel Shading)은 화사하고 맑은 분위기의 애니메이션 스타일 게임에 주로 사용됩니다. 그러나 셀 셰이딩 특유의 명확한 경계선 표현과 톤 분할 기법을 다르게 응용하면 극도로 어둡고 그로테스크한 카툰 아포칼립스 비주얼을 연출할 수 있습니다. 명암의 거친 단절과 노이즈 기법을 결합하면 그래픽 노블 스타일의 스산하고 절망적인 분위기를 효과적으로 만들어냅니다.
Unity와 Unreal Engine에서 활용할 HLSL 코드와 Shader Graph를 바탕으로, 밝은 셀 셰이딩을 어둡고 기괴한 아포칼립스 분위기로 바꾸는 원리와 구현 방법을 살펴봅니다.
어두운 카툰 아포칼립스 비주얼의 핵심 구성 요소
카툰 아포칼립스 스타일을 구현하려면 일반적인 비등방성 라이팅이나 부드러운 음영 방식과 차별화되는 몇 가지 시각적 요소가 필요합니다.
- 불균일한 명암 양자화(Quantization): 부드러운 음영 그라데이션을 억제하고 2~3단계의 극단적인 명암 경계를 형성합니다.
- 스크린 스페이스 디더링(Dithering) 및 미세 노이즈: 그림자 경계 영역에 거친 기하학적 텍스처나 망점 노이즈를 섞어 그로테스크한 느낌을 강조합니다.
- 핏빛 역광과 림 라이팅(Rim Lighting): 캐릭터나 오브젝트의 윤곽을 조여주는 림 라이트에 어둡고 붉은 톤을 적용하여 스산한 분위기를 더합니다.
- 깊이 및 노멀 기반 아웃라인: 오브젝트 간 경계에 날카로운 외곽선을 생성하여 2D 그래픽 노블의 느낌을 부여합니다.

셰이더 연산 원리 및 수학적 모델
어두운 카툰 연출의 핵심은 광원 벡터와 표면 노멀 벡터의 내적값인 NdotL을 정밀하게 제어하는 과정에 있습니다.
표면 노멀 벡터 과 광원 방향 벡터 의 내적은 다음과 같습니다.
이 기본 내적값을 0과 1 사이로 클램핑한 후 명암 단계를 끊어주는 양자화 연산을 수행합니다. 양자화 단계 수를 라 할 때 양자화 공식은 다음과 같이 정의됩니다.
단순한 단계 구분을 넘어 어두운 분위기를 극대화하기 위해 그림자 영역 에 스크린 스페이스 디더링 매트릭스 를 결합합니다. 이를 반영한 최종 음영 톤 의 식은 다음과 같습니다.
아래 흐름도는 버텍스 노멀 계산부터 최종 픽셀 컬러 합성까지의 연산 단계를 나타냅니다.
flowchart TD
A[표면 노멀 N 및 광원 벡터 L] --> B[NdotL 내적 연산]
B --> C[명암 단계 양자화 S]
C --> D[컬러 램프 텍스처 매핑]
D --> E{그림자 경계 판단}
E -->|경계 영역| F[스크린 스페이스 디더링 연산]
E -->|주음영 영역| G[컬러 톤 다운 및 림 라이트 합성]
F --> H[최종 픽셀 렌더링]
G --> H
HLSL 커스텀 셰이더 구현
HLSL 커스텀 셰이더 코드를 작성하여 위 연산을 구현할 수 있습니다. 부드러운 경계를 제거하고 좁은 스텝 영역을 지정하는 방식의 라이팅 셰이더 코드 예시입니다.
float3 CalculateApocalypseLighting(float3 normal, float3 lightDir, float3 viewDir, float3 lightColor, float2 screenUV, float2 screenSize)
{
normal = normalize(normal);
lightDir = normalize(lightDir);
viewDir = normalize(viewDir);
// NdotL 계산 및 0~1 클램핑
float NdotL = saturate(dot(normal, lightDir));
// 명암 단계 양자화 (3단계 명암 적용)
float steps = 3.0;
float quantizedShadow = floor(NdotL * steps) / steps;
// 스무스스텝을 통한 날카로운 경계 제어
float ramp = smoothstep(0.2, 0.22, quantizedShadow);
// 어두운 영역 디더링 패턴 계산
float ditherPattern[16] = {
0.0, 0.5, 0.125, 0.625,
0.75, 0.25, 0.875, 0.375,
0.1875, 0.6875, 0.0625, 0.5625,
0.9375, 0.4375, 0.8125, 0.3125
};
int2 pixel = int2(floor(screenUV * screenSize));
int index = (pixel.y % 4) * 4 + (pixel.x % 4);
float ditherValue = ditherPattern[index];
float shadowFactor = (ramp < ditherValue) ? 0.2 : 1.0;
// 그로테스크 림 라이트 연산
float NdotV = 1.0 - saturate(dot(normal, viewDir));
float rim = pow(NdotV, 4.0) * (1.0 - ramp);
float3 rimColor = float3(0.6, 0.05, 0.05) * rim; // 붉은 톤의 강한 역광
float3 finalColor = lightColor * shadowFactor + rimColor;
return finalColor;
}
이 코드는 광원과 시선 방향에 따라 날카로운 노이즈 명암과 붉은빛 윤곽선을 동시에 연산합니다.
Shader Graph 노드 구성 및 엣지 디텍션
노드 기반 환경인 Unity Shader Graph나 Unreal Material Editor에서는 Custom Function 노드를 사용하거나 기본 노드의 조합으로 동일한 효과를 구축할 수 있습니다.

외곽선 표현을 위해 렌더링 파이프라인 상에서 Depth 텍스처와 Scene Normal 텍스처를 활용하는 엣지 디텍션(Edge Detection) 기법을 함께 합성합니다.
flowchart LR
SubGraph1[Depth & Normal 텍스처] --> SobelFilter[소벨 필터 엣지 연산]
SobelFilter --> EdgeMask[외곽선 마스크 생성]
SubGraph2[카툰 아포칼립스 라이팅] --> Composite[최종 픽셀 합성]
EdgeMask --> Composite
Shader Graph에서는 Dither 노드에 Screen Position 노드를 연결하고 이를 Multiply 및 Step 노드로 결합하여 스크린 좌표에 고정된 망점 효과를 쉽게 표현할 수 있습니다.
실전 응용 및 성능 최적화
카툰 아포칼립스 셰이더를 실제 게임 프로젝트에 적용할 때 고려해야 할 핵심 최적화 사항은 다음과 같습니다.
- 스크린 스페이스 연산 최적화: 디더링 패턴 연산 시 텍스처 샘플링을 줄이고 4x4 매트릭스를 셰이더 내 배열로 처리하면 ALU 비용을 낮출 수 있습니다.
- 포스트 프로세싱 외곽선과의 병합: 개별 오브젝트 메쉬마다 엣지 디텍션을 수행하면 그리기 호출과 셰이더 연산량이 증가합니다. 외곽선 연산은 렌더링 파이프라인의 후처리 단계로 이관하는 것이 효율적입니다.
- 모바일 렌더링 주의점: 망점 패턴이 낮은 디스플레이 해상도에서 앨리어싱 현상을 일으킬 수 있으므로 Mipmap 기반 노이즈 텍스처를 활용하거나 스크린 해상도 비율에 맞춰 Scale 스칼라를 동적으로 조정해야 합니다.
정리하며
카툰 아포칼립스 셰이더는 셀 셰이딩의 기본 틀을 활용하여 강렬한 절망감과 기괴함을 렌더링하는 기법입니다. 명암 양자화 단계 조정, 스크린 스페이스 디더링, 붉은 림 라이트의 조화는 그래픽 노블 스타일의 어두운 무대를 연출해 줍니다. 셰이더 연산 비용을 고려하여 적절한 후처리 최적화를 도입한다면 독창적인 아트를 가진 완성도 높은 게임을 제작할 수 있습니다.



