2023. 12. 7. 18:35ㆍComputer Sciences/Game Mathemathics
*인프런 <게임 엔진을 지탱하는 게임수학, 이득우 교수님> 강의를 듣고 공부한 글입니다.
1. 깊이 버퍼(Depth Buffer)
`깊이 버퍼(Depth Buffer)`는 카메라로부터 얼마나 깊은 곳에 있는지는 파악할 수 있는 데이터라고 할 수 있습니다. 3차원 공간에서 다수의 물체들을 그릴 때, 그리는 순서를 고려하지 않으면 어떤 물체가 앞에 있고 뒤에 있는지 구분할 수가 없습니다. 앞에 있어야 할 물체(1)가 먼저 그려지고, 뒤에 있어야 할 물체(2)가 나중에 그려지면, 사실상 우리 눈으로 봤을 때는 (1) 물체가 (2) 뒤에 있는 것처럼 보이기 때문이죠.
이러한 문제가 발생하는 이유는 각 물체마다 카메라로부터 얼만큼 뒤에 있는지에 대한 기준치가 없기 때문입니다. 이러한 기준치를 정하기 위해 깊이 버퍼라는 것을 도입했으며, 이 깊이 버퍼 값에 따라 뒤에 있는 물체가 앞에 있는 물체를 덮어 쓰지 않게 픽셀 별로 계산해 적용함으로써 위 문제를 해결할 수 있습니다.
이러한 깊이 버퍼를 구현할 수 있는 대표적인 방법으로 `Z-Buffer` 알고리즘이 있습니다. 이 글에서는 깊이 버퍼 구현 내용에 대해선 다루지 않고, 깊이 버퍼 값을 어떻게 계산하는 지에 대해서만 알아보도록 하겠습니다.
1) 절두체(Frustum)
카메라의 `화각(Field of View)`이 지정되면, 카메라에서 화각으로 무한대로 펼쳐질 겁니다. `사영 공간(Projective Space)`에서는 \(z\) 값이 \(x, \ y\) 값에 영향을 미치기 때문에, \(z\) 값이 커짐에 따라 \(x, \ y\) 값도 늘어나기 때문이죠.
그런데 이렇게 무한대의 영역에서 깊이 값을 산출하기 보다는, NDC 좌표계처럼 일정한 범위를 두는 겁니다. 무한대로 펼쳐진 사영 공간에서 시작점과 끝 점을 주고, 이 영역만 딱 잘라서 이 공간에 속한 내용들만 그리는 겁니다. 이렇게 시작점과 끝 점을 자르게 되면 피라미드 꼭대기 부분을 가로로 자른 듯한 도형이 나오게 되는데, 이를 `절두체(Frustum)`라고 부르죠.
이 절두체의 시작점을 `근 평면(Near Plane)`, 끝 점을 `원 평면(Far Plane)` 이라고 부릅니다. 일반적으로 근 평면의 깊이 값은 -1, 원 평면의 깊이 값은 1을 주기에, 이 절두체 내에 속한 물체들의 깊이 값은 \(-1 \sim \ 1\) 의 범위를 가지게 됩니다.
이러한 절두체의 근 평면과 원 평면 개념을 기반으로 NDC 좌표계를 확장하면 다음과 같은 모양이 됩니다.
그런데 보통 깊이 값을 계산할 때, 이미지로 나타내는 형태가 많습니다. 이미지라는 것은 결국에는 색상 정보로 이루어져 있고, 색상은 일반적으로 마이너스(-) 값이라는 게 존재하지 않습니다. 이미지에서 0은 주로 검은색, 1은 흰색으로 사용됩니다. 이러한 점에서 비롯하여, DirectX는 0 ~ 1까지의 색상 표현에 유용하게 사용하기 위해, 근 평면 값을 -1이 아닌 0부터 시작합니다.
2) 공간의 좌표계
오른손 좌표계를 사용하는 뷰 공간의 경우, 카메라 앞에 있는 물체들의 \(z\) 축 값은 항상 마이너스(-) 값이며, 카메라로부터 멀어질수록 \(z\) 축 값이 작아집니다. 하지만, 위에서 구성한 3차원 NDC 공간의 경우에는 물체가 카메라보다 앞에 있을 때 양수의 \(z\) 값을 가지게 됩니다.
이 이야기는 NDC 공간에서는 왼손 좌표계를 사용한다는 의미로 받아들일 수 있습니다.
이 때까지 사용했던 공간에 대한 좌표계 복습 시간
- 로컬 공간(Local Space) : 모델링 프로그램에 따라 초기 설정이 되고, FBX로 변환되어 게임 엔진에 들어오면 게임 엔진이 지정한 좌표계로 변경된다.
- 월드 공간(World Space) : 게임 엔진이 지정한 좌표계 사용
- 뷰 공간(View Space) : 대부분의 게임 엔진들은 오른손 좌표계를 사용
- 클립 공간(Clip Space) : 왼손 좌표계 사용
👉🏻 원근 투영 행렬을 설계할 때 \(z\) 값에다가 -1을 곱했기 때문 - NDC 공간(Normalized Device Coordinate Space) : 왼손 좌표계 사용
2. 최종 원근 투영 행렬
기존에 사용했던 행렬은 \(4 \times 4\) 크기를 맞춰주기 위해, 사용하지 않는 한 행(row)을 사용하여 원근 투영 행렬을 구성하였습니다.
$$ P=\left[\begin{matrix}\frac{d}{a}&0&0&0\\0&d&0&0\\0&0&-1&0\\0&0&0&1\\\end{matrix}\right] $$
이제 깊이 값을 계산하기 위해, \(z\) 축 행에 해당하는 3번째 행에다가 미지수(\(i, \ j, \ k, \ l\))를 넣어보도록 하겠습니다.
$$ P\cdot\ v_{view}=\left[\begin{matrix}\frac{d}{a}&0&0&0\\0&d&0&0\\i&j&k&l\\0&0&-1&0\\\end{matrix}\right]\left[\begin{matrix}v_x\\v_y\\v_z\\1\\\end{matrix}\right]=\left[\begin{matrix}\frac{d}{a}\cdot v_x\\d\cdot v_y\\?\\-v_z\\\end{matrix}\right] $$
그런데 우리가 계산하는 것은 깊이 값이죠. 깊이 값은 \(z\) 축에 해당하며, 이는 \(x, \ y\) 축에 직교하기 때문에 \(x, \ y\) 값들은 깊이 값에 전혀 영향을 주지 않습니다. 따라서, \(x, \ y\) 값들은 0으로 처리할 수 있죠.
$$ P\cdot\ v_{view}=\left[\begin{matrix}\frac{d}{a}&0&0&0\\0&d&0&0\\0&0&k&l\\0&0&-1&0\\\end{matrix}\right]\left[\begin{matrix}v_x\\v_y\\v_z\\1\\\end{matrix}\right]=\left[\begin{matrix}\frac{d}{a}\cdot v_x\\d\cdot v_y\\?\\-v_z\\\end{matrix}\right] $$
이제 \(k, \ l\) 값을 구해야 하는데, 이것은 근 평면과 원 평면의 특징을 사용하여 구할 수 있습니다. \(k, \ l\) 을 구할 수 있게, 근 평면과 원 평면에서 샘플 데이터를 얻어주는 것이죠. 근 평면과 원 평면의 값들은 사용자가 지정해주는 값이기 때문에 상수라고 볼 수 있고, 근 평면에 대한 값을 \(n\), 원 평면에 대한 값을 \(f\) 로 줬다면 다음과 같이 표현할 수 있습니다.
NDC 좌표계에서는 \(-1 \sim 1\) 의 범위를 가지기 때문에, \( (0, \ 0, \ -n) \rightarrow (0, \ 0, \ -1) \) 이 되고, \( (0, \ 0, \ -f) \rightarrow (0, \ 0, \ 1) \) 이 됩니다. 이러한 기본 데이터를 활용해, \(k, \ l\) 을 구해봅시다.
$$ P=\left[\begin{matrix}\frac{d}{a}&0&0&0\\0&d&0&0\\0&0&k&l\\0&0&-1&0\\\end{matrix}\right]\left[\begin{matrix}0\\0\\-n\\1\\\end{matrix}\right]=\left[\begin{matrix}0\\0\\?\\n\\\end{matrix}\right] $$
$$ P=\left[\begin{matrix}\frac{d}{a}&0&0&0\\0&d&0&0\\0&0&k&l\\0&0&-1&0\\\end{matrix}\right]\left[\begin{matrix}0\\0\\-f\\1\\\end{matrix}\right]=\left[\begin{matrix}0\\0\\?\\f\\\end{matrix}\right] $$
위 행렬을 곱해져서 나온 결과의 네 번째 요소는 행렬 곱을 통해 얻은 결과이니, 각각 \(n, \ f\) 인 것은 확실합니다. 그렇다면, \( ? \) 표시된 부분을 구해야 하는데, 여기서 생각해 볼 것은 위 행렬을 곱해서 나온 결과는 클립 공간의 좌표라는 겁니다.
위에서 얻었던 근 평면과 원 평면의 샘플 데이터의 NDC 공간 \(z\) 좌표는 각각 \(-1, \ 1\) 이었습니다. 클립 공간에서 NDC 공간으로 변환할 때, 마지막 차원의 값으로 나눠주었던 걸 기억하시나요? 근 평면에서는 마지막 차원의 값인 \(n\) 으로 나누었을 때, \(z = -1\) 이 되어야 하니, 세 번째 요소 \(? = -n\) 이 되어야 합니다.
원 평면에서도 마찬가지로, 마지막 차원의 값인 \(f\) 로 나누었을 때, \(z = 1\) 이 되어야 하니, 세 번째 요소 \( ? = f\) 가 되어야 하죠. 이에 따라, 근 평면과 원 평면의 각각 클립 좌표는 다음과 같습니다.
$$ (0, \ 0, -n, \ n) $$
$$ (0, \ 0, \ f, \ f) $$
이제 이를 기반으로 원근 투영 행렬을 구성하는 두 개의 미지수 \(k, \ l\)을 구할 수 있는 식을 만들 수 있게 됩니다.
$$ P=\left[\begin{matrix}\frac{d}{a}&0&0&0\\0&d&0&0\\0&0&k&l\\0&0&-1&0\\\end{matrix}\right]\left[\begin{matrix}0\\0\\-n\\1\\\end{matrix}\right]=\left[\begin{matrix}0\\0\\-n\\n\\\end{matrix}\right] $$
$$ P=\left[\begin{matrix}\frac{d}{a}&0&0&0\\0&d&0&0\\0&0&k&l\\0&0&-1&0\\\end{matrix}\right]\left[\begin{matrix}0\\0\\-f\\1\\\end{matrix}\right]=\left[\begin{matrix}0\\0\\f\\f\\\end{matrix}\right] $$
이로부터 다음과 같은 연립 방정식을 구할 수 있습니다.
$$ \begin{cases} -kn + l = -n \\ -kf + l = f \end{cases} $$
각각을 풀면, 다음과 같이 \(k, \ l\) 값을 계산할 수 있습니다.
$$ k=\frac{-(n+f)}{(-n+f)}=\frac{n+f}{n-f} $$
$$ l=\frac{2nf}{(n-f)} $$
이를 행렬에 적용하면, 이제 깊이 값이 적용된 다음과 같은 원근 투영 행렬을 얻을 수 있습니다.
$$ P=\left[\begin{matrix}\frac{d}{a}&0&0&0\\0&d&0&0\\0&0&\frac{n+f}{n-f}&\frac{2nf}{n-f}\\0&0&-1&0\\\end{matrix}\right] $$
'Computer Sciences > Game Mathemathics' 카테고리의 다른 글
[게임 수학] #20 | 원근 투영(Perspective Projection) (2) | 2023.12.06 |
---|---|
[게임 수학] #19 | 벡터의 외적(Cross Product) (2) | 2023.12.05 |
[게임 수학] #18 | 오일러 각(Euler angle) (2) | 2023.12.04 |
[게임 수학] # 17 | 3차원 공간 (4) | 2023.11.30 |
[게임 수학] #16 | 뷰 공간(View Space) (2) | 2023.11.29 |