TL;DR — 풀 3D 장면을 Pixiv풍 일러스트처럼 보이게 만드는 가장 큰 함정은 “예쁜 스크린샷”이 만들어진다는 점입니다. FPS 카메라로 들어가는 순간 외곽선·텍스처·그림자가 차례로 무너집니다. 오브젝트별 외곽선은 화면 후처리 Sobel(depth+normal)로, 자동 텍스처 후처리는 authored 텍스처로, 검정 그림자는 receiver material의
shadow_colormix로 갈아치우고 나서야 화면이 묶였습니다. 이 글은 그 수렴 과정과 채택된 5개의 룰입니다.
Table of contents
Open Table of contents
들어가며
Pixiv풍 일러스트는 평면적인 색면, 선명한 외곽선, 의도적으로 설계된 그림자, 부드럽지만 선명한 팔레트가 매력입니다. 정지 이미지에서는 비교적 잘 성립합니다. 문제는 플레이어가 직접 카메라를 움직이는 3D 게임에서는 같은 인상이 쉽게 무너진다는 점입니다.
이 글은 작은 실내 테이블탑 씬을 기준으로, 풀 3D 장면이 FPS 카메라 안에서도 일러스트풍으로 읽히게 만드는 조건을 찾는 실험 기록입니다. 카메라, 외곽선, 텍스처, 머티리얼, 그림자, 색보정을 차례로 검증하면서 폐기한 직관과 남긴 룰을 정리합니다.
이 글에서 다루는 내용:
- FPS 카메라가 일러스트풍 3D의 약점을 어떻게 빠르게 드러내는가
- Inverted hull 외곽선을 폐기하고 Sobel(depth+normal) 화면 후처리로 수렴한 과정
- 사진 텍스처 자동 변환 다섯 가지(bilateral, posterize, quantize, Kuwahara, palette remap)를 비교한 결과
- 머티리얼 카테고리 정책과 receiver 기준
shadow_colormix - LUT를 약한 통합 레이어로 두는 이유
사전 지식: Godot 4 셰이더, 화면 후처리, depth/normal buffer, 머티리얼 정책에 대한 기본적인 이해를 전제로 합니다. 구현 세부사항보다 의사결정 흐름 중심으로 정리합니다.
1. 풀 3D에서 일러스트풍이 무너지는 지점
이번 실험의 첫 결정은 “어디서 약점을 측정할 것인가”였습니다. 정지된 직교 카메라에서는 어떤 방식이든 어느 정도 그럴듯해 보입니다. 그래서 검증 기준 카메라를 직교가 아니라 FPS로 잡았습니다. FPS는 가까운 거리에서 큰 빈 면, 사진 질감의 이질감, 외곽선 떨림, 그림자 과잉을 가장 먼저 드러내는 조건입니다.
같은 장면을 직교, TPS, FPS 세 카메라로 비교했고, 외곽선 알고리즘을 적용하기 전 baseline FPS 화면을 먼저 잡았습니다.

가까운 거리에서는 벽과 바닥의 큰 면이 비어 보였고, 오브젝트 경계가 일러스트처럼 읽히지 않았습니다. TPS와 직교에서는 어느 정도 분위기가 유지되어도 FPS에서는 단순한 3D 프리미티브 인상이 빠르게 드러났습니다. 이 결과 때문에 이후 모든 실험의 1차 판정 기준은 “FPS에서 버티는가”로 고정했습니다.
2. 외곽선: 두 번 갈아엎고 화면 후처리로 수렴
2.1 Inverted hull은 자유 카메라에서 부담이 컸습니다
첫 후보는 Inverted hull 계열의 오브젝트별 외곽선이었습니다. 오브젝트 바깥쪽에 외곽선용 형태를 하나 더 두고 어두운 색으로 렌더링하는 익숙한 방식입니다.
직교 카메라에서는 충분히 작동했습니다.

