본문 바로가기
게임 공부/Computer graphics

3 - 래스터화(Rasterization)

by woohyeon 2020. 8. 7.
반응형

책을 보며 개인적으로 공부하는 내용이므로 틀린 부분이 있을 수 있습니다.

자료 및 내용 출처
<게임 프로그래밍을 위한 3차원 그래픽스> 

 

래스터화(Rasterization)

https://youtu.be/LAsnQoBUG4Q

 

렌더링 파이프라인의 2단계인 래스터화는 1단계인 정점 처리의 출력을 입력으로 받는다. 정점 처리 단계는 모델링으로부터 얻어진 폴리곤 메쉬의 각 정점들에 여러 변환을 적용하여 클립 공간으로 이동시켰다. 이러한 정점들이 래스터화의 입력으로 들어온다. 래스터화 단계에선 각 정점들을 정점, 선분, 삼각형(폴리곤) 단위로 처리하는데, 보통 삼각형 단위로 처리한다. 그리고 이러한 단위를 프리미티브(primitive)라 한다.

실시간 렌더링에서 대부분 삼각형 메쉬를 사용하는 이유는 다음과 같다.
  • 삼각형은 가장 단순한 다각형이다.
  • 삼각형은 언제나 평평하다.
  • 아핀 변환, 투영 등을 거치더라도 삼각형 모양을 유지한다. 최악의 경우 옆에서 보면 선분이 된다.
  • 거의 대부분의 렌더링 하드웨어 들은 삼각형 래스터화 처리에 특화되어 있다.

이렇게 조립되는 각 삼각형들은 최종적으로 우리가 보는 2차원 평면에 그려지게 될텐데, 우리가 보는 화면은 픽셀(pixel)로 구성이 된다. 따라서 삼각형들을 픽셀로 채워야 하는데, 해당 단계에선 우선 일종의 예비 픽셀인 프래그먼트(fragment)로 채우게 된다. 프래그먼트를 생성할 땐 각 정점에 할당되었던 여러 데이터(노멀, 텍스처 좌표 등)를 이용(보간)하여 생성한다. 이를 포함하여 래스터화 단계에서 수행되는 작업들을 나열하면 다음과 같다.

  • 클리핑(clipping)
  • 원근 나눗셈(perspective division)
  • 뒷면 제거(back-face culling)
  • 뷰포트 변환(view-port transform)
  • 스캔 변환(scan conversion)

래스터화 단계는 고정되어 있어 프로그래밍이 불가능하다. 그러나 몇몇 작업들은 매개변수를 통해 그 설정을 조금씩 변경할 수 있다. 따라서 각 작업들의 동작 원리를 이해하면 훨씬 좋은 프로그램을 개발할 수 있다.



클리핑(Clipping)

뷰 프러스텀 컬링과 함께 살펴보았던 클리핑은 래스터화 단계에서 실행된다. 클리핑 알고리즘은 하드웨어로 구현이 되어 있어 수정이 불가능하다. 클리핑은 이미 뷰 프러스텀 컬링이 실행되고, 클립 공간으로 이동된 물체들을 대상으로 수행이 된다. 따라서 클리핑은 피라미드 모양의 뷰 프러스텀이 아닌 2x2x1의 직육면체 공간에서 수행이 된다. 그러나 여기선 직관적인 이해를 위해 피라미드 모양에서 수행한다.

아래 그림은 카메라 공간의 뷰 프러스텀을 나타낸 것이다.

image

세 개의 삼각형 중 t1과 t3의 일부가 뷰 프러스텀의 바깥 쪽에 놓여져 있다. 우선 t1의 전체가 외부에 있으므로 t1은 버려진다. t2는 완전히 내부에 존재하므로 파이프라인의 다음 단계로 넘어가게 된다. t3는 일부는 내부에 존재하고 나머지는 외부에 존재한다. 이 경우 외부에 존재하는 부분을 자르고 내부에 존재하는 부분만 다음 단계로 보내야 한다. 외부의 부분을 자르게 되면 잘린 부분을 기점으로 새로운 정점이 생기게 된다. 따라서 새 정점을 추가하여 다음 단계로 보내게 된다.

