셰이더와 GPU 파이프라인 · 레이 마칭과 거리 함수 · 이론
거리를 알면 그릴 수 있다
한 줄 요약
어떤 점에서 표면까지의 최단 거리를 알려 주는 함수가 있으면, 광선을 그 거리만큼씩 안전하게 전진시켜 표면을 찾을 수 있습니다. 삼각형도 정점 버퍼도 필요 없습니다.
왜 이게 필요했나
래스터라이즈는 삼각형을 전제로 합니다. 형태를 바꾸려면 정점을 다시 만들어야 하고, 두 형태를 합치거나 빼는 일은 기하 연산이라 까다롭습니다. 부드럽게 녹아 붙는 두 방울을 삼각형으로 표현하려면 매 프레임 메시를 다시 만들어야 합니다.
거리 함수는 형태를 수식으로 표현합니다. 구는 length(p) - r 한 줄이고, 두 형태의 합집합은 min(a, b) 한 줄입니다. 부드럽게 이어 붙이는 것도 몇 줄이면 됩니다. 데이터가 없으니 메모리도 거의 쓰지 않고, 형태를 바꾸는 것이 곧 수식을 바꾸는 것입니다.
어떻게 동작하나
부호 있는 거리 함수(SDF)는 점 하나를 받아 그 점에서 표면까지의 최단 거리를 돌려줍니다. 안쪽이면 음수, 바깥이면 양수, 표면 위면 0 입니다.
구 length(p) - r평면 p.y - h상자 q = abs(p) - b length(max(q, 0)) + min(max(q.x, q.y, q.z), 0)핵심은 이 값이 안전한 전진 거리라는 점입니다. 지금 위치에서 가장 가까운 표면이 d 만큼 떨어져 있다면, 어느 방향으로 가도 d 만큼은 아무것도 만나지 않습니다. 그래서 광선을 d 만큼 건너뛰어도 표면을 지나칠 수 없습니다. 이것을 반복하는 것이 구 추적(sphere tracing) 이고 흔히 레이 마칭이라고 부릅니다.
t = 0반복: d = scene(ro + rd * t) if d < eps: 맞았다 t += d if t > 최대거리: 빗나갔다빈 공간에서는 크게 건너뛰고 표면 가까이서는 조금씩 다가갑니다. 그래서 표면에 스치듯 지나가는 광선이 가장 비쌉니다 — 거리가 계속 작아 걸음이 잘게 쪼개집니다. 반복 횟수를 색으로 칠해 보면 실루엣 언저리가 밝게 빛나는 그림이 나오는데, 성능 문제를 눈으로 보는 좋은 방법입니다.
형태를 합치는 것도 함수로 합니다.
합집합 min(a, b)교집합 max(a, b)차집합 max(a, -b) # a 에서 b 를 파낸다부드러운 합집합은 두 거리를 섞습니다.
h = clamp(0.5 + 0.5 * (b - a) / k, 0, 1)mix(b, a, h) - k * h * (1 - h)마지막 항이 이어지는 부분을 부풀려 매끄러운 연결을 만듭니다. k 가 클수록 더 넓게 녹아 붙습니다.
법선은 별도로 저장하지 않고 거리 함수의 기울기로 구합니다. 각 축으로 아주 조금 움직여 거리의 차이를 보면 됩니다. 거리 함수의 기울기는 정의상 가장 가파르게 멀어지는 방향, 즉 표면의 바깥 방향입니다.
그림자도 광선 하나면 됩니다. 맞은 지점에서 광원 쪽으로 다시 마칭해 무언가에 맞으면 그늘입니다. 래스터라이즈에서 그림자 맵을 굽고 좌표를 변환하며 겪는 온갖 문제가 여기서는 없습니다. 대신 화면의 픽셀마다 광선을 두 번 쏘게 됩니다.
성능의 성질도 래스터라이즈와 다릅니다. 래스터라이즈의 비용은 삼각형 수와 덮은 픽셀 수에 붙지만, 레이 마칭의 비용은 픽셀마다의 걸음 수에 붙습니다. 그래서 화면 해상도가 그대로면 형태를 아무리 복잡하게 만들어도 비용이 크게 늘지 않는 반면, 해상도를 두 배로 키우면 네 배가 됩니다. 형태가 복잡해질 때 느려지는 것은 거리 함수 자체가 무거워질 때뿐입니다.
공간 반복이라는 재미있는 성질도 있습니다. 점의 좌표에 나머지 연산을 걸어 거리 함수에 넘기면, 같은 형태가 무한히 되풀이되는 장면이 함수 한 줄로 만들어집니다. 메모리는 하나도 더 쓰지 않습니다. 다만 이때 거리 함수가 실제 거리보다 커질 수 있어서, 반복 주기와 형태의 크기를 함께 고려한 보정이 필요합니다.
현장에서 만나는 모습
이 기법은 셰이더 하나로 장면 전체를 그리는 데모 씬과 Shadertoy 문화에서 자랐고, 지금은 실무에서도 쓰입니다. 구름과 연기 같은 부피 표현, 물체가 녹아 붙는 효과, 그리고 폰트나 아이콘을 해상도와 무관하게 또렷하게 그리는 SDF 텍스처가 그것입니다.
주의할 점은 거리 함수가 실제 거리보다 크면 안 된다는 것입니다. 크게 잡으면 광선이 표면을 건너뛰어 구멍이 뚫립니다. 그래서 형태를 비균등하게 늘리거나 반복시킬 때는 거리를 보수적으로 줄여 주는 보정이 필요합니다.
마지막으로 래스터라이즈와 어떻게 섞이는가 를 적어 두겠습니다. 실무에서 장면 전체를 레이 마칭으로 그리는 경우는 드물고, 보통은 화면을 덮는 사각형 하나를 래스터라이즈한 뒤 그 프래그먼트 셰이더 안에서 마칭을 돕니다. 그러면 깊이 버퍼에 결과를 써 넣어 삼각형으로 그린 물체와 자연스럽게 섞을 수 있습니다. 구름이나 연기 같은 부피 효과가 이렇게 만들어집니다 — 형태는 거리 함수로, 나머지 장면은 삼각형으로 그리고 둘을 깊이로 합칩니다.
다음 실습에서 할 것
거리 함수 몇 개와 합치기 연산을 만들고, 구 추적으로 표면을 찾습니다. 실루엣만 그린 다음 기울기 법선으로 음영을 넣고, 교집합과 차집합과 부드러운 합집합을 엮어 만든 형태를 그립니다. 마지막에는 바닥 평면을 놓고 그림자 광선을 쏘아 공이 바닥에 그림자를 드리우는 그림을 뽑습니다.