같은 방식을 FPS로 옮기자 곧장 한계가 보였습니다.

문제는 두 가지였습니다. 첫째, 카메라 각도에 따라 외곽선이 표면에서 떠 보이는 순간이 생겼습니다. 둘째, 외곽선 두께·색·제외 대상·작은 소품 처리를 오브젝트마다 수동 관리해야 했습니다. 자유 카메라가 들어가는 게임에서 모든 메시에 외곽선용 형태와 파라미터를 붙이는 비용은 너무 컸습니다. 고정 카메라 연출용으로는 남기되, 자유 카메라용 주 경로에서는 폐기했습니다.
2.2 Sobel 커널을 색이 아닌 depth와 normal에 적용했습니다
두 번째 후보는 화면 후처리 외곽선이었습니다. 개별 오브젝트에 선을 붙이는 대신 최종 화면에서 경계를 검출합니다.
핵심 결정은 Sobel 커널의 입력이었습니다. 일반적인 이미지 처리에서는 밝기에 적용하지만, 3D 화면에서 색만 보면 같은 색면 안의 형태 경계를 놓치고, 노이즈가 많은 텍스처에서는 가짜 선이 잡힙니다. 그래서 입력을 두 가지로 분리했습니다.
- Depth 변화: 오브젝트 실루엣과 앞뒤 관계를 잡습니다.
- Normal 변화: 깊이 차이가 작아도 면이 꺾이는 경계(벽/바닥, 상판/측면)를 잡습니다.
두 결과를 합쳐야 하는 이유는 단순합니다. depth만 쓰면 같은 메시 내부의 면 꺾임을 놓치고, normal만 쓰면 앞뒤로 떨어진 오브젝트 실루엣을 놓치기 때문입니다.
같은 FPS 조건에 적용한 결과입니다.

디버그 패스에서 어떤 입력에서 어떤 경계가 나왔는지 색으로 분리해서 확인했습니다.

추가로 가까운 카메라에서 모든 경계가 두껍게 보이는 문제를 막기 위해 거리 기반 완화를 넣었습니다. 가까이 있는 모든 선이 두껍게 보이면 FPS 화면이 빠르게 지저분해지기 때문입니다.
이 시점에서 외곽선 문제는 일단 닫혔습니다. 하지만 외곽선이 안정되어도 큰 면이 비어 보이는 문제와 사진 텍스처가 튀는 문제는 그대로 남아 있었습니다. 그 두 가지는 외곽선이 풀 수 있는 문제가 아니었습니다.
3. 텍스처: 자동 변환은 밑그림이고, 최종은 authored
3.1 외부 자산 자동 변환 다섯 가지를 비교했습니다
씬에 들어온 라디오 같은 외부 PBR/사진 기반 prop은 그대로 두면 한 화면 안에서 혼자 사실적으로 보입니다. 자동 후처리로 톤만 맞출 수 있는지 확인하기 위해 다섯 가지 방향을 모두 시도했습니다.
| 방식 | 입력 | 결과 | 판정 |
|---|---|---|---|
| Bilateral | 픽셀 + 주변 색 가중치 | 경계 보존하면서 표면 노이즈 감소. 구조는 살았지만 실사 인상도 같이 남음 | support |
| Posterize | 명암 단계 축소 | 빠르게 단순해지지만 원본의 오염·경계도 같이 남김 | support |
| Quantize | 색 수 축소 | 라디오 기준 부품 구조가 뭉개짐 | 미채택 |
| Kuwahara | 회화적 면 평탄화 | 사진 인상은 줄지만 부품 구조까지 유화처럼 녹음 | 미채택 |
| Palette remap (전역) | RGB nearest mapping | 의도하지 않은 영역까지 색이 이동 | 미채택 |
| Palette remap (masked) | SAM 마스크 기반 부위별 remap | 부품별로 톤 통제 가능. 채택 | adopted support |
채택된 경로는 마스크 기반 부분 remap + 필요 시 부위별 스크립트 repaint 조합이었습니다. 자동 변환은 최종 텍스처를 만드는 도구가 아니라 외부 자산을 빠르게 톤 정렬(tone alignment)하기 위한 입력으로 정리했습니다.
자동 변환을 통합한 텍스처 통합 패스의 FPS 화면입니다.

