설명의 시작은 오른손 좌표계(OpenGL)를 기준으로 시작한다.
투영(Projection)을 하기전에 모든 좌표값은 카메라 공간으로 이동된 상태가 되며
이번에는 원근 투영을 정리할 것이므로 절두체를 이동하여 투영을 계산하게 된다.
이때 투명을 하게 되면 절두체를 정방형공간으로 바꾸는 과정이 이루어 진다.
첫번째로 일단 z의 범위는 생각 하지 않고 x,y를 먼저 투영평면에 투영을 한다.
변환하게 될 정방형 공간의 범위에 따라서 투명평면의 x,y 범위는 [-1, -1] ~ [1, 1]이 된다.
먼저 y를 투명해 보기로 한다.
절두체를 yz폄명으로 잘라서 보자.
투영평면의 y범위에 따라서 y_p는 1값을 가지게 된다.
그리고 닮은꼴 삼각형에 따라서
y / -z = y_p / d가 성립하게 되고 y_p = dy /-z가 되어 y_p를 구할 수 있게 된다.
하지만 여기서 원점에서 투명 평면까지 거리인 d를 구해야 한다.
d는 간단한 삼각형 공식을 이용해 구해진다.
tan (Ɵ / 2) = 1 / d
d = 1 / tan (Ɵ / 2), d = cot (Ɵ / 2)
두번째로 x값도 y값을 구하는 과정과 동일하게 구할 수 있지만
투명평면은 정사각형이 아닌 직사각형 형태이다.
일단 첫번째와 동일 과정을 거치면
x / -z = a x_p / d가 되고 여기서 a값은 직사각형 형태를 만들어 주기 위한 종횡비값을 나타낸다.
a = width / height이고 정리를 하면
x_p = dx / -za 가 된다.
이제 변환된 새로운 좌표를 동차표현식으로 나타내면 다음과 같아 진다.
x_new = dx / a
y_new = dy
z_new = dz
w = -z
그리고 w로 각 요소를 나누면 투영 변환 후의 좌표를 확인할 수 있다.
x_w = dx / -za
y_w = dy / -z
z_w = -d
w_w = 1
이제 w로 나누기전의 식들을 행렬로 나타내면 아래와 같다.
| d/a 0 0 0 |
| 0 d 0 0 |
| 0 0 d 0 |
| 0 0 -1 0 |
이 행렬에 변환되지 않은 x, y,z를 곱하면 변환된 x, y, z를 확인할 수 있다.
| d/a 0 0 0 | | x | = | dx / a |
| 0 d 0 0 | | y | = | dy |
| 0 0 d 0 | | z | = | dz |
| 0 0 -1 0 | | 1 | = | -z |
현재까지 z값을 고려하지 않고 행렬을 만들었다.
이제 z값을 [near, far] 범위로 변환해야 한다.
near = 원점 부터 near 평면까지의 거리
far = 원점부터 far 평면까지의 거리
z범위 변환을 위해 위에서 구해진 행렬에 추가 과정를 더 거친다.
1. [ -n, -f]의 범위를 정방형 범위인 [ -1, 1 ]로 Scaling
2. [ -1, 1 ]로 이동
(오른쪽 좌표계 기준이므로 z 방향은 화면 바깥 방향을 가리키고 n, f는
크기만을 나타내므로 - 부호가 붙어 지게 된다.)
참고로 4x4 Affine 행렬에서
Scale 인자는 행렬의 대각선분에
Translation 인자는 마지막 열에 나타내게 된다.
| S_x 0 0 T_x |
| 0 S_y 0 T_y |
| 0 0 S_z T_z |
| 0 0 0 1 |
이전에 구한 행렬에서 z값에 2가지 과정를 반영하면
| d/a 0 0 0 | => | d/a 0 0 0 |
| 0 d 0 0 | | 0 d 0 0 |
| 0 0 d 0 | | 0 0 A B |
| 0 0 -1 0 | | 0 0 -1 0 |
이제부터는 변환 과정이 반영된 A, B를 구해본다.
먼저 near 폄면에 대해서 계산해 보자.
| d/a 0 0 0 | | 0 | = | 0 |
| 0 d 0 0 | | 0 | = | 0 |
| 0 0 A B | | -n | = | -nA + B |
| 0 0 -1 0 | | 1 | = | n |
w값으로 나누어 보면
z_w = -A + B / n
z_w는 near 평면의 거리이므로 -1이 되고 대입해 보면
-1 = -A + B / n, B = (A-1) / n
잠시 여기서 멈추고 far 평면에 대해서 계산해 보자.
| d/a 0 0 0 | | 0 | = | 0 |
| 0 d 0 0 | | 0 | = | 0 |
| 0 0 A B | | -f | = | -fA + B |
| 0 0 -1 0 | | 1 | = | f |
w값으로 나누면
z_w = -A + B / f
z_w는 far 평면의 거리이므로 1이 되고 대입을 해 보면
1 = -A + B / f
이 시점에서 위에서 구한 B값을 대입하여 A값을 구한다.
1 = -A + ( (A-1) / n ) / f
A에 대하여 정리를 하면
A = n + f / n - f
구해진 A를 처음에 구해진 B식에 대입하여 B를 구한다.
B = (A-1) / n
B = ( ( n + f / n - f ) - 1 ) / n
B에 대하여 정리를 하면
B = 2nf / n - f
최종적으로 구해진 A, B값을 대입하여 원근 투영 행렬은 다음과 같아진다.
(OpenGL 경우)
| d/a 0 0 0 |
| 0 d 0 0 |
| 0 0 n+f / n-f 2nf / n-f |
| 0 0 -1 0 |
DirectX의 경우
z의 범위는 [0, 1]이 되고
z의 방향도 반대가 된다. (화면 안쪽을 가리킴)
z값을 고려하기 전 행렬은 다음과 같아지고
| d/a 0 0 0 |
| 0 d 0 0 |
| 0 0 A B |
| 0 0 1 0 |
z값의 범위를 다시 고려하여 A, B를 구하면
A = f / f - n
B = -nf / f - n
행렬은 다음과 같아진다.
| d/a 0 0 0 |
| 0 d 0 0 |
| 0 0 f / f - n -nf / f - n |
| 0 0 1 0 |
마지막으로 DirectX은 행벡터 기준이므로 전치행렬을 만들어 사용한다.
| d/a 0 0 0 |
| 0 d 0 0 |
| 0 0 f / f - n 1 |
| 0 0 -nf / f - n 0 |
참고자료
Essential Mathematics for Games & Interactive Applications Second Edtion