image



원근 나눗셈(Perspective division)

래스터화의 전 단계인 정점 처리 과정에서 모든 정점들은 클립 공간으로 이동되었다. 즉 뷰 프러스텀에서 직육면체 뷰 볼륨으로 이동되었다. 이러한 변환은 다음과 같은 투영 행렬을 통해 이루어졌다.

image

그런데 투영 변환을 수행하는 행렬은 월드 행렬, 뷰행렬과는 다르게 동차 좌표를 위한 마지막 행이 (0,0,0,1)이 아닌 (0,0,-1,0)이다. 따라서 정점과의 곱셈의 결과가 다음과 같이 -z로 나오게 된다.

image

전에도 설명했듯이 이러한 결과가 나오면 같은 수로 나누어서 1을 만들어야 한다 했다. 그래서 위 결과의 각 행을 -z로 나누어 다음과 같이 표현한다.

image

이처럼 -z로 나누는 연산을 원근 나눗셈이라 한다. 그리고 이 연산은 기하학적으로 다음과 같은 의미를 가진다.

image

왼쪽의 그림은 투영 변환이 적용되기 전이고, 오른쪽 그림은 투영 변환 적용 후 원근 나눗셈을 한 결과이다. 그 결과 원점(카메라)에 가까운 l2 선분은 길이가 그대로지만, 카메라로부터 멀리 있는 l1은 길이가 줄은 것을 확인할 수 있다.

만약 원근 나눗셈을 수행하지 않는다면 아래의 결과 처럼 l1(P1Q1)은 길이는 변함이 없다.

image

이를 통해 알 수 있는 것은 -z로 나눠주는 과정, 즉 원근 나눗셈은 물체를 3차원 공간에서 2차원 평면에 표현할 때 멀리 있는 것을 더 작게 나타내어 원근감을 갖도록 해준다. 또한 이러한 과정을 통해 뷰 프러스텀은 범위가 정규화(normalization)된 뷰 볼륨의 특징을 가지는데 이러한 좌표계를 NDC(Normalized device coordinates)라 한다. 원근 나눗셈 또한 클리핑과 마찬가지로 알고리즘이 하드웨어로 고정되어 있어, 우리가 건드릴 수 없다.



뒷면 제거(Back-face culling)

정점 처리 과정에서 우리는 뷰 프러스텀의 외부에 있는 물체들을 제거하는 뷰 프러스텀 컬링을 수행했다. 또한 이전의 클리핑 단계에서도 시야에서 벗어나는 일부분들을 제거했다. 이제 파이프라인엔 시야 내의 물체들만 존재한다. 그러나 여전히 우리 눈에 들어오지 않는 부분들이 존재한다. 바로 물체의 앞면이 아닌 뒷면이다.

다음 그림에서 물체가 불투명하다고 가정할 때, 삼각형 t1은 카메라에게 보이지 않는다. 따라서 이러한 삼각형들을 그리지 않는다면 그만큼 렌더링 속도가 향상될 것이다. 이처럼 물체의 뒷면을 제거하는 작업을 뒷면 제거(back-face culling) 라 한다. 예를 들어 게임 캐릭터를 앞에서 바라 볼 때 뒷모습을 그릴 필요는 없다. (아래 그림은 카메라 공간이지만 이해를 돕기 위한 그림이며, 실제론 클립 공간에서 수행된다.)

image

물체의 앞면과 뒷면을 판단하는 방법은 생각보다 간단하다. 바로 각 삼각형마다 존재하는 노멀(n)을 이용한다. 그리고 카메라와 각 삼각형의 한 정점을 연결하는 벡터(c)를 만든다. 그리고 각각의 c와 n이 이루는 각도를 이용하여 내적을 계산한다.

 

내적은 각 벡터의 길이와 이루는 각도에 대한 cos 값을 곱하여 계산한다. 길이는 항상 양수이므로 결과에 영향을 미치지 않고, cos 값만 영향을 미치게 된다. cos의 값은 예각일 경우 양수이며, 둔각일 경우 음수이다. 그리고 직각일 경우 0이 된다. 이를 이용하여 다음과 같이 앞면과 뒷면을 판별할 수 있다.