3.2 Authored 텍스처가 큰 색면을 책임집니다
자동 변환만으로는 한계가 분명했습니다. 종이, 카드, 앨범, 포스터, 벽, 바닥처럼 화면의 큰 색면을 만드는 대상은 처음부터 직접 설계한 텍스처를 사용했습니다.
paper/card material을 적용한 화면입니다.

album card는 한 번에 만들어지지 않았습니다. mesh-only → texture pass → supersample → grain 순으로 단계별 evidence를 남겼고, edge aliasing과 종이 질감을 동시에 잡기까지 시작과 끝 사이의 차이가 다음과 같습니다.

벽과 책상처럼 넓은 표면은 강한 패턴 대신 부드러운 mottle/noise grain만 사용했습니다. 긴 직선 fiber stroke는 빠르게 라인 아티팩트로 보입니다.

3.3 Alpha card는 cutout을 기본 후보로 두었습니다
포스터처럼 “테두리가 비치는” 얇은 카드를 만들 때는 두 가지 방식이 있습니다.
- Alpha cutout (alpha scissor): 픽셀의 alpha 값이 임계값(예: 0.5)을 넘으면 그리고, 아니면 버립니다(
discard). 결과는 완전 불투명 픽셀과 완전 빈 픽셀만 남는 이분법입니다. depth buffer에 정상적으로 기록되므로 정렬이 필요 없고, 외곽선/그림자 같은 후속 패스가 그대로 동작합니다. 단점은 가장자리가 계단처럼 보일 수 있다는 점입니다. Godot 셰이더의ALPHA_SCISSOR_THRESHOLD가 이 동작을 제어합니다. - Alpha blend: alpha 값을 그라데이션으로 섞어 부드러운 반투명을 표현합니다. 유리, 연기, 얇은 천처럼 진짜 반투명이 필요할 때 적합하지만, 비용이 큽니다. 반투명 오브젝트는 뒤에서 앞 순서로 그려야 결과가 맞기 때문에 깊이 정렬 비용이 발생하고, 보통 별도의 transparent render queue로 분리되어 추가 비용이 듭니다. 그리고 Sobel 같은 화면 후처리 패스가 반투명 픽셀의 depth/normal을 어떻게 처리할지 애매해져 외곽선·그림자와 충돌하기 쉽습니다.
포스터/카드처럼 가장자리가 약간 거칠어도 되고 진짜 반투명이 필요 없는 케이스는 cutout으로 충분합니다. 그래서 처음부터 cutout을 기본 후보로 잡고, 진짜 반투명이 필요한 경우에만 blend를 별도 검증으로 미뤘습니다. blend를 채택했다가 폐기한 것이 아니라, 알려진 비용과 패스 충돌을 피하기 위해 처음부터 cutout을 선택한 결정입니다.

4. 머티리얼 카테고리: shader가 아니라 정책으로 묶기
검증을 진행할수록 새 셰이더를 늘리는 방향은 확장이 안 됐습니다. 같은 셰이더라도 가구·소품·종이·장비·얇은 카드는 색면 책임이 서로 달라야 했습니다. 그래서 셰이더 종류를 늘리는 대신 머티리얼 카테고리별 uniform 정책으로 묶었습니다.
| Category | 색면 정책 | shadow 정책 |
|---|---|---|
| Architecture (벽/바닥) | 큰 빈 면 회피, 부드러운 grain | 낮은 contrast, warm violet/rose |
| Furniture (책상/의자/러그) | category 팔레트 기준 색면 | mauve/brown 계열, texture grain 보존 |
| Props (라디오/램프/화분) | masked remap으로 톤 정렬 | 낮은 mix |
| Paper / Card | warm cream, coral, teal, mustard 큰 색면 | 약하거나 off |
| Device / Metal | neutral gray 회피, muted blue-gray / warm charcoal | blue-gray 계열 |
| Alpha Card | cutout 기본 | 카드 자체에는 약하게 |
채택된 정책을 통합한 머티리얼 closeout FPS 화면입니다.

