프리미티브(primitive)
- GPU 파이프라인이 처리할 기하학적인 개체
- 그래픽스 응용에 따라 삼각형, 선(line), 점(point)도 독자적인 프리미티브가 될 수 있는데, 이들은 각각 정점 3개, 2개, 1개를 사용하여 정의된다.
책에서는 삼각형 프리미티브만 다루므로 삼각형 프리미티브로 생성된다고 가정하자.
래스터라이저는 클립 공간의 정점들을 프리미티브로 다시 조립(assemble)한다. 이 프리미티브는 스크린에 그려질 형태로 변환된 후 프래그먼트로 분해되는 래스터화(rasterization)가 진행된다.
프래그먼트(fragment)
- 컬러 버퍼의 픽셀을 갱신하는 데 필요한 데이터를 총칭하는 것
- 정점들에 저장된 노멀과 텍스처 좌표 등은 프리미티브를 따라 보간(interpolate)되어 각 프래그먼트에 할당된다.
래스터라이저에서 수행하는 일
- 클리핑(clipping)
- 원근 나눗셈(perspective division)
- 뒷면 제거(back-face culling)
- 뷰포트 변환(viewport transform)
- 스캔 전환(scan conversion
7.1 클리핑
클리핑은 2 x 2 x 2 크기의 클립 공간 뷰 볼륨 바깥에 놓인 폴리곤을 잘라내는 작업이다.
클리핑은 클립 공간에서 수행되지만, 직관적인 이해를 위해 책에서는 카메라 공간을 사용해서 설명한다.
위 사진은 '카메라 공간 뷰 프러스텀' 이다. 삼각형과 뷰 프러스텀 사이의 공간적인 관계는 세 가지로 나눌 수 있다.
- 삼각형 $t_1$은 뷰 프러스텀 바깥에 위치하므로 제거
- 삼각형 $t_2$는 뷰 프러스텀 안에 위치하므로 그대로
- 삼각형 $t_3$는 뷰 프러스텀과 교차하므로 뷰 프러스텀 바깥에 놓인 부분을 잘라내는 작업 수행, 기존 정점 일부가 제거되고 새로운 정점을 추가해서 새로운 프리미티브를 만듦
2, 3번의 경우, 프리미티브들은 래스터라이저의 다음 단계로 넘어간다.
7.2 원근 나눗셈
$M_{proj}$의 첫 3개 행 중에서 0이 아닌 원소들을 $m_{11}$, $m_{22}$, $m_{33}$, $m_{34}$로 표기했다.
아핀 변환과는 달리 $M_{proj}$의 마지막 행은 $\left(0 \, 0 \, 0 \, 1 \right)$이 아니라 $\left(0 \, 0 \, -1 \, 0 \right)$이다.
따라서, $M_{proj}$에 의해 변환된 정점의 $w$좌표는 $-z$가 된다. 이는 대개의 경우 1이 아니다.
투영 변환을 통해 얻어진 동차 좌표를 카테시안 좌표를 전환하기 위해서는 동차 좌표의 모든 원소를 $w$로 나눠야 한다.
$M_{proj}$를 두 선분 $l_1$과 $l_2$에 적용한 결과를 비교해 보자.
나눗셈에 적용된 $w$좌표는 $-z$로, 이는 카메라 공간의 $xy$평면으로부터 해당 정점까지의 수직 거리를 나타내는 양수 값이다.
선분 $l_1$과 끝점 $P_1$과 $Q_1$의 $-z$는 2이고, $l_2$의 끝점 $P_2$와 $Q_2$의 $-z$는 1이다.
카메라로부터 멀리 떨어져 있는 정점은 투영 변환후 w좌표가 크므로, w로 나누는 연산은 멀리 떨어진 물체를 작게 만든다.
카메라 공간에서 $l_1$과 $l_2$는 동일한 길이를 가지지만, $M_{proj}$를 적용하고 w로 나눈 결과, $l_{1}'$는 $l_{2}'$보다 작아졌다. 이는 바로 원근법 구현에 해당한다. 따라서, $w$로 나누는 연산을 원근 나눗셈(perspective division)이라 부른다.
정규화된 장치 좌표(normalized device coordinates, NDC)
- 원근 나눗셈 결과로 얻은 카테시안 좌표
- 정규화라는 이름을 붙인 이유는 이 좌표의 $x$, $y$, $z$ 범위가 모두 [-1, 1]이기 때문
7.3 뒷면 제거
뒷면(back face)
- 카메라를 등지고 있는 폴리곤
- 카메라에 보이지 않으므로 제거
앞면(front face)
- 카메라를 향하는 폴리곤
- 보존되어 래스터라이저의 다음 단계로 넘어감
7.3.1 뒷면 제거 - 개념
카메라(EYE)가 삼각형 노멀이 가리키는 방향의 반대쪽에 존재한다면 이 삼각형은 뒷면
$t_1$은 뒷면, $t_2$는 앞면이다. 이를 구분하기 위해 삼각형의 정점과 카메라를 연결하는 벡터가 필요하다.
이를 $c_i$라 하고, 삼각형 노멀 $n_i$와 $c_i$의 내적 $n_i \cdot c_i$를 계산한다.
$n_i$와 $c_i$ 사이 각도를 $\theta$라 할 때, $n_i \cdot c_i$는 $\left\| n_i \right\| \left\| c_i \right\| \cos{\theta}$이다.
$t_1$처럼 $n_1$와 $c_1$이 둔각을 이루면 내적은 음수가 되고, 해당 삼각형은 뒷면으로 판정
$t_2$처럼 $n_2$와 $c_2$이 예각을 이루면 내적은 양수가 되고, 해당 삼각형은 앞면으로 판정
$t_3$에서는 $n_3$와 $c_3$가 수직이므로 내적은 0이 되는데, 이 경우 해당 삼각형은 변만 보이는 것으로 판정
7.3.2 뒷면 제거 - 구현
투영 변환 이후에는 모든 $c_i$가 $z$축과 나란해 진다. 위의 첫 번째 사진은 단일한 투영선을 보여준다.
두 번째 사진과 같이 투영 변환이 적용된 구가 주어졌을 때, 이 구의 삼각형들을 단일한 투영선을 따라 $xy$평면으로 투영하면 각 삼각형 정점의 $z$좌표를 버리고 $xy$좌표만 취하는 것과 같다.
삼각형 $t_1$을 투영한 결과는 세 번째 그림의 우측과 같다. 정점이 시계 방향(clockwise, CW)으로 정렬되어 있다.
그런데 3차원 공간에서는 $t_1$을 포함한 모든 삼각형의 정점이 반시계 방향(counter-clockwise, CCW)으로 정렬되었었다.
왜냐하면 3차원 구의 $t_1$이 반시계 방향으로 정렬된 정점을 가졌다는 것은 그 구를 바깥에서 바라보았을 때의 성질이기 때문이다. 구의 안에서 $t_1$을 관찰하면 시계방향으로 보이게 된다.
반면에 세 번째 그림의 좌측은 반시계 방향으로 정렬되어 있다.
즉, 2차원으로 투영된 삼각형의 정점이 시계 방향으로 정렬되면 뒷면으로 판정하고, 반시계 방향으로 정렬되면 앞면으로 판정할 수 있다는 것이다.
2차원으로 투영된 삼각형 $<v1, v2,v3>$를 생각하면, 각 정점 $v_i$는 $\left(x_i, y_i \right)$ 좌표를 가진다.
이 삼각형의 정점이 시계 방향 혹은 반시계 방향으로 정렬되어 있는지 판정하는 방법은 행렬식을 이용한다.
$v_1$과 $v_2$를 잇는 벡터 $\left(x_2-x_1, y_2-y_1\right)$, 그리고 $v_1$과 $v_3$를 잇는 벡터 $\left(x_3-x_1, y_3-y_1\right)$을 계산한 후 다음과 같은 행렬식(determinant)을 계산한다.
위 행렬식이 음수라면 시계 방향 즉 뒷면, 양수라면 반시계 방향 즉 앞면이 된다. 만약 0이라면 변만 보이는 삼각형을 의미
항상 뒷면을 제거해야하는 것은 아니다. 응용에 따라 다르다.
반투명 물체를 렌더링 하는 경우, 앞면을 통해 뒷면을 볼 수 있어야 하므로 뒷면을 제거하면 안 된다.
하나의 구가 주어졌을 때 뒷면 대신 앞면을 모두 제거한다면, 그 구를 반으로 잘라서 내부를 보는 효과를 얻을 수 있다.
뒷면 제거 알고리즘은 래스터라이저 내부에서 하드웨어로 구현되어 있다.
하지만, 클래핑이나 원근 나눗셈과 달리 뒷면 제거는 선택적인 조절이 가능하다.
GL의 glEnable 함수를 사용하여 GPU 파이프라인의 다양한 기능을 활성화할 수 있는데, 삼각형 제거를 위해서는 우선 glEnable(GL_CULL_FACE)를 호출해야 한다.
그 다음 glCullFace를 호출하여 뒷면과 앞면 중 어느 것을 제거할 것인지 정해야 한다. 기본값은 GL_BACK이다.
즉, glCullFace를 호출하지 않으면 뒷면이 제거되고, 앞면을 제거하고 싶으면 glCullFace(GL_FRONT)를 호출해야 한다.
마지막으로, glFrontFace를 호출하여 앞면의 정점 정렬 순서를 명시해야 한다.
이 함수의 기본값은 GL_CCW, 즉 반시계 방향이다.
일반적으로 불투명한 물체의 뒷면을 제거하는 이유는 그것이 보이지 않기 때문이다.
하지만, 앞면이라고 해서 반드시 보이는 것은 아니다. 또 다른 앞면에 의해 가려질 수 있기 때문이다.
이러한 앞면들을 z-버퍼링(z-buffering)이라고 하는 프래그먼트별 연산에 의해 처리된다.
7.4 뷰포트 변환
윈도우 공간(window space) 또는 스크린 공간(screen space)
- 컴퓨터 스크린 위의 윈도우가 가지는 좌표계
- 윈도우의 왼쪽 아래 모퉁이에 원점을 가진다.
뷰포트(viewport)
- NDC 정육면체 뷰 볼륨 안의 내용이 최종적으로 그려질 영역
- 때로 윈도우 전체를 차지하지만, 반드시 그럴 필요는 없다.
- 윈도우에서 뷰포트의 범위는 glViewport에 의해 정의
- glViewport의 첫 두 파라미터 minX와 minY는 뷰포트 왼쪽 아래 모퉁이의 스크린 공간 좌표, 마지막 두 파라미터 w와 h는 각각 뷰포트 너비(width), 높이(height)를 지정
- 종횡비(aspect ratio)는 $\frac{w}{h}$가 되는데, 뷰 프러스텀 파라미터 aspect와 동일하게 설정하는 것이 좋다.
실제 스크린 공간은 3차원이다. 3차원 스크린 공간의 z축은 스크린 안쪽을 향한다. 3차원 뷰포트 역시 3차원인데, 이는 glDepthRange(minZ, maxZ)를 호출하여 2차원 뷰포트에 z범위 [minZ, maxZ]를 더하여 정의된다. z 범위의 기본값은 [0, 1]
NDC로 표현된 2 x 2 x 2 크기의 뷰 볼륨은 glViewport와 glDepthRange로 정의된 뷰포트로 변환되어야 한다.
이 변환은 위의 그림과 같이, 축소확대와 이동의 결합이다. 결합된 행렬은 다음과 같다.
이를 뷰포트 변환(viewport transform)이라 부르는데, 이는 2 x 2 x 2 크기의 뷰 볼륨 안에 있는 모든 정점에 적용된다.
많은 응용에서 뷰포트는 윈도우 전체 영역을 차지한다. 이 경우, glViewport의 두 파라미터 minX와 minY는 모두 0이 된다.
또한, 뷰포트의 z범위 [minZ, maxZ]가 기본값 [0, 1]을 가진다면, 위의 행렬에서 우측 행렬로 간단히 바꿀 수 있다.
7.5 스캔 전환
뷰포트 변환은 모든 삼각형들을 스크린 공간으로 옮긴다. 그 다음 래스터라이저의 마지막 세부 단계인 스캔 전환(scan conversion)이 수행된다. 이는 삼각형 내부를 채우는 프래그먼트를 생성한다. 즉, 개별 삼각형이 차지하는 스크린 공간의 픽셀 위치를 결정하고, 삼각형의 정점별 애트리뷰트를 보간하여 이를 각 픽셀 위치에 할당한다.
왼쪽 그림에서 뷰포트 내부의 삼각형을 보면, 스캔 전환을 위해서는 삼각형 정점의 xy좌표만 사용하므로, 책에서는 중간 그림과 같은 2차원 뷰포트를 사용해 스캔 전환 알고리즘을 설명한다. 이를 확대해 본 것이 오른쪽 그림의 삼각형인데, 이 내부에는 18개의 픽셀이 있다. 이들 각각의 픽셀 위치에 정점별 애트리뷰트가 보간되어 할당될 것이다.
스캔 전환은 실시간 그래픽스의 핵심 사항이고, 정확히 이해해야 GPU 파이프라인의 다음 단계인 프래그먼트 쉐이더를 올바르게 작성할 수 있다.
정점별 애트리뷰트는 노멀과 텍스처 좌표를 기본으로 가지고 있지만 대체로 색상은 포함하지 않는다.
책에서는 노멀과 텍스처 좌표를 보간하는 것은 까다롭기 때문에 간단한 논의를 위해 모든 정점에 RGB 색상 값이 할당되었다는 가정 하에 그 중 R이 어떻게 보간되는지 설명한다.
정점별 애트리뷰트는 삼각형의 변을 따라 선형보간(linear interpolation)된다.
오른족 그림에서 각 변마다 몇 가지 기울기를 계산하는 것이 필요하다.
수직 거리 y의 변화에 따른 R의 변화율을 나타내는 $\frac{\triangle R}{\triangle y}$이 필요하다. 또한, $\frac{\triangle x}{\triangle y}$도 필요하다.
수평 방향으로 이어진 스크린 픽셀들을 스캔라인(scan line)이라 부른다.
스캔 라인에 처음 위치에서는 $\frac{\triangle R}{\triangle y}$과 $\frac{\triangle x}{\triangle y}$를 사용해 어떻게 이 교차점에서의 $R$과 $x$좌표가 계산되는지 파악한다.
이러한 초기화 단계를 거친 후, 그 다음 스캔라인부터는 $\frac{\triangle R}{\triangle y}$과 $\frac{\triangle x}{\triangle y}$를 더하는 작업을 반복하여 왼쪽 변을 따라 R과 x 좌표를 구할 수 있다.
삼각형의 다른 두 변에 대해서도 같은 작업이 이루어진다.
그러면 각 스캔라인에 대하여 양쪽 끝 교차점에서 $R$과 $x$좌표를 가지게 된다.
스캔라인을 따라 이 애트리뷰트들을 삼각형 변을 따른 보간과 동일한 기법을 사용한다.
$\triangle x$와 $\triangle R$을 사용하여 $\frac{\triangle R}{\triangle x}$을 계산한다.
그리고 각 스캔라인 상의 첫 픽셀에 대하여 R을 초기화한다.
그 다음에는 오른쪽으로 한 칸씩 가면서 $\frac{\triangle R}{\triangle x}$을 더해서 픽셀 위치마다 R을 계산한다.
겹선형보간(bilinear interpolation)
- 스캔 전환 단계에서 선형보간은 두 단계로, 즉 먼저 변을 따라, 그 다음에는 스캔라인을 따라 수행
G와 B 역시 같은 방법으로 보간된다.
정점 쉐이더는 각 정점마다 v_normal과 v_texCoord를 출력했다.
이들은 스캔 전환 단계에서 프래그먼트마다 보간된다.
위 그림은 v_normal이 한 삼각형의 위 두변을 따라, 그 다음에는 y좌표가 4.5인 스캔라인을 따라 보간된 결과를 보여준다.
GPU 파이프라인에서 래스터라이저 다음 단계인 프랙그먼트 쉐이더는 이렇게 보간된 v_normal과 v_texCoord를 이용하여 한 번에 하나씩 프래그먼트를 처리한다.
참고
Top-left 규칙
두 삼각형이 공유하는 변 위에 픽셀이 존재한다면 두 삼각형 중 어느 것이 픽셀을 소유하는지 결정해야 한다.
그렇지 않으면 픽셀은 두 번 처리될 것이다.
삼각형은 오른쪽, 왼족, 위쪽, 아래쪽 변으로 구분될 수 있다.
위 그림에서 삼각형 $t_1$은 두 개의 왼쪽 변과 한 개의 오른쪽 변을 가진다.
$t_2$는 아래쪽 변을 가지고, $t_3$은 위쪽 변을 가진다.
GL은 top-left 규칙을 채택하는데, 이는 한 픽셀이 어떤 삼각형의 위쪽(top) 또는 왼쪽(left) 변에 놓이게 되면 이 픽셀은 그 삼각형 소유라고 판정한다.
원근 보정
투영 변환은 선분을 선분으로 매핑한다. 즉, 한 선분에 놓인 점들은 투영 변환 후에도 한 선분에 놓이게 된다.
하지만, 투영 변환은 선분의 거리 비율을 보존하지는 않는다(반면 아핀 변환은 이를 보존한다).
위 그림에서 보듯이 카메라 공간에서의 점 간의 거리 비율이 투영 변환 후에 거리 비율이 변한 것을 볼 수 있다.
추가로 NDC로 표현한 클립 공간에서도 비율이 맞지 않을 것이다.
이러한 클립 공간에서의 거리 비율은 스크린 공간으로 그대로 이어질 것이다. 뷰포트 변환은 거리 비율을 보존하는 아핀 변환이기 때문이다.
스캔전환처럼 색상을 이용해 생각하면, 왜곡되어 색상이 이상하게 보일 것이다. 이는 카메라 공간에서의 거리 비율 대신 스크린 공간에서의 왜곡된 거리 비율을 사용해서 선형보간을 수행했기 때문이다.
왜곡된 거리 비율을 사용할 경우 특히 텍스처링 결과가 눈에 거슬리게 나타날 수 있다. 따라서 이번 Chapter에서 나온 겹선형보간 알고리즘은 카메라 공간에서의 거리 비율을 사용하는 방식으로 수정되어야 한다. 이를 원근보정(perspective correction)이라고 부르는데, 이는 GPU에서 자동으로 수행된다.
출처
[OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문]을 보고 공부하고 정리한 내용
'공부 > 컴퓨터 그래픽스' 카테고리의 다른 글
OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문 Chapter 09 라이팅 (0) | 2024.07.12 |
---|---|
OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문 Chapter 08 이미지 텍스처링 (0) | 2024.07.07 |
OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문 Chapter 06 OpenGL ES와 쉐이더 (0) | 2024.07.07 |
OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문 Chapter 05 정점 처리 (0) | 2024.07.05 |
OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문 Chapter 04 좌표계와 변환 (0) | 2024.06.30 |