image

t1의 경우 c1과 n1이 예각을 이루기 때문에 내적의 결과가 양수가 되어 뒷면으로 판별된다. t2의 경우 c2와 n2가 둔각을 이루기 때문에 내적의 결과가 음수가 되어 앞면으로 판별된다. t3의 경우 c3와 n3가 직각을 이루기 때문에 결과는 0이 되며 면이 아닌 변만 보이게 된다.

이러한 작업은 실제로는 카메라 공간이 아닌 클립 공간에서 수행이 된다. 카메라 공간의 뷰 프러스텀은 다음과 같이 서로 다른 투영선들이(왼쪽) 다음과 같이 카메라로 모이게 된다.(위 그림의 벡터 c가 투영선을 의미)

image

그러나 투영 변환 후의 투영선들은 모두 같은 방향을 가리키며 서로 평행하게 된다. 따라서 각 삼각형마다 c를 구할 필요 없이 하나의 벡터 c로 다른 노멀(n)들과의 각도를 계산할 수 있다.

image

그런데 이처럼 내적을 이용하지 않고 좀 더 쉽게 판단하는 방법이 존재한다. 바로 삼각형의 정점 정렬 순서와 행렬식을 이용하는 것이다.

image

위 카메라 공간의 물체는 투영 변환에 의해 xy 평면에 투영이 될텐데, 그 결과 다음과 같이 삼각형들이 평면에 그려질 것이다. 여기서 주목해야 할 것은 정점 정렬 순서이다. 위 그림의 원점(카메라)의 시점에서 물체를 바라본다고 생각하고 삼각형을 그리면 t2는 아래와 같이 그려질 것이다.

image

반면 t1은 물체가 반투명하다 생각하고 원점의 시점에서 보이는 t1을 그리면 다음과 같이 그려질 것이다.

image

우리가 정점을 카메라 공간에서 클립 공간으로 이동 후 래스터라이저의 입력으로 주기 위해 z축을 반전시켜 오른손 좌표계를 왼손 좌표계로 변경했다. 하지만 정점 순서는 변경하지 않고 그대로 반시계 방향을 유지했다. 따라서 반시계 방향이 정상적인 정렬 순서가 된다. 즉 카메라 시점에서 삼각형을 보았을 때 정점이 반시계 방향일 경우 앞면이며, 시계 방향일 경우 뒷면이 된다.

여기서 벡터 v1v2와 v1v3를 행렬로 만들고 행렬식을 통해 값을 구한다. 그리고 그 결과가 양수라면 정점은 반시계 방향으로 정렬되어 있으며 앞면을 의미한다. 만약 음수라면 시계 방향으로 정렬되어 있으며 뒷면을 의미한다. 만약 0이라면 삼각형의 면이 아닌 변만 보이게 된다.

그런데 뒷면이라고 해서 항상 제거해야하는 것은 아니다. 예를 들어 물체가 반투명한 경우 앞면을 통해 뒷면이 보이는 것이 정상이다. 따라서 이를 위해 Direct3D 또는 OpenGL에선 다양한 옵션을 제공한다. 예를 들면 앞면을 제거할 것인지, 뒷면을 제거할 것인지, 모든 삼각형을 그릴 것인지와 같은 옵션을 제공하여 사용자가 커스텀할 수 있다.

또한 앞면이라고 해서 항상 렌더링되어야 하는 것은 아니다. 예를 들어 해당 물체의 앞면을 다른 물체가 가릴 수 있기 때문이다. 이를 위해 카메라로부터 물체가 얼마나 떨어져 있는지를 판단하기 위해 z(깊이) 값을 이용한다. 각 정점들의 z가 따로 저장된 버퍼를 이용하여 어떤 물체에 의해 가려지는지 등을 판단할 수 있는데, 이를 z-버퍼링이라 하며 출력 병합 단계에서 따로 처리된다.