이 결정의 실무적 장점은 셰이더 그래프 트리를 키우지 않고도 외부 자산을 카테고리에 정렬해 검수할 수 있다는 점입니다. 새 prop이 들어오면 “어떤 셰이더에 붙일까”가 아니라 “어떤 카테고리인가”부터 묻습니다.
5. 그림자: caster가 아니라 receiver의 책임
엔진 cast shadow를 그대로 최종 색으로 믿는 방식은 빠르게 한계에 부딪혔습니다. 검정 곱셈은 색면을 죽이고, 화면 전체에 hue rotation을 거는 방식은 그림자만 따로 통제할 수 없었습니다.
이번에 채택한 정책은 다음입니다.
engine shadow = mask / source
material shadow = receiver category palette color
ShadowPatch = authored graphic depth plane (보조)
look tooling = category profile 기반 자동 적용 (후속)
핵심은 보이는 그림자색을 caster가 아니라 receiver material이 결정한다는 점입니다. 책상이 러그 위에 그림자를 만들더라도, 화면 픽셀은 러그 표면입니다. 따라서 그 픽셀의 그림자색은 책상이 아니라 러그의 카테고리 팔레트에서 옵니다.
전용 셰이더(flat_one_step_shadow_color_lit, flat_one_step_textured_shadow_color_lit)에서 shadow_color와 shadow_mix를 receiver 기준으로 합성했습니다. warm receiver는 rose/mauve/muted violet, cool/device receiver는 blue-gray/teal/charcoal을 target으로 잡았습니다.

검정 곱셈처럼 딱딱하게 꺼지지 않고, desk/floor texture grain이 그림자 영역에서도 죽지 않습니다. 화면 전체가 pink filter처럼 덮이지 않는 범위 안에서 자연스러운 후보로 유지됩니다.
기존에 사용하던 ShadowPatch(authored graphic shadow plane)는 폐기하지 않고 보조 도구로 남겼습니다. material shadow만으로 접지감이 부족한 경우에 배치하는 별도 graphic layer로 책임을 분리했습니다.
6. LUT: 약한 통합 레이어 이상은 위험합니다
마지막 실험은 1D/3D LUT 기반 색보정이었습니다.

약한 LUT는 안전했지만 차이가 작았고, 강한 인쇄 톤은 분위기를 만들 수 있었지만 책상이나 벽이 한쪽 색으로 밀리는 위험이 컸습니다. 디버그 목적으로 극단 LUT를 걸어 보면 한 번에 알 수 있습니다.

