테셀레이션 평가 셰이더
고친 사람 github-actions[bot]
테셀레이션 평가 셰이더는 도형을 잘게 쪼개서 생긴 새 점마다 놓일 위치를 계산하는 프로그램입니다. 그래픽 카드가 굵직한 도형을 잘게 쪼개면 새 점이 많이 생깁니다. 이 점들은 처음에 도형 안에서의 비율 좌표만 갖고 있습니다. 이 프로그램이 그 좌표를 받아 매끈한 곡면이나 울퉁불퉁한 지형 위의 한 점으로 옮겨 놓습니다.
쉽고 빠른 이해
테셀레이션 평가 셰이더는 쪼개서 생긴 새 점 하나를 받아 그 점의 위치 하나를 내놓는 짧은 프로그램입니다. 네 귀퉁이만 적어 보낸 땅 한 조각이 수천 개의 점으로 쪼개지면, 이 프로그램이 점마다 높이를 붙여 언덕과 골짜기를 만듭니다.
이게 없으면 쪼개서 생긴 점들이 전부 평평한 원래 도형 위에 머뭅니다. 점을 늘려도 모양이 안 바뀌니 쪼갠 보람이 없습니다. 세밀한 모형을 처음부터 전부 적어 보내면 메모리와 전송량이 커집니다.
어떻게 도나:
- 앞 단계가 도형 한 조각을 얼마나 잘게 쪼갤지 정합니다
- 그래픽 카드에 박힌 장치가 그 조각 안에 새 점을 고르게 찍습니다. 점마다 비율 좌표가 붙습니다
- 이 프로그램이 점 하나마다 한 번씩 돕니다. 비율 좌표와 조각의 귀퉁이 값을 섞어 위치를 냅니다
대가는 쪼갤수록 도는 횟수가 불어난다는 것입니다. 가로세로를 두 배로 쪼개면 점은 약 네 배가 됩니다. 이웃한 조각끼리 맞닿은 변을 서로 다르게 쪼개면 그 사이에 틈이 벌어집니다. 그래서 평평하고 가까이 볼 일이 없는 물체는 쪼개지 않고 그립니다. 쪼갠 점을 휘거나 밀어 올려 모양을 바꿀 때 씁니다.
상세
모눈을 그린 고무 판 한 장을 떠올려 보십시오. 네 귀퉁이를 잡고 당기거나 비틀면 모눈의 교차점도 저마다 새 곳으로 옮겨 갑니다. 가로로 절반, 세로로 사분의 일에 있던 교차점이 지금 어디 있는지는 귀퉁이를 어떻게 당겼는지만 알면 구할 수 있습니다.
테셀레이션 평가 셰이더가 하는 일이 이 마지막 계산입니다. 그래픽 카드는 도형 한 조각을 잘게 쪼개 새 점을 만듭니다. 새 점마다 조각 안에서의 비율 좌표가 붙어 옵니다. 이 셰이더는 그 좌표와 조각의 귀퉁이 값을 받아 점이 놓일 위치를 계산합니다.
이 절은 도형을 쪼개는 테셀레이션이 왜 필요한지부터 봅니다. 그다음 쪼개는 일을 셋으로 나눈 구조를 그림으로 봅니다. 마지막으로 평가 셰이더가 하는 계산을 코드 한 벌로 확인합니다.
도형을 쪼개는 까닭
정점(꼭짓점)은 도형을 이루는 점 하나입니다. 이 글에서는 꼭짓점이라고 부릅니다. 꼭짓점 셋이 모이면 삼각형 하나가 됩니다. 화면에 보이는 모형은 그 삼각형을 수만 개 이어 붙인 것입니다.
그래픽 카드(GPU, Graphics Processing Unit, 그래픽 처리 장치)는 이 삼각형들을 받아 화면에 그립니다. 꼭짓점 목록은 메모리에서 그래픽 카드로 넘어가야 그려집니다.
매끈한 곡면을 삼각형으로 흉내 내려면 삼각형이 아주 많아야 합니다. 삼각형이 적으면 둥근 표면이 모나 보입니다. 삼각형이 많으면 꼭짓점 목록이 커집니다. 그만큼 메모리를 차지하고 그래픽 카드로 넘기는 데 시간이 듭니다.
테셀레이션은 이 두 요구를 떼어 놓습니다. 성긴 도형만 넘깁니다. 잘게 쪼개는 일은 그래픽 카드 안에서 합니다. 쪼개는 정도를 그릴 때마다 정할 수 있으므로 가까운 물체는 촘촘히, 먼 물체는 성기게 쪼갤 수 있습니다. 거리에 따라 세밀함을 바꾸는 이 방법을 레벨 오브 디테일이라고 부릅니다.
패치와 제어점
테셀레이션에 넘기는 도형 한 조각을 패치라고 부릅니다. 패치는 삼각형이 아니라 꼭짓점 몇 개를 한 덩이로 묶은 것입니다. 몇 개를 한 덩이로 묶을지는 그리는 쪽이 정합니다.
패치를 이루는 꼭짓점은 제어점이라고 부릅니다. 제어점은 모양을 잡는 손잡이입니다. 쪼갠 뒤 생기는 새 점들은 제어점이 어디 있는지를 보고 위치가 정해집니다.
사각형 땅 한 조각이라면 네 귀퉁이를 제어점으로 둡니다. 휘어진 면이라면 제어점을 더 둡니다. 제어점 몇 개로 매끈한 곡면을 정하는 수식이 베지어 곡면입니다. 가로세로 네 줄씩 제어점 열여섯 개를 두는 꼴이 흔히 쓰입니다.
쪼개는 일을 맡은 세 부분
테셀레이션은 한 단계가 아니라 세 부분이 이어 붙은 것입니다. 앞뒤 둘은 프로그램을 적어 넣는 부분입니다. 가운데 하나는 그래픽 카드에 박힌 동작대로 돕니다.
그래픽 카드가 삼각형을 화면에 그리기까지는 여러 단계를 차례로 거칩니다. 이 흐름 전체를 그래픽스 파이프라인이라고 부릅니다. 단계 중 몇은 만드는 쪽이 프로그램을 적어 꽂습니다. 그렇게 꽂는 프로그램을 통틀어 셰이더라고 부릅니다.
테셀레이션은 정점 셰이더 다음에 들어갑니다. 정점 셰이더는 꼭짓점 하나마다 한 번 도는 셰이더입니다. 테셀레이션을 쓸 때는 패치의 제어점 하나마다 한 번씩 돕니다.
첫째 부분은 테셀레이션 제어 셰이더입니다. 이 글에서는 제어 셰이더라고 줄여 부릅니다. 패치 하나를 받아 얼마나 잘게 쪼갤지 정합니다. 쪼개는 정도를 적은 수를 테셀레이션 수준이라고 부릅니다.
둘째 부분은 테셀레이터입니다. 프로그램을 적어 넣지 못하는 고정 기능 단계입니다. 수준을 받아 패치 안에 새 점을 고르게 찍습니다. 찍는 것은 위치가 아니라 패치 안에서의 비율 좌표뿐입니다. 제어점이 어디 있는지는 보지 않습니다.
테셀레이터는 점을 찍으면서 그 점들을 어떻게 이어 삼각형을 만들지도 정합니다. 평가 셰이더가 위치를 정한 점들은 이 순서대로 이어져 삼각형이 됩니다.
셋째 부분이 이 글의 표제어입니다. 이 글에서는 평가 셰이더라고 줄여 부릅니다. 새 점 하나마다 한 번 돕니다. 돌 때마다 그 점의 비율 좌표와 패치의 제어점 전부를 받습니다. 둘을 섞어 그 점의 위치 하나를 내놓습니다.
평가 셰이더 뒤에는 지오메트리 셰이더가 올 수 있습니다. 도형 한 개를 받아 여러 개로 불리거나 버리는 단계입니다. 비워 둘 수 있는 단계라서 없으면 평가 셰이더가 낸 점이 곧장 뒤로 넘어갑니다.
맨 뒤의 래스터화는 삼각형이 화면의 어느 픽셀을 덮는지 골라냅니다. 픽셀은 화면을 이루는 점 하나입니다. 쪼개는 일은 이 단계보다 앞에서 끝나야 합니다.
flowchart TD
A["정점 셰이더 · 제어점마다"] --> B["제어 셰이더 · 패치마다 · 수준을 정한다"]
B -->|테셀레이션 수준| C["테셀레이터 · 새 점의 비율 좌표를 찍는다"]
B -->|패치의 제어점 전부| D["평가 셰이더 · 새 점마다 · 위치를 낸다"]
C -->|점 하나의 비율 좌표| D
D --> E["지오메트리 셰이더 · 있을 때만"]
E --> F["래스터화 · 덮인 픽셀을 고른다"]
그림에서 제어점은 테셀레이터를 건너뜁니다. 테셀레이터는 좌표만 만듭니다. 제어점은 제어 셰이더에서 평가 셰이더로 곧장 갑니다. 좌표와 제어점은 평가 셰이더에서 처음 만납니다.
일을 이렇게 나눈 까닭이 있습니다. 패치 안에 새 점을 고르게 찍는 일은 어떤 곡면을 그리든 같습니다. 그래서 그래픽 카드에 박아 둡니다. 곡면을 정하는 수식은 그리는 것마다 달라서 프로그램으로 남겨 둡니다.
세 부분 중 평가 셰이더를 빼 두면 테셀레이션 전체가 꺼집니다. 테셀레이터가 찍은 좌표를 위치로 바꿔 줄 곳이 없기 때문입니다.
테셀레이터가 찍는 비율 좌표
테셀레이터가 점을 찍는 가상의 판을 도메인이라고 부릅니다. 판의 모양은 사각형·삼각형·선 셋 중 하나입니다. 판은 실제 공간의 크기와 상관없이 늘 가로세로 0에서 1까지로 잽니다.
Direct3D에서는 평가 셰이더를 도메인 셰이더라고 부릅니다. 이 판을 도메인이라 부르는 데서 온 이름입니다. 제어 셰이더에 해당하는 단계는 헐 셰이더라고 부릅니다.
사각형 판에서는 점마다 비율 좌표가 두 개 붙습니다. u 는 가로로 몇 분의 몇 지점인지, v 는 세로로 몇 분의 몇 지점인지를 나타냅니다. 수준 2로 쪼개면 가로세로가 각각 두 토막이 됩니다. 0 · 0.5 · 1 에 줄이 하나씩 생겨 점 아홉 개가 찍힙니다.
block-beta columns 3 a["(0, 1)"] b["(0.5, 1)"] c["(1, 1)"] d["(0, 0.5)"] e["(0.5, 0.5)"] f["(1, 0.5)"] g["(0, 0)"] h["(0.5, 0)"] i["(1, 0)"]
칸에 적힌 값이 그 점이 받는 (u, v)입니다. 위로 갈수록 v 가 커지게 놓았습니다. 이 판에는 제어점이 어디 있는지에 대한 정보가 하나도 없습니다. 아홉 점이 어디에 놓일지는 평가 셰이더가 정합니다.
수준은 두 가지로 나뉩니다. 바깥 수준은 판의 변마다 몇 토막 낼지를 따로 정합니다. 변마다 따로 정하는 까닭은 뒤의 「쪼갤수록 늘어나는 일」 소절에서 봅니다.
안쪽 수준은 판의 안쪽을 가로세로 몇 토막으로 나눌지 정합니다. 앞의 아홉 점 예는 바깥 수준과 안쪽 수준을 모두 2로 둔 경우입니다. 네 변이 두 토막씩, 안쪽도 가로세로 두 토막씩 나뉘었습니다.
삼각형 판에서는 비율 좌표가 세 개 붙습니다. 세 값은 점이 세 귀퉁이에 각각 얼마나 가까운지를 나타냅니다. 셋을 더하면 늘 1 입니다. 귀퉁이 하나에 붙은 점은 (1, 0, 0), 한가운데 점은 (1/3, 1/3, 1/3)입니다. 이런 좌표를 무게중심 좌표라고 부릅니다.
선 판은 0에서 1까지의 사각형 위에 가로줄 여러 개를 긋는 판입니다. 점은 그 줄 위에만 찍힙니다. u 는 줄 위에서 몇 분의 몇 지점인지, v 는 그 점이 몇 번째 줄에 있는지를 나타냅니다.
선 판에 찍힌 점은 삼각형이 아니라 선으로 이어집니다. 머리카락이나 풀잎처럼 가는 선 여러 가닥을 만들 때 씁니다.
좌표를 위치로 바꾸는 계산
평가 셰이더는 새 점 하나마다 한 번 돕니다. 받는 것은 둘입니다. 그 점의 비율 좌표 하나와 패치의 제어점 전부입니다. 내놓는 것은 그 점의 위치 하나입니다.
가장 단순한 계산은 귀퉁이 사이를 비율대로 섞는 것입니다. 두 값 사이를 비율대로 섞는 계산을 선형 보간이라고 부릅니다. 0 과 10 사이를 0.3 비율로 섞으면 3 이 나옵니다.
사각형 패치는 두 번 섞습니다. 아래 변의 두 귀퉁이를 u 로 섞습니다. 위 변의 두 귀퉁이도 u 로 섞습니다. 그렇게 얻은 두 점을 다시 v 로 섞으면 패치 안의 한 점이 나옵니다.
평가 셰이더는 점을 늘리거나 없애지 못합니다. 점 하나가 들어오면 위치 하나가 나갑니다. 점이 몇 개 생길지는 앞의 두 부분이 이미 정해 두었습니다. 도형 개수를 바꿀 수 있는 지오메트리 셰이더와 이 대목이 갈립니다.
코드 한 벌
셰이더를 적는 언어는 그래픽 API(Application Programming Interface)마다 다릅니다. 여기서 API 는 프로그램이 그래픽 카드에 그리기를 시키는 함수 묶음입니다.
아래는 OpenGL의 셰이더 언어 GLSL(OpenGL Shading Language)로 적은 평가 셰이더입니다.
패치의 제어점 넷은 왼쪽 아래 (0,0), 오른쪽 아래 (4,0), 왼쪽 위 (0,4), 오른쪽 위 (6,6)에 있습니다. 오른쪽 위 귀퉁이만 비스듬히 끌려 나간 사각형입니다. 테셀레이터가 넘긴 비율 좌표는 판 한가운데인 (0.5, 0.5)입니다.
layout(quads, equal_spacing, ccw) in;
uniform mat4 mvp;
void main() {
float u = gl_TessCoord.x; // 0.5
float v = gl_TessCoord.y; // 0.5
vec4 c0 = gl_in[0].gl_Position; // (0,0)
vec4 c1 = gl_in[1].gl_Position; // (4,0)
vec4 c2 = gl_in[2].gl_Position; // (0,4)
vec4 c3 = gl_in[3].gl_Position; // (6,6)
vec4 lo = mix(c0, c1, u); // (2,0)
vec4 hi = mix(c2, c3, u); // (3,5)
vec4 p = mix(lo, hi, v); // (2.5,2.5)
gl_Position = mvp * p;
}
첫 줄은 테셀레이터에게 주는 주문입니다. 판의 모양은 평가 셰이더가 고릅니다. 찍힌 좌표를 읽는 쪽이 판의 모양을 알아야 하기 때문입니다. 적는 값은 셋입니다.
| 적는 값 | 무엇을 정하나 |
|---|---|
quads |
판을 사각형으로 고른다. 삼각형이면 triangles, 선이면 isolines |
equal_spacing |
변을 같은 길이의 토막으로 나눈다 |
ccw |
새로 만든 삼각형의 꼭짓점을 시계 반대 방향 순서로 잇는다 |
꼭짓점을 잇는 방향은 삼각형의 앞면과 뒷면을 가르는 데 씁니다. 방향이 뒤집히면 보여야 할 면이 뒷면으로 처리됩니다.
gl_TessCoord 가 테셀레이터가 넘긴 비율 좌표입니다. 사각형 판이라 앞의 두 값만 씁니다.
gl_in 은 패치의 제어점 배열입니다. gl_Position 은 꼭짓점의 위치를 담는 값입니다. 몇 번 칸이 어느 귀퉁이인지는 그리는 쪽이 제어점을 넘길 때 정한 순서를 따릅니다.
mix(a, b, t) 가 선형 보간입니다. a 에서 b 쪽으로 t 만큼 간 값을 돌려줍니다. 아래 두 귀퉁이를 섞은 것이 lo, 위 두 귀퉁이를 섞은 것이 hi 입니다.
vec4 는 값 넷을 담는 꼴입니다. 이 예에서 뒤의 두 값은 안 바뀌므로 오른쪽 주석에는 앞의 두 값(가로·세로)만 적었습니다.
주석을 따라가 보면 판 한가운데 좌표가 (2.5, 2.5)에 놓였습니다. 네 귀퉁이가 반듯한 정사각형이었다면 (2,2)였을 점입니다. 오른쪽 위 귀퉁이가 끌려 나간 만큼 가운데 점도 그쪽으로 따라갔습니다.
마지막 줄의 mvp 는 모형 안의 좌표를 화면에 비추기 위한 좌표로 바꾸는 행렬입니다. 바뀐 뒤의 좌표 공간을 클립 공간이라고 부릅니다. 테셀레이션이 없으면 이 변환은 정점 셰이더가 맡습니다. 테셀레이션을 쓰면 평가 셰이더가 맡습니다. 새로 생긴 점은 정점 셰이더를 지나지 않기 때문입니다.
곡면과 지형
위 예는 사각형 안에 점을 고르게 까는 데서 끝납니다. 평가 셰이더를 쓰는 까닭은 이 계산을 바꿔 모양을 만들 수 있어서입니다. 이 소절은 대표적인 쓰임 둘을 봅니다.
첫째는 곡면입니다. 선형 보간 대신 베지어 곡면의 수식으로 제어점 열여섯 개를 섞으면, 새 점이 제어점 사이로 매끈하게 휜 면 위에 놓입니다. 수준을 올릴수록 삼각형이 작아져 그 곡면에 가까워집니다.
둘째는 지형입니다. 그래픽 카드 메모리에 올려 둔 그림 데이터를 텍스처라고 부릅니다. 평가 셰이더도 이 텍스처를 읽을 수 있습니다.
칸마다 밝기 대신 높이를 적은 텍스처가 높이 맵입니다. 평가 셰이더는 점의 비율 좌표로 높이 맵의 한 칸을 읽습니다. 그리고 읽은 높이만큼 점을 위로 올립니다. 평평한 사각형 패치가 언덕과 골짜기로 바뀝니다.
이렇게 텍스처에서 읽은 값으로 표면을 밀어 올리는 기법이 변위 매핑입니다. 성긴 패치 하나와 높이 맵 한 장만 넘기면 세밀한 지형이 그려집니다.
표면이 휘면 빛을 계산할 때 쓰는 방향도 바뀝니다. 면이 향한 방향을 법선이라고 부릅니다. 그래서 변위 매핑을 쓰는 평가 셰이더는 위치와 함께 새 점의 법선도 흔히 다시 구해 내보냅니다.
쪼갤수록 늘어나는 일
테셀레이션 수준은 올리는 만큼 일이 늘어납니다. 이 소절은 수준에 따라 늘어나는 양을 먼저 봅니다. 그다음 수준을 정할 때 생기는 흠 둘을 봅니다.
평가 셰이더는 새 점마다 한 번 돌므로 도는 횟수가 곧 점 수입니다. 사각형 판에서 모든 수준을 n 으로 같게 두면 점은 (n+1)² 개, 삼각형은 2n² 개가 생깁니다.
| 수준 | 새 점 수 · 평가 셰이더가 도는 횟수 | 삼각형 수 |
|---|---|---|
| 1 | 4 | 2 |
| 2 | 9 | 8 |
| 4 | 25 | 32 |
| 8 | 81 | 128 |
| 16 | 289 | 512 |
표를 보면 수준을 두 배로 올릴 때마다 점과 삼각형이 약 네 배가 됩니다. 이 양이 패치 하나의 몫이라서 패치 수를 곱한 만큼 전체 일이 쌓입니다.
삼각형이 픽셀 하나보다 작아지면 더 쪼개도 화면의 모양은 나아지지 않습니다. 그래도 래스터화가 다룰 삼각형 수는 계속 늘어납니다. 그래서 수준은 패치가 화면에서 차지하는 크기를 보고 정합니다.
처음부터 평평하고 가까이 볼 일이 없는 물체는 쪼개도 달라지는 것이 없습니다. 그런 물체는 테셀레이션을 끄고 그립니다. 평가 셰이더가 값을 하는 때는 쪼갠 점을 휘거나 밀어 올려 모양을 바꿀 때입니다.
첫째 흠은 모양이 툭 튀는 것입니다. 수준이 정수로만 바뀌면 카메라가 다가올 때 점이 한꺼번에 생깁니다. 그 순간 모양이 눈에 띄게 달라집니다. 이렇게 모양이 튀는 현상을 팝핑이라고 부릅니다.
equal_spacing 대신 fractional_odd_spacing 이나 fractional_even_spacing 을 고르면 수준에 소수를 쓸 수 있습니다. 새 점이 이웃한 점 옆에서 조금씩 벌어져 나오므로 모양이 서서히 바뀝니다.
둘째 흠은 패치 사이에 벌어지는 틈입니다. 이웃한 두 패치는 변 하나를 나눠 씁니다. 두 패치가 그 변을 다른 수로 쪼개면 변 위에 찍힌 점이 서로 어긋납니다. 어긋난 점 사이로 틈이 벌어져 뒤가 비쳐 보입니다.
바깥 수준을 변마다 따로 두는 까닭이 이것입니다. 맞닿은 변의 수준은 그 변만 보고 계산합니다. 변 두 끝의 위치처럼 양쪽 패치가 똑같이 아는 값에서 구하면 양쪽이 같은 수를 얻습니다.
평가 셰이더도 같은 약속을 지켜야 합니다. 변 위의 점이 양쪽 패치에서 같은 위치로 가야 합니다. 높이 맵을 읽는다면 양쪽이 같은 칸을 읽어 같은 높이를 얻어야 틈이 안 생깁니다.
관련 항목
테셀레이션 평가 셰이더 앞뒤에 놓이는 처리 단계
그래픽스 파이프라인 · 입력 어셈블러 · 정점 셰이더 · 테셀레이션 제어 셰이더 · 테셀레이터 · 지오메트리 셰이더 · 프리미티브 조립 · 클리핑 · 래스터화 · 프래그먼트 셰이더
테셀레이션을 이루는 구성 요소
테셀레이션 · 패치 · 제어점 · 테셀레이션 수준 · 테셀레이션 도메인 · 무게중심 좌표 · 아이소라인 · 고정 기능 단계
평가 셰이더로 그리는 곡면과 표면 기법
베지어 곡면 · B-스플라인 · NURBS · 서브디비전 곡면 · 퐁 테셀레이션 · PN 삼각형 · 변위 매핑 · 높이 맵 · 지형 렌더링 · 법선
테셀레이션 수준을 정할 때 따라오는 문제와 기법
레벨 오브 디테일 · 팝핑 · 분수 간격 · T-정션 · 워터타이트 메시 · 쿼드 오버셰이딩 · 프리미티브 수
평가 셰이더 계산에 쓰이는 수학
선형 보간 · 쌍선형 보간 · 행렬 · 동차 좌표 · 벡터
새 점이 거쳐 가는 좌표 공간
모델 좌표계 · 월드 좌표계 · 뷰 좌표계 · 클립 공간 · 정규화 장치 좌표 · 텍스처 좌표
같은 흐름의 다른 단계에 꽂히는 셰이더
셰이더 · 컴퓨트 셰이더 · 메시 셰이더 · 태스크 셰이더 · 셰이더 단계
Direct3D 에서 테셀레이션을 이루는 단계
헐 셰이더 · 패치 상수 함수 · 테셀레이터 단계 · 출력 제어점
테셀레이션 단계를 정의하는 그래픽 API
OpenGL · OpenGL ES · Direct3D · Vulkan · Metal · API
평가 셰이더를 적는 언어와 그 처리
GLSL · HLSL · SPIR-V · 셰이더 컴파일 · 내장 변수 · 레이아웃 한정자
평가 셰이더를 돌리는 그래픽 카드와 그 성질
GPU · 셰이더 코어 · 통합 셰이더 구조 · 병렬 처리 · 비디오 메모리 · 메모리 대역폭 · 텍스처
테셀레이션 평가 셰이더가 속하는 상위 분류
다른 이름: tessellation evaluation shader · 도메인 셰이더 · domain shader · 테셀레이션 이밸류에이션 셰이더