그리고 z-버퍼링을 이용하여 래스터화의 다음 단계로 넘어가기 전에 물체를 미리 제거하는 작업을 z-컬링(z-culling)이라 한다. 이는 래스터화 단계에서 수행하지만 z-버퍼링을 이해해야 하기 때문에 출력 병합 단계에서 소개한다.



뷰포트 변환(View-port transform)

우리들은 모니터의 스크린을 통해서 물체들을 보게 된다. 즉 물체들은 우리의 화면 상에 그려져야 한다. 그러나 프로그램에 따라 윈도우(창)의 크기는 다를 수 있다. 따라서 스크린 공간에 뷰포트라는 공간이 정의가 되며, 뷰포트는 그림이 그려지는 공간을 의미한다. 스크린과 뷰포트 공간은 3차원이며 모니터의 깊이를 z축이라 생각하면 된다. 깊이는 3차원 공간의 물체가 2D 화면에 그려질 때, 겹치는 물체의 우선순위를 판단하기 위한 수단으로 사용된다.

image

뷰포트는 위와 같이 시작점 (MinX, MinY)너비(W)높이(H), 깊이 범위 [MinZ, MaxZ]로 정의된다. 물체가 항상 모니터라는 스크린 공간의 전체를 차지하는 것은 아니므로, 뷰포트의 시작점인 (MinX, MinY) 좌표는 유동적이다. (위 스크린 공간은 Direct3D 기준이다. OpenGL은 y축이 반전되어 있다.)

현재 우리의 물체들은 다음과 같은 정규화된 클립 공간인 NDC에 존재한다. 이를 위와 같은 뷰포트 공간으로 이동시키기 위해선 반사, 축소확대, 이동 3번의 변환이 필요하다.

image


1. y축 반전

image

Direct3D 기준으로 y축이 아래 방향으로 향하게 한다.


2. 뷰 볼륨의 너비를 W, 높이를 H, 깊이를 MaxZ-MinZ로 축소확대한다.

image

기존의 뷰 볼륨은 너비, 높이, 깊이가 각각 2, 2, 1이었다. 이를 각각 W, H, MaxZ-MinZ로 만들기 위해 알맞은 수를 곱한다.


3. 뷰 볼륨을 스크린 공간 내로 알맞게 이동시킨다.

image

뷰 볼륨(뷰포트) 내의 물체를 뷰 볼륨 중앙으로 위치시킬 경우 위와 같이 이동 변환을 적용한다. 뷰포트의 시작점인 MinX,MinY에서 너비 및 높이의 반만큼 추가로 이동시킨 것이다. (Z축 방향으로의 이동은 잘 이해가 가지 않아 일단 보류해둠.)

 

이러한 세 개의 변환 행렬의 곱을 하나의 행렬로 나타내면 다음과 같다.

image

만약 뷰포트가 스크린의 일부분이 아닌 전체 영역을 차지할 경우 다음과 같이 간단하게 표현된다. MinX와 MinY는 모두 0이 되며, MinZ와 MaxZ는 0과 1이 된다.

image



스캔 변환(Scan conversion)

래스터화의 마지막 단계인 스캔 변환은 스크린 공간의 물체들을 2차원 평면으로 나타내어, 물체를 구성하는 삼각형마다 프래그먼트로 채운다. 즉 모니터에 물체가 2차원 형태로 그려질텐데, 모니터 내의 모든 그림은 픽셀로 구성이 된다. 따라서 각 픽셀마다 위치가 정해져 있으며, 물체를 픽셀로 채워야 한다.

그러나 래스터화에선 프래그먼트라는 예비 픽셀로 먼저 채우게 된다. 프래그먼트는 각 정점이 가진 노멀, 텍스쳐 좌표, 색상 등과 같은 여러 정보를 이용하여 생성하게 된다. 즉 스캔 변환이란 2차원 평면에 그려질 물체들의 내부에 정점의 속성을 이용(보간)하여 프래그먼트를 생성하는 것을 말한다.

아래와 같은 스크린 공간에서 우리는 모니터라는 2D 화면에 그리기 위해 (아직은) xy좌표만 고려한다. z좌표는 다른 단계에서 사용된다.

image