결론은 단순했습니다. LUT는 머티리얼·그림자 정책을 대체하지 않고, 이미 맞춰진 장면을 약하게 묶는 통합 레이어로만 둡니다. 약한 자산 색이나 잘못된 그림자를 LUT로 고치려 들면 다음 자산이 들어왔을 때 다시 깨집니다.
7. 수렴한 파이프라인
여섯 단계의 검증을 거치고 남은 룰은 다음 다섯 가지입니다.
- 검증 카메라는 FPS를 기본으로 둡니다. 직교/TPS에서 통과한 결과를 신뢰하지 않습니다.
- 외곽선은 화면 후처리(Sobel, depth+normal 합성, 거리 기반 완화)를 기본 경로로 둡니다. 오브젝트별 외곽선은 고정 카메라 연출용으로만 남깁니다.
- 외부 자산은 마스크 기반 부분 remap으로 톤 정렬만 하고, 화면의 큰 색면은 authored 텍스처가 책임집니다. 자동 변환은 최종 제작기가 아닙니다.
- 머티리얼은 셰이더 종류가 아니라 카테고리별 uniform 정책으로 관리합니다. 새 자산은 “어떤 카테고리인가”부터 묻습니다.
- 그림자는 receiver material의
shadow_colormix로 설계합니다. 엔진 cast shadow는 mask로 쓰고, ShadowPatch는 보조 graphic layer로 분리합니다. LUT는 약한 통합 레이어로만 둡니다.
8. 남은 한계
이번 검증은 그래픽 방향을 정리하는 데에는 유용했지만, 다음 한계가 남아 있습니다.
- 작은 실내 테이블탑 씬과 사물 중심 자산만 다루었습니다. 외부 공간, 복잡한 건축, 다양한 조명 상황은 다루지 못했습니다.
- 캐릭터, 손, 얼굴, 머리카락, 옷 주름, 걷기 애니메이션처럼 움직이는 대상은 이번 범위에 없습니다. 움직이는 실루엣에서는 외곽선과 그림자가 다르게 깨질 수 있습니다.
- 처음부터 일러스트풍 3D를 목표로 한 production 모델링·UV·머티리얼로는 다시 검증해야 합니다. 이번에는 placeholder/free asset을 섞어 사용했습니다.
정리하며
이번 실험의 결론은 “하나의 강한 효과로 일러스트풍이 만들어지지 않는다”는 점입니다. 외곽선·텍스처·머티리얼·그림자·LUT 중 어느 하나라도 정책이 비면 FPS 카메라에서 같은 자리가 다시 무너졌습니다.
수렴한 다섯 가지 룰의 공통 패턴은 caster가 아니라 receiver, shader가 아니라 카테고리, 효과가 아니라 정책으로 책임을 옮긴다는 점입니다. 그래야 외부 자산이 들어와도 같은 기준으로 통합할 수 있고, 실패한 자산을 LUT로 가리지 않게 됩니다.
핵심 요약:
- 일러스트풍 3D의 약점은 FPS 카메라에서 가장 빨리 드러납니다. 검증 기준 카메라를 FPS로 고정해야 합니다.
- 외곽선은 오브젝트별 inverted hull이 아니라 화면 후처리 Sobel(depth+normal)이 자유 카메라 조건에서 안정적입니다.
- 사진 텍스처 자동 후처리(bilateral, posterize, quantize, Kuwahara)는 최종 제작기가 아니라 톤 정렬용 입력입니다.
- 머티리얼은 셰이더 종류가 아니라 카테고리별 uniform 정책으로 관리해야 확장됩니다.
- 그림자색은 caster가 아니라 receiver category palette가 결정하고, LUT는 약한 통합 레이어 이상으로 쓰지 않습니다.
다음 단계는 실험을 더 늘리는 것이 아니라, 실제 게임 콘셉트와 첫 production 후보 자산 세트를 좁히는 일입니다. 그때부터는 위 다섯 룰이 제작 파이프라인의 기준선이 됩니다.
참고 자료
- Kenney — Furniture Kit — 책상, 의자, 러그, 벽/바닥 등 베이스라인 가구 자산 (CC0)
- Sketchfab — 시티팝 소품 (램프, 화분, 라디오) — 프로젝트에서 톤 정렬 대상으로 사용한 외부 PBR 자산
- Sobel operator — depth/normal buffer 기반 화면 후처리 외곽선의 표준 입력
- Kuwahara filter — 회화적 면 평탄화 비교용
- Godot 4 spatial shader —
ALPHA_SCISSOR_THRESHOLD기반 alpha cutout
이 게시물은 프로젝트 그래픽 검증 문서를 바탕으로 초안을 작성한 뒤, LLM의 도움을 받아 구조와 문체를 다듬었습니다.