그래픽스 프로그래밍, 그림으로 이해하기
수식만 보면 어렵지만, 눈으로 보면 쉽습니다. 이 가이드의 모든 개념에는 직접 만져보는 인터랙티브 데모가 있습니다. 슬라이더를 움직이고, 점을 드래그하면서 "아 이게 이런 거였구나"를 느껴보세요.
개념을 넘어 직접 코드로 구현하고 싶다면 → C++ / DirectX 11 구현 가이드
벡터 (Vector)
그래픽스의 알파벳. 위치, 방향, 속도, 색깔까지 전부 벡터로 표현합니다.
벡터를 C++로 한 줄 한 줄 직접 구현하기벡터는 그냥 숫자 묶음이에요. 2D에서는 (x, y) 두 개,
3D에서는 (x, y, z) 세 개죠. 이걸 두 가지로 볼 수 있어요:
- 위치: 원점(0,0)에서 어디에 있는가 → 하나의 점
- 방향+크기: 어느 쪽으로 얼마나 → 하나의 화살표
파란 점 A, 초록 점 B를 드래그해보세요. 노란 벡터가 A+B입니다.
A + B는 "A만큼 간 뒤, 이어서 B만큼 더 가기"입니다.
각 성분(x끼리, y끼리)을 그냥 더하면 돼요.벡터의 길이와 정규화
벡터의 길이(크기)는 피타고라스 정리로 구합니다:
|v| = √(x² + y²). 길이를 1로 만든 벡터를
단위 벡터(정규화, normalize)라고 하고, "순수한 방향"만 남긴 것입니다.
조명 계산, 카메라 방향 등에서 항상 쓰여요.
화살표 끝을 드래그하세요. 회색은 원본, 노란색은 길이 1로 정규화한 벡터입니다.
행렬 (Matrix)
벡터를 한꺼번에 이동·회전·확대하는 "변환 기계".
행렬을 C++로 한 줄 한 줄 직접 구현하기행렬은 숫자를 격자로 늘어놓은 것입니다. 그래픽스에서 행렬의 진짜 의미는 "공간을 통째로 변형시키는 규칙"이에요. 어떤 점에 행렬을 곱하면 그 점이 회전하거나, 커지거나, 밀려납니다.
2×2 행렬은 격자(공간)의 기저 벡터 두 개를 정하는 것과 같아요. 아래에서 슬라이더로 행렬의 네 숫자를 바꾸면 공간 전체가 어떻게 휘는지 보이죠.
좌표계와 변환
이동(Translate) · 회전(Rotate) · 확대(Scale), 그리고 순서의 중요성.
변환 행렬을 C++로 한 줄 한 줄 직접 구현하기물체를 화면에 놓는 것은 세 가지 기본 변환의 조합입니다. 중요한 건 순서예요. "회전 후 이동"과 "이동 후 회전"은 완전히 다른 결과를 만듭니다.
회전 각도와 이동량을 바꾸고, 순서 버튼을 눌러 차이를 확인하세요.
1을 하나 더 붙여 (x, y, 1)로 만들면, 이동까지도
하나의 3×3 행렬 곱으로 통일할 수 있습니다. 3D에서는 (x,y,z,1)과 4×4 행렬을 씁니다.내적 · 외적
두 벡터의 관계를 숫자로: 내적은 "얼마나 같은 방향인가", 외적은 "수직 방향과 넓이".
내적·외적 활용을 C++로 한 줄 한 줄 직접 구현하기내적 (Dot Product)
A · B = |A||B|cosθ. 결과는 스칼라(숫자 하나)입니다.
두 벡터가 같은 방향이면 양수, 수직이면 0, 반대면 음수가 돼요.
조명에서 "빛이 표면에 얼마나 정면으로 닿는가"를 이걸로 계산합니다.
두 화살표 끝을 드래그하세요. 사이각과 내적 부호를 보세요.
외적 (Cross Product)
3D에서 외적 A × B는 두 벡터에 동시에 수직인 새 벡터를 줍니다.
삼각형의 법선(normal, 면이 바라보는 방향)을 구할 때 필수예요.
2D에서는 결과가 스칼라 하나가 되는데, 그 부호로 회전 방향(시계/반시계)과
평행사변형 넓이를 알 수 있습니다.
외적 값의 크기 = 평행사변형 넓이, 부호 = B가 A의 왼쪽인지 오른쪽인지.
삼각함수
원운동, 회전, 파동의 언어. sin·cos만 이해하면 절반은 끝납니다.
삼각함수 활용을 C++로 한 줄 한 줄 직접 구현하기단위원(반지름 1인 원) 위의 한 점을 각도 θ로 표현하면 그 좌표가 바로
(cos θ, sin θ)입니다. 즉 cos은 x, sin은 y예요.
회전, 궤도, 흔들림, 파형 전부 여기서 나옵니다.
각도 슬라이더를 돌리면 원 위의 점과 오른쪽 파형이 함께 움직입니다.
180° = π 라디안 ≈ 3.14159. 변환: 라디안 = 도 × π / 180.선형대수와 기하학
지금까지의 도구를 합쳐서 "선분 교차", "점과 선 거리" 같은 실전 문제 풀기.
기하 교차 판정을 C++로 한 줄 한 줄 직접 구현하기게임과 그래픽스에서 끝없이 나오는 질문들 — "이 점이 이 도형 안에 있나?", "두 선분이 만나나?", "마우스가 버튼 위에 있나?" — 은 전부 앞의 내적·외적으로 풀립니다.
삼각형 꼭짓점과 빨간 점을 드래그하세요. 안에 있으면 삼각형이 초록으로 채워집니다.
픽셀과 색상
화면은 작은 빛 점들의 격자. 각 점은 빨강·초록·파랑의 밝기로 색을 만듭니다.
이 챕터를 C++/DirectX로 직접 구현하기모니터의 모든 색은 RGB — 빨강(R), 초록(G), 파랑(B) 세 빛을 섞어 만듭니다.
각 채널은 0~255. (255,0,0)은 순수 빨강, (255,255,255)는 하양,
(0,0,0)은 검정이죠. 빛을 섞을수록 밝아지는 가산 혼합입니다.
래스터라이제이션
수학적 도형(선·삼각형)을 네모난 픽셀 격자로 "칠하는" 과정.
컴퓨터는 완벽한 선을 그릴 수 없어요. 격자 위에서 "어느 픽셀을 켤까?"를 정해야 하죠. 대각선을 그리면 계단 현상(에일리어싱)이 생기는 이유입니다.
두 끝점을 드래그하세요. 실제로 켜지는 픽셀이 격자에 표시됩니다.
꼭짓점을 드래그하세요. 각 픽셀 중심이 삼각형 안이면 칠합니다. 이게 모든 3D 렌더링의 심장입니다.
이미지 처리
이미지는 숫자 배열. 숫자를 바꾸면 필터가 됩니다 — 밝기, 흑백, 블러.
이미지 = 픽셀들의 2차원 배열입니다. 각 픽셀 값을 수식으로 바꾸면 필터가 되죠. 블러 같은 필터는 주변 픽셀들의 평균을 쓰는데, 이걸 합성곱(convolution)이라고 합니다.
알파 블렌딩
반투명 유리처럼, 위 그림과 아래 그림을 비율로 섞기.
알파(A)는 불투명도입니다(0=투명, 1=불투명). 반투명한 층을 아래 층 위에 올릴 때 최종 색은 이렇게 계산돼요:
결과 = 위색 × 알파 + 아래색 × (1 − 알파)
이게 오버 연산이며 UI, 그림자, 파티클, 안티에일리어싱의 기반입니다.
스프라이트 · 텍스처
작은 이미지를 화면에 배치·반복·변형해서 세계를 채우기.
스프라이트는 게임 캐릭터·아이템 같은 작은 2D 이미지입니다. 텍스처는 표면에 입히는 그림이고요. 텍스처 좌표 UV(0~1 범위)를 이용해 "이미지의 어느 부분을 어디에 붙일지" 정합니다.
UV 슬라이더로 원본 텍스처(왼쪽)의 어느 영역을 오른쪽 사각형에 매핑할지 조절합니다.
Model → World → View → Projection 변환
3D 점이 화면의 2D 픽셀이 되기까지 거치는 4단계 여행.
이 챕터를 C++/DirectX 11로 직접 구현하기3D 물체 하나가 화면에 그려지려면 네 개의 좌표계를 거칩니다. 이걸 파이프라인이라 해요:
- Model: 물체 자기 기준 좌표 (예: 주전자의 중심이 원점)
- World: 물체를 세계 속 제 위치에 배치
- View: 카메라 기준으로 세계를 다시 봄 (카메라가 원점)
- Projection: 3D를 2D 화면 평면에 투영 (원근감 부여)
각 슬라이더는 파이프라인의 한 단계를 조절합니다. 큐브를 마우스로 드래그해도 됩니다.
화면x = x / z처럼 깊이 z로 나누기 때문이에요. z가 클수록(멀수록) 작아지죠.카메라
카메라는 "위치 + 바라보는 방향 + 위쪽". 세계를 카메라 앞으로 끌어오는 것.
재미있는 사실: 그래픽스에서 카메라는 실제로 움직이지 않아요. 카메라를 원점에 고정하고, 대신 세계 전체를 반대로 움직입니다. 이 변환을 만드는 것이 LookAt이며 세 벡터로 정의됩니다: 위치(eye), 목표(target), 위쪽(up).
위: 세계를 위에서 본 모습(카메라 위치·시야각 조절 가능). 아래: 그 카메라가 보는 화면.
삼각형과 메시
모든 3D 모델은 결국 삼각형 덩어리. 왜 하필 삼각형일까?
구, 사람, 자동차 — 3D의 모든 것은 삼각형(polygon)의 모음(메시)입니다. 왜 삼각형이냐면: 세 점은 항상 하나의 평면을 이루기 때문이에요(휘지 않음). GPU도 삼각형만 그릴 줄 압니다.
세분화 슬라이더를 올리면 삼각형이 많아지며 구가 매끄러워집니다.
깊이 버퍼 (Z-buffer)
"누가 앞에 있나?" — 픽셀마다 깊이를 기록해 가림 처리를 하는 방법.
여러 삼각형이 겹칠 때, 화면의 각 픽셀은 가장 앞(가장 가까운) 것만 보여야 합니다. 이를 위해 색 버퍼와 별개로 깊이 버퍼를 둬서, 픽셀을 칠하기 전에 "지금 그리려는 게 이미 있는 것보다 더 가까운가?"를 비교합니다. 가까우면 덮어쓰고, 멀면 버려요.
깊이 테스트를 껐다 켜보세요. 끄면 나중에 그린 도형이 무조건 위로 와서 어색해집니다.
조명과 그림자
표면 법선과 빛 방향의 내적으로 밝기를 정하는 것 — 여기서 내적이 빛납니다.
표면이 밝은지 어두운지는 표면이 빛을 얼마나 정면으로 받는가로 정해집니다. 이건 표면 법선(normal) · 빛 방향의 내적이에요(디퓨즈 조명, 램버트 법칙). 정면이면 밝고(내적 1), 비스듬하면 어둡고(내적 0에 가까움), 뒤돌면 안 받죠(음수→0).
빛 방향 슬라이더를 돌려보세요. 구의 밝은 면이 따라 움직이고, 반짝이는 하이라이트도 생깁니다.
텍스처 매핑
2D 이미지를 3D 표면에 "포장지처럼" 입히기. UV 좌표가 다리 역할.
맨질맨질한 3D 면에 벽돌·나무·얼굴을 그려 넣는 방법이 텍스처 매핑입니다. 메시의 각 꼭짓점에 UV 좌표(텍스처 이미지 상의 위치, 0~1)를 붙여두면, 삼각형 내부의 각 픽셀에 대해 UV를 보간(interpolation)해서 이미지의 해당 색을 가져와 칠합니다.
각 면에 텍스처가 UV 좌표를 따라 입혀진 채 회전합니다. 원근 보정도 확인해보세요.
GPU 병렬 처리
CPU는 몇 명의 천재, GPU는 수천 명의 일꾼. 그래픽스는 "수천 명의 일꾼"이 유리합니다.
이 챕터를 C++/DirectX 11로 직접 구현하기화면의 픽셀은 수백만 개. 각 픽셀 계산은 서로 독립적이라 동시에 처리해도 됩니다. CPU는 코어가 몇 개뿐이라 순서대로(직렬) 빠르게 처리하고, GPU는 느리지만 코어가 수천 개라 한꺼번에(병렬) 처리해요. 같은 계산을 데이터만 바꿔 대량 반복 — 이게 그래픽스에 딱 맞습니다.
"출발" 버튼을 누르면 같은 이미지를 CPU 방식(한 칸씩)과 GPU 방식(여러 칸 동시)으로 채웁니다.
GPU에게 "각 정점/픽셀마다 이걸 해라"라고 시키는 작은 프로그램이 셰이더(Shader)입니다. 렌더링 파이프라인에서 순서대로 Vertex Shader → (래스터화) → Fragment Shader가 돌아요.
Vertex Shader
정점(vertex) 하나하나의 위치를 계산하는 프로그램. MVP 변환이 여기서 일어납니다.
메시의 각 정점에 대해 딱 한 번씩 실행됩니다. 주 임무는 3D 좌표를 화면 클립 좌표로
변환하는 것(gl_Position = MVP * position). 여기서 정점을 흔들거나 밀면
물결·바람·모프 같은 변형 효과를 GPU에서 공짜로 얻습니다.
진폭·주파수·시간 슬라이더가 정점의 높이를 y += sin(x*f + t)*a 로 바꿉니다.
정점만 움직였는데 표면 전체가 물결쳐요.
Fragment / Pixel Shader
삼각형을 덮는 픽셀 하나하나의 색을 계산하는 프로그램. 조명·텍스처·특수효과의 무대.
래스터화로 삼각형이 픽셀들로 쪼개지면, 각 픽셀(프래그먼트)마다 프래그먼트 셰이더가 실행되어
최종 색을 출력합니다(gl_FragColor = ...). 수백만 번 병렬 실행되죠.
아래는 진짜 GPU(WebGL)에서 도는 프래그먼트 셰이더입니다.
효과 버튼을 눌러보세요. 각 픽셀이 자기 좌표만으로 색을 계산합니다 — 도형 데이터 없이 그림이 나와요. WebGL 미지원 시 소프트웨어로 대체됩니다.
uv를 알아요.
거기에 수식을 넣으면(예: 중심까지 거리 < 0.5 이면 원 안) 도형·그라디언트·패턴이 계산으로 만들어집니다.
이게 셰이더 아트와 프로시저럴 텍스처의 원리예요.Compute Shader
그리기와 무관하게 GPU를 순수 계산기로 쓰는 셰이더. 물리·파티클·이미지 처리에.
버텍스/프래그먼트 셰이더는 "그리기 파이프라인"에 묶여 있지만, 컴퓨트 셰이더는 화면 출력과 무관하게 임의의 대규모 병렬 계산을 합니다. 수만 개 파티클의 위치 갱신, 입자 시뮬레이션, 블러, 물리 등을 GPU의 수천 코어로 한꺼번에 처리해요. 데이터를 작업 그룹(workgroup) 단위로 나눠 스레드들이 병렬 실행됩니다.
각 파티클은 "같은 규칙"을 자기 데이터로 실행합니다 — 컴퓨트 셰이더가 하는 일과 동일한 구조예요. 파티클 수를 늘려보세요.
GLSL · HLSL · WGSL
셰이더를 쓰는 언어들. 문법은 조금 다르지만 하는 일은 똑같습니다.
C를 닮은 작은 언어들이에요. 어떤 그래픽스 API를 쓰느냐에 따라 언어가 갈립니다:
- GLSL — OpenGL / WebGL / Vulkan(SPIR-V로 컴파일)
- HLSL — DirectX (그리고 요즘은 Vulkan에도 많이 사용)
- WGSL — WebGPU 전용 신생 언어
아래는 "화면 좌표로 빨강→파랑 그라디언트를 칠하는" 똑같은 프래그먼트 셰이더를 세 언어로 쓴 것입니다. 분위기만 비교해보세요 — 개념은 동일합니다.
GLSL (WebGL)
precision mediump float;
varying vec2 vUv;
void main() {
vec3 col = mix(
vec3(1.0, 0.2, 0.2),
vec3(0.2, 0.4, 1.0),
vUv.x);
gl_FragColor = vec4(col, 1.0);
}
HLSL (DirectX)
float4 main(
float2 uv : TEXCOORD0
) : SV_Target {
float3 col = lerp(
float3(1.0, 0.2, 0.2),
float3(0.2, 0.4, 1.0),
uv.x);
return float4(col, 1.0);
}
WGSL (WebGPU)
@fragment
fn main(
@location(0) uv: vec2f
) -> @location(0) vec4f {
let col = mix(
vec3f(1.0, 0.2, 0.2),
vec3f(0.2, 0.4, 1.0),
uv.x);
return vec4f(col, 1.0);
}
mix/lerp 같은
내장 함수, ③ "입력을 받아 색(vec4)을 반환". 하나를 익히면 나머지는 문법만 갈아끼우면 됩니다.그래픽스 API 지형도
GPU에게 명령을 내리는 통로. "쉽고 자동" ↔ "어렵지만 완전 제어"의 스펙트럼.
D3D11 실전 + 타 API 매핑 구현 가이드그래픽스 API는 내 코드와 GPU 드라이버 사이의 약속(인터페이스)입니다. 고수준 API는 많은 걸 알아서 해줘 배우기 쉽지만 세밀한 제어가 어렵고, 저수준 API는 메모리·동기화까지 직접 다뤄 강력하지만 코드가 길고 복잡합니다. 아래 차트에서 각 API의 위치와 "삼각형 하나 그리는 데 드는 대략적 코드량"을 비교해보세요.
점 위에 마우스를 올리면 설명이 뜹니다. 아래 막대는 "삼각형 하나 띄우기"에 필요한 대략적 코드 라인 수예요.
OpenGL · DirectX 12 · Vulkan · Metal · WebGPU
언제 무엇을 골라야 할까? 목적별 정리.
| API | 수준 | 플랫폼 | 이럴 때 좋음 |
|---|---|---|---|
| OpenGL | 고수준 | 크로스플랫폼(구형 폭넓음) | 그래픽스 원리 학습, 빠른 프로토타이핑, 예제·자료가 가장 많음 |
| DirectX 12 | 저수준 | Windows · Xbox | Windows 게임·엔진 개발, 콘솔, 최대 성능 |
| Vulkan | 저수준 | 크로스플랫폼(Win/Linux/Android) | 저수준 GPU 제어, 멀티스레드 렌더링, 이식성 필요한 고성능 엔진 |
| Metal | 중·저수준 | Apple(macOS · iOS) | Apple 플랫폼 전용 최적 성능, 애플 생태계 통합 |
| WebGPU | 중수준 | 웹 브라우저 | 웹에서 GPU 프로그래밍, 최신 웹 3D·컴퓨트, WebGL의 후계 |
Physically Based Rendering (PBR)
"대충 예뻐 보이게"가 아니라 실제 빛의 물리 법칙대로 표면을 계산하기.
이 챕터를 C++/DirectX 11로 직접 구현하기PBR은 재질을 몇 개의 물리적 파라미터로 표현합니다. 핵심은 두 개:
- Roughness(거칠기): 표면이 매끄러운가(반짝, 좁은 하이라이트) 거친가(뿌연 반사)
- Metallic(금속성): 금속인가(반사색=재질색, 확산 거의 없음) 비금속인가(하얀 반사)
어떤 조명 아래서도 일관되게 사실적으로 보이는 게 PBR의 장점이라, 오늘날 거의 모든 엔진의 표준입니다.
거칠기를 낮추면 하이라이트가 좁고 날카로워지고, 금속성을 올리면 반사가 재질색을 띱니다.
HDR · 톤 매핑
태양은 종이보다 수만 배 밝다. 이 넓은 밝기를 0~255 화면에 우겨넣는 기술.
HDR(High Dynamic Range)은 밝기를 0~1에 가두지 않고 실제 물리량(수천, 수만)으로 계산합니다. 그런데 모니터는 결국 0~255만 표현하죠. 그래서 마지막에 넓은 밝기 범위를 화면 범위로 부드럽게 압축하는 톤 매핑(tone mapping)이 필요합니다. 단순히 잘라내면(clip) 밝은 곳이 하얗게 다 타버려요.
노출을 올리며 "그냥 자르기(clip)"와 "톤 매핑(Reinhard/ACES)"을 비교하세요. 오른쪽 그래프가 밝기 압축 곡선입니다.
Shadow Mapping
그림자 = "빛의 눈으로 볼 때 가려진 곳". 빛 시점의 깊이 지도를 만들어 판정합니다.
가장 널리 쓰이는 그림자 기법입니다. 2단계로 동작해요:
- 1패스: 빛의 위치에서 장면을 렌더해 각 방향의 "가장 가까운 표면까지 거리"를 섀도우 맵(깊이 지도)에 저장.
- 2패스: 카메라로 그릴 때 각 점을 빛 시점으로 변환해, 그 점이 섀도우 맵에 적힌 거리보다 멀면 = 뭔가에 가려짐 = 그림자.
빛과 가림막을 움직여보세요. 빛에서 각 방향으로 쏜 광선이 가림막에 막히면 그 뒤가 그림자가 됩니다.
Deferred Rendering
조명을 나중에! 먼저 화면 정보만 모아두고, 조명은 화면 픽셀에 한 번씩만 계산.
조명이 수백 개면, 물체마다 모든 빛을 계산하는 전방 렌더링(Forward)은 폭발합니다. 디퍼드 렌더링은 순서를 바꿔요:
- G-buffer 패스: 조명 없이, 각 화면 픽셀의 재질 정보(색/Albedo, 법선/Normal, 깊이/Depth, 위치)만 여러 장의 텍스처에 저장.
- 라이팅 패스: 저장된 G-buffer를 읽어 화면 픽셀당 딱 한 번 조명 계산. 빛이 아무리 많아도 "보이는 픽셀 수"만큼만 계산돼요.
버튼으로 각 G-buffer 채널을 보고, 조명 수를 늘려보세요. 조명이 많아도 라이팅 패스는 화면 크기만큼만 돕니다.
Screen-Space Effects
이미 렌더된 "화면 이미지"를 후처리해서 효과를 얹기. 싸고 빠른 눈속임의 예술.
3D를 다시 계산하지 않고, 완성된 2D 화면(과 깊이·법선 정보)만 가지고 효과를 추가합니다. 빠르기 때문에 실시간에 애용돼요:
- SSAO: 화면 깊이로 "구석·틈"을 찾아 은은한 그늘을 넣어 입체감 ↑
- Bloom: 밝은 부분이 번지는 빛무리
- Vignette / 색수차 / 모션블러 등 영화 같은 룩
효과를 켜고 강도를 조절하세요. 원본 3D는 그대로, 화면 이미지에만 필터를 얹습니다.
Ray Tracing
픽셀마다 눈에서 광선을 쏴, 무엇에 맞았는지 추적해 색을 정하기. 반사·굴절이 자연스럽습니다.
래스터화가 "삼각형 → 픽셀"이라면, 레이 트레이싱은 반대로 "픽셀 → 광선 → 장면"입니다. 각 픽셀에서 광선을 쏘고, 가장 먼저 부딪힌 표면을 찾은 뒤, 거기서 빛 쪽으로 또 광선을 쏴 그림자를 판정하고, 반사 방향으로 광선을 튕겨 거울 반사를 얻어요. 아래는 브라우저에서 도는 실제 소프트웨어 레이트레이서입니다.
반사 횟수(bounce)를 올리면 거울에 비친 상 안에 또 상이 비칩니다. 조명·구를 움직여보세요.
Path Tracing
레이 트레이싱을 확률로 확장. 빛의 무작위 경로를 수없이 쌓아 사진 같은 그림을 얻기.
패스 트레이싱은 각 픽셀에서 광선을 쏘되, 표면에 맞으면 무작위 방향으로 계속 튕기며 빛을 만날 때까지 경로(path)를 추적합니다. 한 번으로는 노이즈(지글지글)투성이라, 같은 장면을 수백~수천 번 반복해 평균내면 점점 깨끗한 사실적 이미지가 됩니다. 부드러운 그림자, 간접광, 색 번짐이 공짜로 나오는 게 매력이에요.
"누적 시작"을 누르면 샘플이 쌓이며 노이즈가 서서히 사라집니다. 샘플 수를 보세요.
Global Illumination
빛은 한 번 튕기고 끝나지 않는다. 벽에 튄 빛이 옆면을 물들이는 간접광까지 계산.
직접광(Direct)만 계산하면 그림자 속은 새까맣습니다. 하지만 현실에선 빛이 벽·바닥에 여러 번 튕기며(bounce) 온 공간을 은은히 채우고, 빨간 벽 옆 물체는 살짝 붉게 물들죠 (색 번짐, color bleeding). 이 간접광 전부를 다루는 게 전역 조명(GI)입니다. 패스 트레이싱은 GI를 자연스럽게 포함하고, 실시간에선 라이트맵·복셀·SSGI 등으로 근사합니다.
GI를 켜면 그림자 속이 밝아지고, 색깔 벽이 이웃 표면을 물들입니다 — 훨씬 사실적이죠.
안티앨리어싱 (Antialiasing)
비스듬한 선의 "계단(지글지글)"을 부드럽게. 픽셀 하나를 여러 번 샘플링해 평균 내기.
이 챕터를 C++/DirectX 11로 직접 구현하기픽셀은 네모난데 도형의 경계는 매끈합니다. 그래서 경계가 계단(재기, jaggies)처럼 보이죠. 이건 앞서 본 래스터라이제이션의 숙명입니다. 해법의 핵심은 "픽셀 하나 안에서 여러 지점을 검사해 평균색을 내는 것"이에요. 경계에 걸친 픽셀은 도형색과 배경색이 섞여 중간톤이 되고, 멀리서 보면 부드럽게 보입니다.
- SSAA: 화면을 통째로 크게 그린 뒤 축소 — 가장 정확하지만 가장 비쌈
- MSAA: 경계(엣지)에서만 여러 번 샘플 — 실시간의 고전적 절충안
- FXAA/TAA: 완성된 화면에서 후처리로(또는 여러 프레임을 섞어) 매끈하게 — 저렴하고 현대 게임의 주력
픽셀당 샘플 수를 1 → 16으로 올려보세요. 확대된 경계에서 계단이 중간톤으로 부드러워집니다.
감마 · 선형 색공간
"밝기 0.5는 왜 회색이 아닐까?" 빛 계산은 선형에서, 저장·표시는 sRGB에서.
모니터의 픽셀 값(0~255)은 빛의 세기에 정비례하지 않습니다. 사람 눈이 어두운 쪽 차이에 더 민감해서, 저장은 sRGB(감마) 공간에 촘촘하게 하죠. 그런데 빛의 덧셈·조명 계산은 실제 물리량, 즉 선형(linear) 공간에서 해야 맞습니다. 이 변환을 빼먹으면 조명이 탁해지고, 두 색을 섞을 때 엉뚱한 결과가 나와요.
선형 → 화면 : 대략 out = pow(color, 1/2.2) (감마 인코딩)
화면 → 선형 : 대략 lin = pow(value, 2.2) (감마 디코딩)
감마 슬라이더와 "선형 공간에서 계산" 토글을 바꿔보세요. 그라디언트 밝기와 색 혼합 결과가 달라집니다.
밉맵 · 텍스처 필터링
멀리 있는 텍스처가 지글거리는 이유와 해법. 미리 축소해 둔 이미지 사다리.
텍스처를 입힌 바닥이 멀어지면, 화면 픽셀 하나가 텍스처의 수많은 텍셀을 덮게 됩니다. 이때 한 점만 콕 집어 읽으면(점 샘플링) 프레임마다 어떤 텍셀이 뽑힐지 튀어서 지글지글(반짝임) 거려요 — 텍스처의 앨리어싱이죠.
밉맵(mipmap)은 텍스처를 미리 절반씩 줄인 이미지들(1/2, 1/4, 1/8…)로 만들어 두고, 거리(축소 정도)에 맞는 크기를 골라 씁니다. 여기에 필터링을 더해요:
- 이중선형(Bilinear): 가까운 4텍셀을 섞어 매끈하게
- 삼중선형(Trilinear): 인접한 두 밉맵 레벨 사이도 섞어 경계 없이
- 이방성(Anisotropic): 비스듬히 볼 때 뭉개짐을 줄여 바닥·벽이 선명
밉맵/필터를 껐다 켜보세요. 끄면 먼 곳이 지글거리고, 켜면 부드럽게 정리됩니다. 밉맵 사다리도 함께 보여줘요.
투명과 렌더링 순서
반투명은 순서가 생명. Z-buffer만으로는 유리를 제대로 못 그립니다.
알파 블렌딩은 "위색 × α + 아래색 × (1−α)"였죠. 문제는 여러 반투명 물체가 겹칠 때입니다. 블렌딩은 이미 그려진 색 위에 섞는 연산이라, 뒤에서 앞으로 (back-to-front) 순서대로 그려야 맞아요. 순서가 틀리면 뒤 유리가 앞 유리 위에 섞여 색이 어긋납니다.
게다가 불투명 물체는 Z-buffer로 순서를 신경 안 써도 되지만, 반투명은 깊이 기록을 켜면 서로를 가려버려 엉망이 됩니다. 그래서 보통 ① 불투명 먼저(깊이 쓰기 ON) → ② 반투명은 정렬해서 나중에(깊이 쓰기 OFF) 그려요.
정렬을 껐다 켜보세요. 카드 깊이를 바꾸며 순서가 틀렸을 때 색이 어떻게 어긋나는지 확인하세요.
앰비언트 오클루전 (Ambient Occlusion)
구석·틈이 어두운 이유. 주변광이 얼마나 "가려지는가(차폐)"를 계산해 입체감을 살리기.
주변광(ambient)은 사방에서 은은히 오는 빛인데, 좁은 틈·구석·물체가 맞닿는 곳은 그 빛이 주변 기하에 가려져(차폐, occlusion) 실제로 더 어둡습니다. 이걸 근사해 어둡게 깔아주면 장면이 갑자기 땅에 붙어 있는 듯한 입체감을 얻어요.
실시간에선 화면 깊이 정보만으로 근사하는 SSAO(Screen-Space AO)가 대표적입니다 (스크린 스페이스 효과의 일종).
AO를 켜면 물체가 맞닿는 구석과 틈에 부드러운 그늘이 생겨 훨씬 사실적으로 앉아 보입니다.
LOD · 컬링 (상세 수준과 걸러내기)
안 보이거나 멀리 있는 건 대충/아예 안 그리기. 프레임을 지키는 최적화의 기본.
수백만 삼각형을 매 프레임 다 그릴 순 없습니다. 두 가지 큰 아이디어로 일을 줄여요:
- 컬링(Culling) — 아예 안 그리기: 절두체 컬링(카메라 시야 밖 제외), 후면 컬링(뒤돌아선 삼각형 제외), 가림 컬링(다른 물체에 완전히 가려진 것 제외).
- LOD(Level of Detail) — 대충 그리기: 멀어지면 삼각형 적은 저해상도 모델로 교체. 어차피 작게 보여서 티가 안 나요.
카메라 거리/시야를 바꿔보세요. 멀어지면 모델이 단순해지고, 시야 밖 물체는 그리기에서 빠지며 삼각형 수가 확 줄어요.