GPU는 사실 원을 모릅니다: 테셀레이션과 3D 렌더링의 진실


최근 Hacker News에서 꽤 흥미로운 글을 하나 읽었습니다. 제목은 “Cutting Up Curved Things”. 직역하자면 “곡선 자르기” 정도가 되겠네요. 15년 넘게 그래픽스와 시스템 프로그래밍을 다뤄온 입장에서, 이 글은 우리가 매일 화면에서 보는 ‘부드러운 곡선’의 실체를 아주 적나라하게, 그리고 우아하게 설명하고 있습니다.

오늘은 이 글을 바탕으로 우리가 흔히 오해하는 GPU의 작동 방식, 그리고 수학적 이상(Ideal)이 현실의 하드웨어와 만날 때 벌어지는 ‘타협’의 예술인 테셀레이션(Tessellation)에 대해 깊게 파고들어 보려 합니다.

GPU의 어휘력은 ‘삼각형’ 뿐이다

많은 주니어 엔지니어들이나 비전공자들은 GPU가 매우 똑똑해서 drawCircle() 같은 명령을 내리면 알아서 원을 그려준다고 생각합니다. 하지만 사실 GPU의 어휘력은 인덱스 카드 한 장에 다 들어갈 정도로 빈약합니다. GPU가 아는 건 딱 하나, 삼각형 입니다.

여러분이 보는 매끈한 아이폰 렌더링, 픽사의 애니메이션 캐릭터, 최신 게임의 자동차 휠… 이 모든 것은 사실 수백만 개의 아주 작은 삼각형 조각들입니다. 이것이 바로 ‘테셀레이션’이 필요한 이유입니다. 수학적으로 완벽한 곡면을 GPU가 이해할 수 있는 삼각형으로 ‘다져넣는’ 작업이죠.

수학 vs 현실: 번역의 문제

CAD 커널이나 수학적인 세계에서 원기둥(Cylinder)은 이렇게 정의됩니다:

fn point_on_cylinder(u: f64, v: f64) -> Point3 {
    Point3::new(
        radius * u.cos(),
        radius * u.sin(),
        v
    )
}

이 함수는 완벽합니다. uv만 주면 무한한 정밀도의 좌표를 뱉어냅니다. 하지만 렌더링 파이프라인에서 이건 쓸모가 없습니다. 우리는 이 연속적인(Continuous) 함수를 이산적인(Discrete) 삼각형 메시(Mesh)로 변환해야 합니다.

보통 이 과정은 UV 그리드 샘플링을 통해 이루어집니다. u를 0에서 2π까지, v를 높이만큼 순회하며 점을 찍고, 그 점들을 이어 삼각형을 만듭니다. 샘플링을 많이 할수록 곡선은 부드러워지지만, 버텍스(Vertex) 수가 폭발적으로 늘어납니다. 반대로 적게 하면 각진 폴리곤 덩어리가 되죠. 이 트레이드오프가 그래픽스 최적화의 영원한 숙제입니다.

예외 처리의 미학: 구(Sphere)와 구멍(Hole)

원기둥은 그나마 쉽습니다. 문제는 ‘구(Sphere)‘나 ‘구멍 뚫린 면’ 같은 엣지 케이스들입니다.

1. 극점(Poles) 문제

지구본을 생각해보세요. 위도와 경도로 그리드를 나누면 북극과 남극에서는 모든 점이 하나로 모입니다. 여기서 일반적인 사각형(Quad) 처리를 하면, 한 변의 길이가 0인 찌그러진 삼각형들이 생겨나 렌더링 아티팩트(Artifact)를 유발합니다.

이 글에서는 이를 ‘오렌지 조각’처럼 처리한다고 설명합니다. 극점에서는 사각형이 아닌 부채꼴(Fan) 모양의 삼각형을 사용하여 모든 삼각형이 극점 하나를 공유하게 만드는 식이죠. 이건 꽤 고전적이지만 여전히 유효한 테크닉입니다.

2. 구멍 뚫린 다각형과 Ear Clipping

더 골치 아픈 건 구멍입니다. 평판에 구멍을 뚫으면, 그 면은 더 이상 단순한 다각형이 아닙니다. 내부 루프(Inner Loop)가 생기죠.

