3D 수학과 소프트웨어 래스터라이저 · 삼각형을 채우고 깊이로 가린다 · 이론
래스터라이저가 하는 일
한 줄 요약
래스터라이저는 세 꼭짓점을 받아 그 안에 드는 픽셀을 골라내고, 꼭짓점에 붙은 값들을 무게중심 좌표로 섞어 픽셀마다 넘겨 줍니다. 앞뒤는 깊이 버퍼가 픽셀 단위로 정합니다.
왜 이게 필요했나
앞 단계까지 하면 정점이 화면 좌표를 갖습니다. 그런데 화면은 픽셀 격자이고, 삼각형의 변은 격자에 딱 맞지 않습니다. 어떤 픽셀을 이 삼각형의 것으로 볼지 정하는 규칙이 필요합니다.
그리고 색은 꼭짓점에만 있습니다. 삼각형 안쪽 픽셀의 색은 어디서 옵니까. 세 꼭짓점의 값을 거리에 따라 섞어야 하는데, "거리에 따라" 를 정확히 정의하는 것이 무게중심 좌표입니다.
마지막으로 물체가 여러 개면 어느 것이 앞인지 정해야 합니다. 삼각형 단위로 정렬해 뒤부터 그리는 방법(화가 알고리즘)은 삼각형이 서로 관통하거나 순환하며 겹칠 때 답이 없습니다. 그래서 하드웨어는 픽셀마다 깊이를 기억하는 쪽을 골랐습니다.
어떻게 동작하나
무게중심 좌표는 점 P 를 세 꼭짓점의 가중합 P = u*A + v*B + w*C 로 적었을 때의 (u, v, w) 입니다. 세 값의 합은 항상 1 이고, 셋이 모두 0 이상이면 P 는 삼각형 안에 있습니다. 이것이 안팎 판정과 값 보간을 동시에 해결합니다.
d = (By-Cy)(Ax-Cx) + (Cx-Bx)(Ay-Cy) # 삼각형 넓이의 두 배(부호 있음)u = ((By-Cy)(Px-Cx) + (Cx-Bx)(Py-Cy)) / dv = ((Cy-Ay)(Px-Cx) + (Ax-Cx)(Py-Cy)) / dw = 1 - u - v실제 구현은 삼각형의 경계 상자 안만 훑습니다. 화면 전체를 도는 것에 비해 훨씬 빠르고, GPU 도 타일 단위로 같은 일을 합니다.
색뿐 아니라 깊이도 같은 방식으로 보간합니다. 픽셀의 깊이를 구했으면 그 자리의 깊이 버퍼 값과 비교해 더 가까울 때만 색과 깊이를 함께 씁니다. 이것이 z-버퍼이고, 그리는 순서에 상관없이 같은 그림이 나오게 해 줍니다. 순서 독립성이 이 방법의 진짜 값어치입니다 — 반투명이 아닌 이상 정렬이 필요 없어집니다.
뒷면 제거(back-face culling) 는 그보다 앞에서 걸러 냅니다. 화면 좌표에서 세 꼭짓점을 도는 방향이 시계인지 반시계인지는 부호 있는 넓이로 알 수 있습니다.
signed_area = ((Bx-Ax)(Cy-Ay) - (Cx-Ax)(By-Ay)) / 2닫힌 물체라면 뒷면은 어차피 앞면에 가려지므로, 이 부호 하나로 삼각형의 절반을 래스터라이즈 전에 버릴 수 있습니다. 공짜로 얻는 두 배입니다.
보간에는 한 가지 함정이 더 있습니다. 화면 좌표에서 구한 무게중심 좌표로 값을 그냥 섞으면, 원근이 들어간 장면에서는 틀린 결과가 나옵니다. 화면에서 균등하게 나뉜 간격이 3차원 공간에서는 균등하지 않기 때문입니다. 색은 눈에 잘 안 띄지만 텍스처는 확실히 뒤틀립니다. 올바른 방법은 값을 w 로 나눈 상태에서 보간한 뒤 마지막에 되돌리는 것이고, 이것을 원근 보정 보간이라고 합니다. 셰이더 코스에서 이 뒤틀림을 눈으로 보게 됩니다.
GPU 가 픽셀을 하나씩 처리하지 않고 2x2 묶음으로 처리한다는 것도 알아 둘 값어치가 있습니다. 프래그먼트 셰이더가 텍스처의 상세 단계를 고르려면 이웃 픽셀과의 차이를 알아야 하는데, 그 차이는 묶음 안에서 구합니다. 그래서 삼각형이 아주 가늘면 묶음의 절반 이상이 버려지는 계산이 되고, 삼각형을 잘게 쪼갤수록 이 낭비가 커집니다.
현장에서 만나는 모습
인접한 두 삼각형 사이에 한 줄짜리 틈이 보이는 버그가 있습니다. 공유하는 변 위의 픽셀을 양쪽 다 자기 것이 아니라고 판정하면 생깁니다. 반대로 양쪽 다 자기 것이라고 하면 그 픽셀이 두 번 그려져 반투명 합성에서 색이 진해집니다. 그래서 명세는 채우기 규칙(top-left rule) 을 정해 경계 픽셀의 주인을 하나로 정합니다.
또 하나는 z-파이팅입니다. 두 면이 거의 같은 깊이에 있으면 깊이 버퍼의 정밀도 안에서 앞뒤가 흔들려 화면이 지글거립니다. 해법은 두 면을 실제로 떨어뜨리거나, 깊이 오프셋을 주거나, near 평면을 키우는 것입니다.
그리고 뒷면 제거는 닫힌 물체에서만 안전합니다. 종이 한 장이나 나뭇잎처럼 양면을 다 봐야 하는 것에 켜 두면 특정 각도에서 통째로 사라집니다.
마지막으로 성능 감각을 하나 붙여 둡니다. 래스터라이즈의 비용은 삼각형 개수가 아니라 채우는 픽셀 수에 가깝게 비례합니다. 화면을 가득 채우는 큰 삼각형 하나가 화면 밖의 작은 삼각형 천 개보다 비쌉니다. 그래서 최적화의 방향은 두 갈래로 갈립니다. 하나는 화면에 안 나올 것을 미리 버리는 것이고(절두체 컬링, 뒷면 제거, 가림 판정), 다른 하나는 같은 픽셀을 여러 번 칠하지 않는 것입니다. 뒤쪽 문제를 오버드로라고 하는데, 불투명 물체를 앞에서 뒤 순서로 그리면 깊이 판정이 먼저 걸러 주므로 프래그먼트 계산이 줄어듭니다. 깊이 버퍼가 정렬을 필요 없게 만들어 주지만, 그래도 성능을 위해서는 대략적인 정렬이 이득이라는 뜻입니다.
다음 실습에서 할 것
무게중심 좌표를 직접 만들고, 그것으로 삼각형을 채운 뒤 꼭짓점 색을 보간합니다. 그다음 서로 겹치는 두 삼각형에 깊이를 주어 z-버퍼로 앞뒤를 가르고, 깊이만 맞바꿔 그림이 어떻게 달라지는지 눈으로 봅니다. 마지막에는 부호 있는 넓이로 뒷면을 골라 버립니다.