(DirectX : Basic) 18.(수학) Projection, Screen 변환 행렬

Posted by : at

Category : DirectX


View좌표계 까지오면 카메라에 보이는 좌표계로 모든 오브젝트를 옮긴 후 이다.
이제 멀리있는것은 멀리 가까이 있는것은 가까이등을 표현하기 위해 좌표계를 옮겨야한다.

View <-> Projection

  • Projection 좌표계 : 멀리있는 아이템을 작게 가까이 있는 아이템을 크게 등등 이런 작업을 할 좌표계
  (y)
   | 
   |     /
   |    /
   |   /             o(x, y, z)
   |  /
   | /
 <카메라>--(1)----(n)----(f)--------- (z)
   | \
   |  \
   |   \
   |    \
   |     \
   |   

* n : near
* f : far

카메라의 각도가 90도이고, n~f사이에 있는 아이템만 그려준다고 가정하자

우선, Projection의 핵심은 카메라와의 거리에 따라 얼마나 작게/크게 그려질것인가를 계산하는데 있다.
비례식을 통해서 구해야한다.

   | 
   |     /(카메라 화면)
   |    /  |
   |   /   |(X, Y)   o(x, y, z)
   |  /    |(1)      |
   | /     |         |
 <카메라>-(1)-------(z)--------------
   | \     |
   |  \    |(1)
   |   \   |
   |    \  |
   |     \ |
   |  

구하고자 하는 것은 카메라 좌표계에 나올 X, Y이다.
비례식에 따라

1 : X = z : x
-> X * z = 1 * x
-> X = x / z
1 : Y = z : y
-> Y * z = 1 * y
-> Y = y / z

이러면 끝일까?

카메라에 의해 출력되는 화면의 비율이 변경될 수 있기에 반영해 줘야한다.

r = w(카메라 width) / h(카메라 height) = 화면비율

화면비율을 반영하자

X = (1/r) * (x/z)

잉? 왜 X에만 곱해주나?
상대적인 값이기에 그러함 Y가 1이고 상대적으로 X는 1/r을 곱해준다.
예를들어 width(800), height(600)이라면 800/600~=1.3 –> width가 1.3 움직일때 height는 1움직이면 되기에 X에만 곱해준다.

카메라 각도가 90도가 아닌경우를 반영

카메라로 찍을 수 있는 공간이 많아질수록 기존의 물체들의 크기는 상대적으로 작아져야한다.

// 구하고자 하는 X, Y는 아래와 같이 표현할 수 있다.
// z는 깊이 값이다.
X = (1/r) * (x/z) * (1/tan(a/2))
Y = (y/z) * (1/tan(a/2))
  • (x/z) : 기존 x에 깊이값 z만큼을 반영해야하고
  • 1/tan(a/2) : 카메라 각도만큼도 반영해 줘야하며
  • (1/r) : 화면의 비율또한 반영해 줘야한다.
// 참고1 : 화면의 비율
r(화면의 비율)=w(width)/h(height)
// 참고2 : 높이를 tan로 구할수 있음.

    /|
   / |
  /  tan(a/2) <- 높이
 / a | 
/----|

우선 깊이를 고려하지 않고 식을 간단히 해보자면

1/(r*tan(a/2)) 0            0 0
0              1/(tan(a/2)) 0 0
0              0            1 1 // z를 보존하기 위해서 여기 1을 넣음
0              0            0 0

이 Matrix를 (x, y, z, 1) 에 곱한다

[1/(r*tan(a/2)), 1/(tan(a/2)), z, z] 를 구할수 있는데 세 번째 z값은 기존의 깊이값인 z가 필요한 것이아니라 n~f사이의 깊이값 z가 필요하다
Matrix를 좀 더 수정해야 한다.

1/(r*tan(a/2)) 0            0 0
0              1/(tan(a/2)) 0 0
0              0            A 1
0              0            B 0

[1/(r*tan(a/2)), 1/(tan(a/2)), Az+B, z] 이 값이 나오면
마지막 z로 앞의 세 항을 나눈다
[1/(r*z*tan(a/2)), 1/(z*tan(a/2)), A+(B/z)]

마지막으로 A, B만 구하면 된다.

// n(near)일때 0이고
// f(far)일때 1이기에
G(n) = A + (B/n) = 0
G(f) = A + (B/f) = 1

// ...

B = -An
A-(An/f) = 1

// ...

A = f/(f-n)
B = -nf/(f-n)

완성

1/(r*tan(a/2)) 0            0         0
0              1/(tan(a/2)) 0         0
0              0            f/(f-n)   1
0              0            -nf/(f-n) 0

Projection <-> Screen

Projection은 -1~1의 값을 갖는데 그 값을 실제 Screen(Viewport)의 Width, Height에 맞춰 주면 끝이다.

(left, top)
      -------------------------
      |                        |
      |                        |
      |                        |
      |        ViewPort        | h(height)
      |                        |
      |                        |
      |                        |
      |                        |
      -------------------------
              w(width)

X = Left + ((x+1)/2) * w
Y = TOP + ((1-y)/2) * h
w/2      0       0 0
0        -h/2    0 0
0        0       0 0
w/2+left h/2+top 0 1

여러 ViewPort를 사용할 수 있어서 Viewport자체의 깊이 값도 생각해야 한다.(많이 쓰진 않음)

w/2      0       0       0
0        -h/2    0       0
0        0       max-min 0
w/2+left h/2+top min     1

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links