여기서 Ear Clipping 알고리즘이 등장합니다. 다각형을 피자 먹듯이 가장자리부터 한 입씩(삼각형 하나씩) 잘라 들어가는 방식입니다. 단, 조건이 있습니다.

  • 잘라낼 삼각형의 중간 점이 볼록(Convex)해야 한다.
  • 그 삼각형 안에 다른 점이 없어야 한다.

이 알고리즘은 직관적이지만, 구멍이 있는 경우 바로 적용할 수 없습니다. 그래서 보통 ‘Bridge’를 놓습니다. 구멍의 한 점과 바깥 경계의 한 점을 잇는 가상의 선(Bridge)을 만들어, 구멍 뚫린 도넛 모양을 하나의 긴 ‘C’자형 다각형으로 바꿔버리는 겁니다. 일종의 위상 수학적 트릭이죠.

Hacker News의 논쟁: “삼각형이 전부는 아니다”

이 글이 HN에 올라왔을 때, 댓글 창은 예상대로 뜨거웠습니다. 특히 “모든 매끄러운 표면은 사실 삼각형이다”라는 문장에 대해 시니어들의 반박이 이어졌습니다.

“현대 GPU는 래스터화(Rasterization) 말고도 많은 걸 할 수 있어. Ray Tracing이나 Signed Distance Fields(SDF)를 쓰면 삼각형 없이도 완벽한 구를 그릴 수 있다고!”

맞는 말입니다. 저도 셰이더 토이(ShaderToy)의 거장 Inigo Quilez의 SDF 작업을 매우 좋아합니다. 레이 트레이싱을 사용하면 수학적 수식 그대로 렌더링이 가능합니다. 테셀레이션 자체가 필요 없죠.

하지만 현실적인 관점에서(특히 게임 엔진이나 일반적인 CAD 뷰어에서), 삼각형 래스터화는 여전히 압도적인 표준 입니다. 성능 대 비용 효율이 가장 좋기 때문입니다. 레이 트레이싱이 ‘Hello World’ 급으로 쉬워졌다고는 하나, 수백만 폴리곤의 씬을 초당 60프레임으로 돌리는 데 있어서 기존 파이프라인을 완전히 대체하기엔 아직 갈 길이 멉니다.

AI와 알고리즘의 미래

댓글 중 또 하나 흥미로웠던 건 AI에 대한 이야기였습니다. 한 유저가 복잡한 삼각분할(Triangulation) 알고리즘을 구현하다 막혀서 AI에게 시켰더니, 자신이 짠 코드보다 더 구조적이고 나은 코드를 뱉어냈다는 경험담이었습니다.

사실 기하 알고리즘(Computational Geometry)은 코너 케이스가 많아 사람이 실수하기 딱 좋은 분야입니다. 부동소수점 오차(Floating Point Error) 때문에 이론상 완벽한 알고리즘도 실제론 빵꾸가 나기 일쑤죠. 이런 ‘이미 정답이 있는’ 복잡한 문제 해결에 있어서 AI가 주니어 엔지니어, 어쩌면 시니어 엔지니어의 생산성을 압도하는 시대가 왔음을 다시 한번 느낍니다.

결론: 환상을 만드는 기술

결국 테셀레이터(Tessellator)는 마술사의 조수와 같습니다. 수학적으로 아름다운 곡선을, GPU라는 투박한 기계가 이해할 수 있는 ‘삼각형 조각’으로 보이지 않게 썰어내는 역할이죠.

우리가 화면에서 보는 부드러움은 사실 수많은 직선들의 집합이 만들어낸 착시 입니다. 하지만 그 착시가 깨지지 않도록 정교하게 버텍스를 배치하고 인덱스를 엮는 그 과정, 그 자체가 엔지니어링의 아름다움 아닐까요?

여러분의 다음 프로젝트가 3D와 관련이 있다면, 한 번쯤은 Mesh.vertices 배열을 들여다보시길 바랍니다. 그 수만 개의 숫자 속에 엔지니어들의 피땀 어린 최적화가 숨어있을 테니까요.