프리미티브(primitive)
- GPU 파이프라인이 처리하는 기하적인 개체
GPU 테썰레이션(tessellation)
- 하나의 프리미티브를 여러 개의 작은 프리미티브로 잘게 나누는 것
- OpenGL ES 3.2부터 지원 시작
- 이를 위해 테썰레이션 컨트롤 쉐이더(tessellation control shader, 이하 컨트롤 쉐이더로 약칭), 테썰레이션 생성 쉐이더(tessellation evaluation shader, 이하 생성 쉐이더로 약칭) 추가되었고, 두 쉐이더 사이에는 하드웨어로 고정된(hardwired) 단계인 테썰레이션 프리미티브 생성기(tessellation primitive generator, 이하 테썰레이터로 약칭)가 놓인다.
18.1 변위 매핑
노멀 매핑은 기초 표면(base surface) 자체를 오돌토돌하게 바꾸는 것이 아니라 참조 표면(reference surface)의 노멀을 사용하여 오돌토돌한 표면을 '흉내'내었다.
변위 매핑
- 기초 표면을 고해상도 메시로 바꾼 후 그 메시의 정점 위치를 옮겨서(displace) 실제로 오돌토돌한 메시를 만드는 것
18.1.1 변위 매핑을 위한 GPU 테썰레이션
변위 매핑 구현을 위해서는 컨트롤 쉐이더, 테썰레이터, 생성 쉐이더가 순차적으로 작업을 수행한다.
컨트롤 쉐이더의 입력은 패치(patch)라고 부르는데, 이는 GL 3.2에 새로 추가된 프리미티브 타입으로, 사각형(quard)과 삼각형 중 하나이다.
컨트롤 쉐이더는 사각형 패치를 입력으로 받아서 이를 그대로 생성 쉐이더에게 넘긴다.
컨트롤 쉐이더는 이 사각형 패치를 얼마나 잘게 나눌지 결정해서 테썰레이터에게 알려줘야 한다. 이를 테썰레이터 레벨이라고 한다.
테썰레이터는 컨트롤 쉐이더가 지정해주는 대로 사각형 패치의 정의역(domain)을 잘게 나눠 2차원 메시를 구성한다.
위 사진의 우측에서, 면적이 1인 정사각형 영역이 2차원 메시로 변한 것을 볼 수 있다(사각형 패치에서 우측 그래프처럼 표시한 사각형 말하는 것).
이러한 메시의 정점마다 $\left( u, v \right)$ 좌표가 할당된다.
2차원 메시의 각 정점에 대해 생성 쉐이더가 한 번 실행되는데, 여러 개의 정점이 여러 개의 생성 쉐이더에 의해 병렬 처리된다.
- 컨트롤 쉐이더가 건네준 사각형 패치를 겹선형 패치(bilinear patch)로 간주하고 테썰레이터가 건네준 $\left( u, v \right)$를 사용해서 사각형 패치의 한 점을 생성한다.
- GL 프로그램은 하이트맵을 생성 쉐이더에게 제공하는데, 생성 쉐이더는 이로부터 높이 값을 추출한다.
- 사각형 패치의 점을 이 높이 값만큼 수직 방향으로 올린다.
2차원 메시의 모든 정점이 위의 방식으로 처리되면, 위 사진의 하단과 같은 오돌토돌한 고해상도 메시가 만들어진다.
이 메시는 GPU 파이프라인의 다음 단계로 넘겨진다.
즉, 기하 쉐이더가 있다면 기하 쉐이더를 거쳐 래스터라이저에게, 기하 쉐이더가 없다면 직접 래스터라이저로 넘겨져서 최종적으로 스크린에 출력된다.
사각형 패치를 실제로 수정하는 변위 매핑은, 기초 표면을 그대로 놓아둔 채 오돌토돌한 표면을 흉내 냈던 노멀 매핑과 근본적으로 다른 기법이다.
결과물의 테두리를 보면, 노멀 매핑을 이용한 것은 일직선이었던 반면에 변위 매핑을 이용한 것은 실제로 변형되어 구불구불한 것을 볼 수 있다.
18.1.2 변위 매핑을 위한 GL 프로그램, 쉐이더, 테썰레이터
변위 매핑을 위해 GL 프로그램은 먼저 컨트롤 및 생성 쉐이더를 위한 쉐이더 오브젝트를 각각 만들어야 한다(정점 및 프래그먼트 쉐이더를 위해서도 각각 쉐이더 오브젝트를 만들었다).
이를 위해서 glCreateShader를 호출하는데, 파라미터로 GL_TESS_CONTROL_SHADER와 GL_TESS_EVALUATION_SHADER를 사용한다.
이 쉐이더 오브젝트들도 마찬가지로 프로그램 오브젝트에 붙여(attach)져야 한다.
GL 프로그램은 패치 프리미티브를 구성하는 정점의 개수를 지정해야 한다.
변위 매핑에 사각형 패치가 사용되므로, glPatchParameteri(GL_PATCH_VERTIVES, 4)를 호출한다.
변위 매핑을 위한 정점 쉐이더
정점 쉐이더의 의무 사항이었던 클립 공간 좌표 출력이 GL 3.2에서는 해제되었다.
정점 쉐이더 대신 생성 쉐이더가 gl_Position을 출력한다.
변위 매핑을 위한 컨트롤 쉐이더
패치가 입력으로 주어지면, 한 정점에 컨트롤 쉐이더가 한식 배정되어 공동 작업을 수행한 후 하나의 패치를 출력한다.
출력되는 정점의 개수는 컨트롤 쉐이더에 vertices라는 키워드로 지정되는데, 위 코드에서는 layout(vertices=4) out; 에서 정점의 개수를 4로 지정했다. 이는 네 개의 컨트롤 쉐이더가 공동 작업한다는 뜻이다.
컨트롤 쉐이더에 입력되는 정점의 애트리뷰트들은 정점 쉐이더가 출력해야 한다.
정점 쉐이더에서 출력한 v_position, v_texCoord가 컨트롤 쉐이더에서는 입력으로 선언된다.
컨트롤 쉐이더 간 정점 배분은 내장 변수인 gl_InvocationID를 사용해 이뤄진다.
사각형 패치 입력에 대해 총 네 개의 컨트롤 쉐이더가 공동 작업할 때, gl_InvocationID는 0부터 3까지의 값을 가진다.
컨트롤 쉐이더는 생성 쉐이더에게 패치를 출력함과 동시에 테썰레이터에게 테썰레이션 레벨을 제공해야 한다.
이를 위해 gl_TessLevelInner와 gl_TessLevelOuter라는 내장 변수를 사용한다.
테썰레이터는 gl_TessLevelInner를 사용해 사각형 패치의 정의역 내부를 분할한다.
그림(a)는 수직 방향으로 8등분하고 수평 방향으로 5등분된 정의역 내부를 보여준다.
또한, 테썰레이터는 gl_TessLevelOuter를 사용해 사각형 패치의 정의역 경계를 분할하는데, 그림 (b)에 보인 것처럼 네 개의 테두리는 독립적으로 분할된다.
테썰레이터는 분할된 정의역 전체를 삼각형들로 채운다.
그림 (c)를 보면, 2차원 삼각형 메시의 각 정점에는 $\left( u, v \right)$ 좌표가 할당된다.
그림 (d)는 복잡한 테썰레이션 예이다.
변위 매핑을 위한 컨트롤 쉐이더에서 gl_TessLevelInner와 gl_TessLevelOuter에게 같은 값을 부여했지만, 다른 값을 줄 수 있을 뿐 아니라, 컨트롤 쉐이더가 런타임에 계산할 수 있다.
GL 3.2가 제공하는 테썰레이션 기능은 폴리곤 메시가 가지는 '근사적이며 고정적인 모델'이라는 한계를 극복할 수 있게 해준다.
즉, 베지어 곡면과 같은 '정확한 모델'을 입력으로 받아서, 원하는 만큼의 해상도를 가진 메시로 자유자재로 바궈 사용할 ㅅ수 있다.
변위 매핑을 위한 생성 쉐이더
quads는 사각형 패치를 입력으로 받는 다는 것을 말한다.
테썰레이터가 만든 2차원 삼각형 메시의 각 정점마다 하나의 생성 쉐이더가 작동되는데, 정점의 $\left( u, v \right)$ 좌표는 gl_TessCoord라는 내장 변수에 저장되어 생성 쉐이더에게 전달된다.
생성 쉐이더의 메인 함수는 gl_TessCoord에서 $\left(u, v \right)$를 추출한다.
생성 쉐이더는 컨트롤 쉐이더가 출력한 es_position[]을 겹선형 패치의 컨트롤 포인트로 취급하는데, gl_TessCoord에서 추출한 $\left( u,v \right)$를 사용해 겹선형 패치 위의 한 점을 생성한다.
위 그림은 es_position[]을 겹선형보간하여 한 점(position)을 생성하는 과정이다.
이는 코드에서 mix() 부분이 나온는 곳인데, mix(A, B, u)는 (1-u)A + uB를 리턴한다.
es_texCoord[]에 저장된 텍스처 좌표도 같은 방식으로 겹선형보간되어 v_texCoord를 결정한다.
코드에서 // vertical displacement 주석 부분을 보면, 내장 함수 texture를 호출하는데, 이는 v_texCoord를 사용하여 하이트맵(heightMap)을 참조하여 높이 값 height를 리턴한다.
위 그림의 position은 height만큼 수직 방향으로 옮겨진다.(코드에서 dispFactor를 써서 사각형 패치 전체를 어느 정도 높게 할 것인지 일괄적으로 조절한다).
변위(displacement)가 완료된 정점은 클립 공간으로 변환되어 출력된다.
위 사진을 예시로 들면, 16개의 사각형으로 구성된 표면은 사각형 메시(quad mesh)로 정의되었다.
이를 위한 인덱스 배열(index array)은 사각형 당 네 개, 총 64개의 원소를 갖는다.
폴리곤 메시를 렌더링하려면 드로우콜을 해야 하는데, 삼각형 메시를 렌더링했을 때 glDrawElements를 호출했고 첫 번째 파라미터는 GL_TRIANGLES였다. 하지만 지금은 사각형 메시를 렌더링하므로, GL_TRIANGLES 대신 GL 3.2가 제공하는 GL_PATCHES를 쓴다.
즉, glDrawElements(GL_PATCHES, 64, GL_UNSIGNED_SHORT, 0)을 호출한다.
18.2 PN-삼각형
PN-삼각형은 메시의 한 삼각형으로부터 유도한 베지어 삼각형이라고 정의할 수 있다.
P는 point를, N은 normal을 의미한다.
하나의 PN-삼각형은 테썰레이션 과정을 거쳐 다수의 작은 삼각형들을 만들고 이들은 원래의 삼각형을 대체한다.
위 그림을 보면, 저해상도 삼각형 메시를 구성하는 모든 삼각형을 이런 방식으로 처리하면 부드러운 고해상도 메시를 얻을 수 있다.
18.2.1 컨트롤 포인트 계산
위 그림 (a)는 입력으로 주어진 삼각형 메시에서 추출한 삼각형이다.
각 정점은 $\left\{ p_i, n_i \right\}$ 애트리뷰트를 가지고 있는데, $p_i$는 위치를, $n_i$는 노멀을 나타낸다.
이 삼각형을 3차 베지어 삼각형으로 바구기 위해 위 그림 (b)에 보인 컨트롤 포인트를 결정해야 한다.
10개의 컨트롤 포인트 중 모퉁이에 위치한 $p_{300}, p_{030}, p_{003}$은 각각 $p_1, p_2, p_3$로 설정된다.
테두리에 위치한 컨트롤 포인트들은 입력 삼각형의 변의 양 끝 정점 애트리뷰트를 이용하여 결정된다.
예를 들어 $p_{210}$과 $p_{120}$은 $\left\{p_1,n_1\right\}$과 $\left\{ p_2,n_2 \right\}$를 이용하여 계산된다.
위 그림 (c)는 $\left\{ p_{300}, p_{210}, p_{120}, p_{030} \right\}$으로 정의되는 베지어 삼각형의 변을 점선으로 묘사했다. 이 변은 3차 베지어 곡선이다.
위 그림 (d)를 보면, $p_1$과 $p_2$를 잇는 변을 3등분해서 $p_{21}$과 $p_{12}$를 만든다.
이 중 $p_{21}$은 $n_1$과 나라한 벡터 $v_{21}$에 의해 $p_1$의 접면(tangent plane)으로 옮겨져서 $p_{210}$이 된다.
위 그림 (e)도 $p_{12}$가 $n_2$와 나란한 벡터 $v_{12}$에 의해 $p_2$의 접면으로 옮겨져서 $p_{120}$이 됨을 보여준다.
위 그림 (d)에서 $p_{21}$은 다음과 같이 정의할 수 있다. 먼저 $p_1$에서 $p_2$로 향하는 벡터를 3등분한다.
$v_1 = \frac{p_2 - p_1}{3}$
이렇게 구한 $v_1$을 $p_1$에 더하면 $p_{21}$을 얻을 수 있다.
$p_{21} = p_1 + v_1 = \frac{2p_1 + p_2}{3}$
$v_{21}$은 두 벡터 $n$과 $v$의 내적 $n \cdot v$가 $ \left\| n \right\| \left\| v \right\| \cos\theta$로 정의된다는 사실을 이용해 구할 수 있다.
$n$이 단위 벡터이므로 $ n \cdot v $는 $ \left\| v \right\| \cos\theta $이다.
$n$과 $v$가 예각을 이루면 이 길이는 양수가 되지만, 둔각을 이루면 음수가 된다.
그림 (d)에서 $n_1$에 투영된 $v_1$의 길이를 구하면
$n_1 \cdot v_1 = n_1 \cdot \frac{p_2-p_1}{3}$
$n_1$과 $v_1$이 둔각을 이루므로, 음수가 된다. 따라서 양수로 바꾸고 여기에 $n_1$을 곱하면 $v_{21}$을 얻을 수 있다.
$v_{21} = - \left( n_1 \cdot \frac{p_2-p_1}{3} \right)n_1$
마지막으로 $p_{21}$에 $v_{21}$을 더하면 $p_{210}$을 얻는다.
$p_{210} = p_{21} + v_{21} = \frac{2p_1+p_2}{3}-\left( n_2 \cdot \frac{p_1-p_2}{3} \right)n_2$
그림 (b)의 테두리에 위치한 나머지 컨트롤 포인트 $p_{021}$, $p_{012}$, $p_{102}$, $p_{201}$도 비슷한 방법으로 계산된다.
내부 컨트롤 포인트는 테두리에 있는 컨트롤 포인트를 이용하여 결정된다.
먼저 모퉁이 컨트롤 포인트의 평균을 구한다.
$V = \frac{p_{300} + p_{030} + p_{003}}{3}$
모퉁이를 제외한 나머지 6개의 테두리 컨트롤 포인트들의 평균을 구한다.
$E = \frac{p_{210} + p_{120} + p_{021} + p_{012} + p_{102} + p_{201}}{6}$
마지막으로, $E$를 $\frac{E-V}{2}$만큼 옮겨서 $p_{111}$을 결정한다.
$p_{111} = E + \frac{E-V}{2} = \frac{1}{4} \left( p_{210} + p_{120} + p_{021} + p_{012} + p_{102} + p_{201} \right) - \frac{1}{3}\left( p_{300} + p_{030} + p_{003} \right)$
테두리에 있는 컨트롤 포인트를 모두 결정했다. 따라서 그림 (a)처럼 3차 베지어 삼각형의 식 $p\left(u,v,w\right)$를 얻었다.
균일하게 샘플한 $\left(u,v,w\right)$ 좌표들을 이용하여 베지어 삼각형을 테썰레이션 할 수 있다.
즉, 각각의 $\left(u,v,w \right)$를 $p\left(u,v,w \right)$에 대입하여 3차원 정점 $\left(x,y,z \right)$를 얻는데, 이들 정점을 연결하여 고해상도 삼각형 메시를 구성할 수 있다.
메시를 렌더링하기 위해서는 정점 위치만으로는 부족하기에 정점 노멀도 필요하다.
정점 노멀을 계산하는 가장 간단한 방법은, 입력으로 주어진 노멀 $n_1$, $n_2$, $n_3$의 무게중심 조합(barycentric combination)인 $un_1 + vn_2 + wn_3$를 택하는 것이다.
$\left(u, v, w \right)$는 $p\left(u,v,w\right)$에 대입했던 것을 그대로 사용한다.
이 무게중심 조합을 $n\left(u,v,w \right)$라고 표기한다.
그런데, 그림(b) 처럼 $n\left(u,v,w \right)$는 $\left\{n_1,n_2,n_3\right\}$에 기반한 1차 베지어 삼각형이다.
컨트롤 포인트와 대비되도록 $\left\{n_1, n_2, n_3\right\}$를 컨트롤 노멀이라 부른다.
결론은, 균일하게 샘플한 $\left( u, v, w \right)$를 그림 (a)의 $p\left( u, v, w \right)$에 대입하여 정점 위치를 얻고, 그림 (b)의 $n\left(u, v, w \right)$에 대입하여 정점 노멀을 얻는 것이다.
하지만 $n\left( u, v, w \right)$는 1차식이므로 문제를 일으킬 수 있다.
참고
틈새 없는 테썰레이션
위 그림에서 인접한 삼각형 $t_1$과 $t_2$는 변 $\overline{p_1p_2}$를 공유한다.
$t_1$과 $t_2$가 각각 PN-삼각형으로 전환되었다고 가정하면, 두 PN-삼각형은 컨트롤 포인트 $p_{210}$과 $p_{120}$을 공유하게 된다.
왜냐하면 $p_{210}$과 $p_{120}$은 $t_1$과 $t_2$가 공유하는 $\left\{ p_1, n_1 \right\}$과 $\left\{p2, n2\right\}$만을 이용해 계산되기 때문이다.
이후 두 PN-삼각형이 동일한 해상도로 테썰레이션되면 이를 이어붙인 고해상도 메시에는 틈이 생기지 않는다.
18.2.2 컨트롤 노멀 계산
$\left\{ p_i, n_i \right\}$ 애트리뷰트를 가진 정점 3개로 구성된 삼각형이 입력되었고, 이를 3차 베지어 삼각형으로 바꿨다고 가정하면, 위 그림(a)는 이 베지어 삼각형을 옆에서 본 그림이다.
$n_1$과 $n_2$의 방향이 이 그림과 같을 때, $p_1$과 $p_2$를 잇는 베지어 삼각형의 변은 점선과 같을 것이다.
이 변의 한 점 $q$를 생각해보면, 컨트롤 포인트와 컨트롤 노멀 그림 중 컨트롤 포인트의 $p\left( u, v, w \right)$에 $\left( \frac{1}{2}, \frac{1}{2}, 0 \right)$을 대입하여 $q$를 얻은 것으로 보면 된다.
그러면 컨트롤 포인트와 컨트롤 노멀 그림 중 컨트롤 노멀의 $n\left(u, v, w \right)$에 동일한 $\left( \frac{1}{2}, \frac{1}{2}, 0 \right)$을 대입하여 $ \frac{\left( n_1 + n_2 \right)}{2}$를 얻는다.
이를 정규화하여 $q$의 노멀로 취한다. 위 그림(a)에서는 이렇게 얻은 노멀이 점선으로 표현된 곡면과 수직을 이루어 적절해 보인다.
하지만, 1차 베지어 삼각형으로 정의된 $n\left( u, v, w \right)$는 항상 적절한 결과를 만드는 것은 아니다.
위 그림 (b)를 보면, $n_1$과 $n_2$는 같은 방향을 향하고 있지만, $p_1$과 $p_2$에서의 접면은 떨어져 있다.
이런 경우, 1차 베지어 삼각형 $n\left(u,v,w \right)$를 사용해 얻은 노멀은 점선으로 묘사된 실제 곡면에 수직하지 않다.
즉, 노멀의 기능을 제대로 못하고 있다.
반면, 위 그림 (c)는 정상적인 노멀을 제시하고 있는데, $n\left( n,v,w \right)$를 2차 베지어 삼각형으로 정의하면 이러한 노멀을 얻을 수 있다.
이를 위해서는 위 그림 (d)에 보인 것처럼, 6개의 컨트롤 노멀을 사용해야 한다.
이 중 모퉁이에 위치한 $n_{200}, n_{020}, $n_{002}$는 주어진 삼각형의 정점 $n_1, n_2, n_3$로 각각 설정된다.
나머지 컨트롤 노멀 $n_{110},n_{011}, n_{101}$은 새로 정의되어야 한다.
이를 위한 방법 중에서, 위 그림(e)는 휴리스틱한 방법을 보여준다.
$p_1$과 $p_2$를 잇는 선분에 수직인 평면 $P$를 보면, $P$의 노멀 $n_{P}$는 다음과 같이 계산된다.
$n_{P} = \frac{p_1-p_2}{ \left\| p_1 - p_2 \right\| }$
정점 노멀 $n_1$과 $n_2$를 더한 결과를 $n_{12}$라 하면, $n_{12}$와 $n_{P}$의 내적은 $n_{12}$를 $n_{P}$에 투영한 길이가 된다.
이를 $-2n_{P}$와 곱한 후 $n_{12}$에 더한 것을 $n_{12}'$라 하면, 이는 $n_{12}$를 $P$에 대해 반사시킨 것이다.
이렇게 계산된 $n_{12}'$를 정규화하면 컨트롤 노멀 $n_{110}$을 얻을 수 있다.
다른 컨트롤 노멀 $n_{011}$과 $n_{101}$도 비슷한 방법으로 계산된다.
컨트롤 포인트와 컨트롤 노멀 그림 중 컨트롤 노멀 부분의 1차 베지엉 삼각형보다는 계선되었지만, 2차 베지어 삼각형인 $n\left(u, v, w \right)$는 3차 베지어 삼각형인 $p\left( u, v, w \right)$와 어울리지 않는다고 생각할 수 있다.
하지만 2차 베지어 삼각형 $n\left( u, v, w \right)$로도 대체로 만족스러운 노멀을 계산할 수 있다.
또한, $n\left( u, v, w \right)$를 3차 베지어 삼각형으로 정의할 특별한 방법을 찾기도 어렵다.
18.2.3 PN-삼각형을 위한 GPU 테썰레이션
컨트롤 쉐이더는 입력으로 받은 삼각형 패치로부터 3차 베지어 삼각형 $p\left(u,v,n\right)$의 컨트롤 포인트를 계산한다.
이를 위해 컨트롤 포인트 계산 알고리즘을 그대로 구현한다. 변위 매핑을 이용한 기법과는 다르게 컨트롤 쉐이더에 입력받은 패치를 그대로 생성쉐이더에 전달하지 않는다.
반면, 입력으로 주어진 노멀 $\left\{ n_1, n_2, n_3 \right\}$를 그대로 컨트롤 노멀로 취한다.
즉 1차 베지어 삼각형 $n\left(u,v,w\right)$를 사용하겠다는 것이다.
컨트롤 포인트와 컨트롤 노멀은 생성 쉐이더에 입력된다.
컨트롤 쉐이더는 테썰레이션 레벨을 결정해서 테썰레이터에게 알려준다.
테썰레이터는 컨트롤 쉐이더가 지정해주는 대로 삼각형 정의역(domain)을 잘게 나눠 2차원 메시를 구성한다.
위 그림의 우측을 보면, 메시의 정점마다 무게중심 좌표 $\left( u,v,w \right)$가 할당된다(그림에서는 2차원 메시는 정삼각형으로만 구성되어 있지만 실제 구현은 이와 다르다).
2차원 메시의 각 정점에 대해 생성 쉐이더가 한 번 실행된다.
컨트롤 쉐이더가 건네준 컨트롤 포인트와 컨트롤 노멀을 사용해 각각 $p\left(u, v, w \right)$와 $n\left(u, v, w \right)$를 만든 후, 테썰레이터가 건네준 $\left(u, v, w \right)$를 $p\left(u, v, w \right)$와 $n\left(u, v, w\right)$에 대입하여, 3차원 정점의 위치와 노멀을 계산한다.
이런 방식으로 2차원 메시가 모두 처리되면, 위 그림의 하단처럼 부드러운 고해상도 메시가 생성된다.
이는 GPU 파이프라인의 다음 단계로 넘겨진다.
18.2.4 PN-삼각형을 위한 GL 프로그램, 쉐이더, 테썰레이터
PN-삼각형을 위한 정점 쉐이더
#version 320 es
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
out vec3 v_position;
out vec3 v_normal;
void main() {
v_position = position; // object-space position
v_normal = normal;
}
위 정점 쉐이더는 position과 normal을 각각 v_position과 v_normal에 복사한다.
그릐고, 변위 매핑의 경우와 마찬가지로, gl_position 계산은 생성 쉐이더에게 맡긴다.
PN- 삼각형을 위한 컨트롤 쉐이더
#version 320 es
layout(vertices = 1) out;
uniform float tessLevel;
in vec3 v_position[];
in vec3 v_normal[];
struct PNT {
vec3 P300, P030, P003; // corner control points
vec3 P210, P120, P021, P012, P102, P201; // mid-edge control points
vec3 P111; // inner control point
vec3 N1, N2, N3; // normals
};
out patch PNT pnTri;
vec3 displace(vec3 pl, vec3 p2, vec3 n1);
void main(){
pnTri.N1 = v_normal[0];
pnTri.N2 = v_normal[1];
pnTri.N3 = v_normal[2];
pnTri.P300 = v_position[0];
pnTri.P030 = v_position[1];
pnTri.P003 = v_position[2];
pnTri.P210 = displace(pnTri.P300, pnTri.P030, pnTri.N1);
pnTri.P120 = displace(pnTri.P030, pnTri.P300, pnTri.N2);
pnTri.P021 = displace(pnTri.P030, pnTri.P003, pnTri.N2);
pnTri.P012 = displace(pnTri.P003, pnTri.P030, pnTri.N3);
pnTri.P102 = displace(pnTri.P003, pnTri.P300, pnTri.N3);
pnTri.P201 = displace(pnTri.P300, pnTri.P003, pnTri.N1);
vec3 V = (pnTri.P300 + pnTri.P030 + pnTri.P003) / 3.0;
vec3 E = (pnTri.P210 + pnTri.P120 + pnTri.P021
+ pnTri.P012 + pnTri.P102 + pnTri.P201) / 6.0;
pnTri.P111 = E + (E - V) / 2.0;
gl_TessLevelOuter[0] = tessLevel;
gl_TessLevelOuter[1] = tessLevel;
gl_TessLevelOuter[2] = tessLevel;
}
vec3 displace(vecc3 p1, vec3 p2, vec3 n1) {
return (2.0 * p1 + p2) / 3.0 - dot(n1, (p2 - p1) / 3.0) * n1;
}
위 컨트롤 쉐이더에서 vertices가 1로 설정된 것은, 단 하나의 컨트롤 쉐이더만 작동된다는 뜻이다.
즉 컨트롤 포인트 10개와 컨트롤 노멀 3개가 단 하나의 컨트롤 쉐이더에 의해 계산된다는 것이다.
이를 위해 PNT라는 구조체가 정의되었는데, 처음 10개 요소는 컨트롤 포인트이고, 마지막 3개는 컨트롤 노멀이다.
생성 쉐이더에게 건네질 PN-삼각형 pnTri를 정의하고, 메인 함수에서 pnTri를 채운다.
삼각형 정의역을 잘게 나눠 2차원 메시를 구성하기 위하여 gl_TessLevelInner와 gl_TessLevelOuter로 테썰레이션 레벨을 지정한다.
메인 함수의 마지막 네 줄을 보면, gl_TessLevelInner의 한 개 원소가 지정되었고, gl_TessLevelOuter의 세 개의 원소가 지정되었다(삼각형 패치에서는 gl_TessLevelInner[1]과 gl_TessLevelOuter[3]은 지정할 필요 없다). (gl_TessLevelInner[1]은 삼각형 안의 영역이 하나있기 때문에 필요 없고, gl_TessLevelOuter[3]은 삼각형의 변이 0부터 2까지 부여되었기 때문에 필요 없다)
삼각형 정의역에서 이 네 개의 원소들이 담당하는 영역은 위 그림과 같다.
테썰레이터는 먼저 gl_TessLevelInner를 사용해 삼각형 정의역 내부를 분할한 후, gl_TessLevelOuter를 이용해 세 변을 분할한다.
gl_TessLevelInner[0]을 간단히 'inner TL'이라 표기하자.
inner TL이 2로 지정되었다고 하면, 그림 (a)처럼 삼각형 정의역의 세 변은 모두 2등분된다.
각 변의 등분점을 가로지르는 직선은 삼각형 중심에서 만날 것이다.
inner TL이 3이면, 그림 (b)처럼 삼각형 정의역의 모든 변은 3등분된다.
삼각형 한 정점에 인접한 등분점 두 개를 가로지르는 직선 둘은 한 점에서 만난다.
삼각형 내부에 그러한 점은 총 세 개 있다. 이들은 하나의 삼각형을 만든다. 이 삼각형은 원래 주어진 삼각형과 중심을 공유한다.
inner TL이 4이면, 그림 (c)처럼 삼각형 정의역의 모든 변은 4등분된다.
삼각형 한 정점에 인접한 등분점 두 개를 가로지르는 직선 둘은 한 점에서 만나는데, 그러한 점 세 개를 묶어 하나의 작은 삼각형을 만든다.
한편, 각 변에 남아있는 하나의 등분점을 가로지르는 직선은 방금 만든 내부 삼각형에 부딪힌다.
이 경우는 그림(a)와 같으므로, 그때 사용한 분할 방법을 그대로 사용하면, 내부의 삼각형도 분할된다.
그림 (d)는 inner TL이 5인 경우의 내부 분할 과정을 보여준다.
이런 방식으로 내부 분할이 완료된 후, 삼각형 정의역의 경계를 분할한다.
위 그림들에서도 경계를 분할했지만, 이는 내부 분할을 위한 임시적인 것이었으므로 이를 모두 무시한다.
gl_TessLevelOuter[0], gl_TessLevelOuter[1], gl_TessLevelOuter[2]는 각각 삼각형의 왼쪽, 아래쪽, 오른쪽 변을 분할하는데, 이를 outer TL[0], outer TL[1], outer TL[2]로 간단히 표기하자.
위 그림(a)는 inner TL과 outer TL에 의해 분할된 예를 하나 보여준다.
테썰레이터는 이렇게 분할된 정의역을 삼각형들로 채운다.
그림 (b)를 보면, 이 2차원 삼각형 메시의 각 정점에는 무게중심 좌표 $\left(u,v,w\right)$가 할당된다.
PN-삼각형을 위한 생성 쉐이더
#version 320 es
layout(triangles) in;
uniform mat4 worldMat, viewMat, projMat;
struct PNT {
vec3 P300, P030, P003; // corner control points
vec3 P210, P120, P021, P012, P102, P201; // mid-edge control points
vec3 P111; // inner control point
vec3 N1, N2, N3; // normals
};
in patch PNT pnTri;
out vec3 v_normal;
void main() {
// powers of the parameters
float u1 = gl_TessCoord.x, v1 = gl_TessCoord.y, w1 = gl_TessCoord.z;
float u2 = pow(u1, 2.0), v2 = pow(v1, 2.0), w2 = pow(w1, 2.0);
float u3 = pow(u1, 3.0), v3 = pow(v1, 3.0), w3 = pow(w1, 3.0);
// position evaluation
vec3 position = vec3(0.0);
position += pnTri.P300 * u3 + pnTri.P030 * v3 + pnTri.P003 * w3;
position += pnTri.P210 * 3.0 * u2 * v1 + pnTri.P120 * 3.0 * u1 * v2;
position += pnTri.P021 * 3.0 * v2 * w1 + pnTri.P012 * 3.0 * v1 * w2;
position += pnTri.P102 * 3.0 * w2 * u1 + pnTri.P201 * 3.0 * w1 * u2;
position += pnTri.P111 * 6.0 * u1 * v1 * w1;
// clip-space position
gl_Position = projMat * viewMat * worldMat * vec4(positionm 1.0);
// normal evaluation
vec3 normal = pnTri.N1 * u1 + pnTri.N2 * v1 + pnTri.N3 * w1;
// world-space normal
v_normal = normalize(transpose(inverse(mat3(worldMat))) * normal);
}
위 생성 쉐이더를 보면, triangles는 삼각형 패치가 입력됨을 의미한다.
컨트롤 쉐이더가 건네준 PN-삼각형(pnTri)은 in patch PNT pnTri로 정의되었다.
테썰레이터가 만든 2차원 삼각형 메시의 각 정점마다 하나의 생성 쉐이더가 작동되는데, 이로부터 3차원 정점의 위치와 노멀을 계산하기 위하여 생성 쉐이더는 pnTri와 gl_TessCoord를 사용한다(glTessCoord는 무게중심 좌표 $\left(u,v,w \right)$를 저장하고 있는 3차원 벡터이다, 사각형 패치의 경우 첫 두 원소는 $\left(u,v \right)$를 담고 있고 세 번째 원소는 0이다).
컨트롤 포인트로 정의되는 베지어 삼각형의 식 $p\left(u,v,w\right)$를 그대로 구현하고 컨트롤 노멀의 무게 중심 조합을 계산한다.
생성 쉐이더가 출력하는 glPosition은 부드럽고 고해상도 메시 정점의 클립 공간 위치를, v_normal은 같은 정점의 월드 공간 노멀을 담고 있다.
이들은 GPU 파이프라인의 다음 단계로 넘겨진다.
저해상도 폴리곤 메시가 GPU 파이프라인에 입력되었다고 가정하면, 이는 우선 정점 쉐이더에 의해 처리되는데, 정점 쉐이더는 다양한 애니메이션 혹은 물리 시뮬레이션을 메시의 각 정점에 적용할 것이다(스키닝 애니메이션이 정점 쉐이더에 의해 구현되는 것을 생각).
그런데 애니메이션과 물리 시뮬레이션은 많은 시간을 소요하는 복잡한 알고리즘을 구현한 경우가 많다.
따라서 이를 저해상도 메시에 적용하는 것은 효율적인 선택이다.
애니메이션 혹은 물리 시뮬레이션을 거친 물체를 테썰레이션을 통해 부드러운 고해상도 메시로 바꾸면 렌더링 품질이 높아진다.
출처
[OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문]을 보고 공부하고 정리한 내용
'공부 > 컴퓨터 그래픽스' 카테고리의 다른 글
OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문 Chapter 17 매개변수 곡선과 곡면 (0) | 2024.07.19 |
---|---|
OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문 Chapter 16 전역 조명과 텍스처 (0) | 2024.07.19 |
OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문 Chapter 15 쉐도우 매핑 (0) | 2024.07.19 |
OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문 Chapter 14 노멀 매핑 (0) | 2024.07.19 |
OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문 Chapter 13 캐릭터 애니메이션 (0) | 2024.07.19 |