정점은 위와 같이 다양한 속성을 가질 수 있다. 위와 같은 속성을 이용하여 프래그먼트를 생성할 수 있는데, 여기선 직관적이고 쉬운 예를 위해 컬러값 보간을 예로 든다.

우선 아래와 같은 삼각형 내부에 프래그먼트를 생성한다고 생각해보자. 삼각형 내부에 컬러를 입히기 위해선 프래그먼트에 색상을 부여해서 생성해야 할 것이다. 그런데 삼각형 내부의 프래그먼트 하나하나를 위해 컬러값을 하나하나 저장해두는 것은 비효율적이다. 그래서 정점 속성의 컬러값으로 선형 보간을 하여서 정해진 기울기를 따라 자동으로 색상이 정해지도록 한다. 쉽게 말하면 기울기를 통해 규칙을 얻어 미리 컬러값의 증가량을 구하는 것이다. 그래서 정점에서의 하나의 컬러값으로 시작해 정해진 증가량에 따라 프래그먼트마다 컬러를 부여한다.

image

여기서 우리의 목표는 삼각형 내의 각 점마다 R값을 얻는 것이다. 이를 위해 R값을 가지고 있는 두 정점을 이용한다.

image

우선 1.6이라는 y값에서 7.2라는 y값이 되기 까지 R이 얼마나 증가하는지를 구해야 한다. 따라서 y증가량 = 7.2-1.6 = 5.6R의 증가량 = 244 - 188 = 56 이라는 값을 구한다. 그리고 R의 증가량을 y의 증가량으로 나눈다. 그러면 10이 나오는데, 이 의미는 y가 1증가할 때 R은 10 증가한다는 의미이다.

첫 번째 정점의 y좌표는 1.6이고 R값은 188이다. 계산을 쉽게 하기 위해, 즉 증가 간격을 1로 잡기 위해 y가 2.0일 때의 R값을 구한다. y가 1증가할 때 R이 10 증가하므로, 0.4증가할 때 R은 4증가 한다. 따라서 y가 2.0일 때 R은 192이다. 이제부턴 y가 1씩 증가할 때 아래와 같이 R을 10씩 증가시켜주면 된다.

image

이제 삼각형 내의 모든 스캔 라인에 대한 R값을 구하기 위해, 아까와 같은 방식으로 x증가량에 대한 R의 증가량을 구한다. (삼각형 내의 픽셀(프래그먼트가) 생성될 각 라인을 스캔 라인이라 한다.)

image

x증가량 = 6.9-3.4 = 3.5이며, 이때 R의 증가량 = 202-97 = 105 이다. 이를 통해 기울기를 구하면 30이고, xl = 3.4 지점부터 시작하여 R의 값을 구한다. 이 역시 계산을 쉽게 하기 위해 3.4가 아닌 4.0에서의 R값을 구한다. x가 1이 증가할 때 R이 30증가 하므로 x가 0.6증가하면 R은 18증가한다. 따라서 x가 4일 때 R의 값은 97+18 = 115이다. 이후부턴 x가 1증가할 때마다 y를 30씩 증가시켜주면 된다.

이처럼 정점을 이용하여 각 프래그먼트를 생성하는 것을 정점의 속성을 이용한 보간(interpolation)이라 한다. 처음엔 변을 따라 보간하고, 그 다음에는 스캔 라인을 따라 보간하였다. 이러한 두 단계를 거친 보간을 겹선형 보간이라고도 한다. 물론 이 예에선 단순한 이해를 위해 컬러값(R)만 보간하였다. 실제론 노멀, 텍스쳐 좌표, 깊이 등도 보간된다. 최종적인 컬러는 다음 단계에서 변경되거나 결정될 수 있다. 이처럼 래스터화의 마지막 단계인 스캔 변환은 정점별 속성을 보간하여 스캔 라인의 각 픽셀 위치마다 프래그먼트를 생성한다.

 

아래는 위와 같이 컬러가 보간된 삼각형의 예이다.

image

 

 

https://enghqii.tistory.com/59

https://stackoverflow.com/questions/21841598/when-does-the-transition-from-clip-space-to-screen-coordinates-happen




댓글