ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [DirectX] 3-3. View 행렬, Projection 행렬
    Game Development/DirectX 2024. 6. 19. 19:34

    이번 글에서는 변환 행렬인 View 행렬과 Projection 행렬에 대해서 알아보도록 하겠습니다.

     

    View 행렬

    View 행렬은 오브젝트를 world space에서 camera space로 변환하는 행렬입니다.

    여기서 view space는 카메라가 공간의 원점에 있고, 양의 z축을 바라보고 있는 좌표계입니다.

    그런데, 여기서 말하는 카메라는 실제로 게임 속에 존재하는 것이 아니라

    위치, 회전, 시야각, 렌더링 범위 같은 정보를 가지지만 실제로 존재하지 않는 오브젝트입니다.

     

    게임들을 보면 유저가 조작하는 것에 따라서 실제로 카메라가 있는 것처럼 화면을 보여주는데,

    카메라가 존재하는 게 아니라면 어떻게 이런 게 가능한지 의문이 드실 것입니다.

    이것의 답은 공간 자체를 이동시키면서 카메라가 있는 것처럼 효과를 주는 것입니다.

    어떤 식으로 공간을 이동시켜 카메라가 있는 것처럼 효과를 내는지 한 번 알아보겠습니다.

     

    예를 들어 카메라의 위치가 \((0, 0, 0)\)에 있다고 하고, z축을 바라보고 있다고 하면,

    위에서 보았을 때 아래의 그림과 같은 모습일 것입니다.

    C - 카메라, O - 오브젝트

     

    그럼 이제 여기서 카메라가 왼쪽으로 1만큼 움직였다고 해보겠습니다.

    이러면 아래와 같은 모습이 될 것입니다.

     

    하지만 아까 위에서 말했듯이 카메라는 실제로 존재하는 것이 아니기 때문에

    카메라를 움직이는 것이 아니라 공간자체를 움직인다고 했었습니다.

     

    따라서 위의 그림에서 카메라에 보이는 모습을 공간의 이동으로 만들려면

    아래의 그림처럼 공간을 카메라가 움직여야 하는 것의 반대로 움직이면 됩니다.

     

    카메라의 회전도 마찬가지입니다.

    예를 들어 카메라가 양의 z축을 보고 있다가, 시계방향으로 45도 회전했다고 하겠습니다.

    그럼 아래 그림처럼 될 것입니다.

     

    카메라가 회전했을 때도 마찬가지로 공간을 카메라가 회전한 것의 반대로 회전시키면 됩니다.

     

    이렇게 해서 카메라의 효과를 내는 것을 알아보았습니다.

    이제 View 행렬이 어떻게 유도되는지 보도록 하겠습니다.

     

    먼저 View 행렬은 world space에서 view space로 만드는 행렬이라고 말씀드렸었습니다.

    이것은 카메라의 위치를 \((0, 0, 0)\)으로 만들고,

    카메라가 바라보는 방향을 z축으로 만든다는 말과 같은 말입니다.

     

    따라서 View 행렬을 구하기 위해서 카메라의 이동 행렬과 회전 행렬에 대해서 먼저 설명하겠습니다. 

    View 행렬은 카메라가 이동하고 회전하는 것의 반대로 움직이도록 하는 행렬로 카메라의 이동 행렬과 회전 행렬을 곱한 것의 역행렬이기 때문입니다.

     

     

    카메라의 회전 행렬

    전에 World 행렬에서도 설명했듯이 회전 행렬이 이동 행렬보다 먼저 적용되어야 하기 때문에 회전 행렬에 대해서 먼저 작성해 보겠습니다.

     

    회전 행렬에는 오일러 각을 이용한 회전 행렬과 목표물을 바라보도록 하는 회전 행렬 2가지가 있습니다.

    오일러 각을 이용한 회전부터 유도해 보도록 하겠습니다.

     

     

    오일러 각을 이용한 회전 행렬은 카메라의 오일러 회전값을 알고 있을 때 사용 가능하며,

    카메라의 오일러 회전값을 통해서 회전 행렬을 구성하는 방식입니다.

    $$ \begin{bmatrix} \cos\theta & 0 & \sin\theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin\theta & 0 & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos\theta & -\sin\theta & 0 \\ 0 & \sin\theta & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} \cos\theta & -\sin\theta & 0 & 0 \\ \sin\theta & \cos\theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} $$ 

    여기서 위의 행렬에서 각 축에 대한 회전 행렬의 \(\theta\)에다가 각 축에 대한 회전값을 넣어주면 됩니다.

     

    하지만 이 오일러 각을 이용한 방식은 짐벌락이 일어나기 쉽습니다.

    짐벌락은 다른 축의 회전에 의해서 나머지 두 축의 회전방향이 같아지는 현상입니다.

    예를 들어 만약 카메라가 x축으로 90도 회전을 한다고 하면, y축과 z축의 회전이 똑같아집니다.

    이 짐벌락을 해결하기 위해서 사원수(Quaternion)라는 것을 사용하는데,

    이 내용은 나중에 다루도록 하겠습니다.

     

     

    다음으로 목표물을 바라보도록 하는 회전 행렬에 대해 알아보겠습니다.

    목표물을 바라보도록 하는 회전 행렬을 사용한 View 행렬을 LookAt 행렬이라고도 하며, 카메라가 바라볼 물체의 위치를 알고 있을 때 사용할 수 있습니다.

     

    이 회전 행렬을 구하는 과정은 다음과 같습니다.

    먼저 카메라의 앞쪽을 향하는 벡터(forward), 카메라의 오른쪽을 향하는 벡터(right), 카메라의 위쪽을 향하는 벡터(up)를 통해서 회전 행렬이 구성되기 때문에 이것들을 먼저 구해야 합니다.

     

    여기서 forward, right, up 벡터는 모두 정규화된 벡터로

    각 벡터는 world space의 z축, x축, y축 단위 벡터가 각각 회전한 것입니다. 

    각각 아래와 같이 구할 수 있으며, 여기서 \(\times\)는 외적 연산 기호입니다.

    $$ forward = Normalize(target_{position} - camera_{position}) $$

    $$ right = Normalize(world_{up} \times forward) $$

    $$ up = Normalize(forward \times right) $$

     

    forward부터 보면 target의 위치에서 카메라의 위치를 뺐는데,

    이렇게 해야지 카메라에서 target으로 향하는 벡터가 나옵니다.

     

    right는 world space의 up 벡터와 forward 벡터를 외적 해서 구할 수 있습니다.

    여기서 왼손 좌표계에서는 외적의 결과가 오른나사 법칙의 반대를 향하기 때문에 up, forward 순서로 외적 해야 합니다.

     

    up도 right와 마찬가지입니다.

    forward 벡터와 right 벡터를 외적 해서 구하며,

    외적의 결과가 오른나사 법칙의 반대로 나오므로 forward, right 순서로 외적해야 합니다.

     

    이렇게 해서 3가지 벡터를 모두 구했으니,

    이제 회전 행렬을 구성해 보면 아래와 같은 과정을 통해서 행렬이 나옵니다.

     

    먼저 z축 벡터가 forward 벡터가 되므로

    $$ \begin{bmatrix} 1 & 0 & forward.x & 0 \\ 0 & 1 & forward.y & 0 \\ 0 & 0 & forward.z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} $$

     

    다음으로 x축 벡터가 right 벡터가 되므로

    $$ \begin{bmatrix} right.x & 0 & forward.x & 0 \\ right.y & 1 & forward.y & 0 \\ right.z & 0 & forward.z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} $$

     

    마지막으로 y축 벡터가 up 벡터가 되므로

    $$ \begin{bmatrix} right.x & up.x & forward.x & 0 \\ right.y & up.y & forward.y & 0 \\ right.z & up.z & forward.z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} $$

     

    이렇게 해서 목표물을 바라보도록 하는 회전 행렬이 완성되었습니다.

     

     

    카메라의 이동 행렬

    카메라의 이동 행렬은 간단합니다. 

    원점에서 카메라의 위치로 이동만 하면 되기 때문에 행렬은 다음과 같이 나오며, 여기서 x, y, z는 카메라의 좌표입니다.

    $$ \begin{bmatrix} 1 & 0 & 0 & x \\ 0 & 1 & 0 & y \\ 0 & 0 & 1 & z \\ 0 & 0 & 0 & 1 \end{bmatrix} $$

     

     

     

    이렇게 해서 카메라의 이동 행렬과 회전행렬을 구했으니 이제 View 행렬만 구하면 됩니다.

    위에서 말씀드렸듯이 View 행렬은 카메라의 이동 행렬과 회전 행렬의 곱의 역행렬입니다.

    또한 World 행렬에서 했던 것처럼 회전 행렬이 이동 행렬보다 먼저 적용되어야 하기 때문에

    다음과 같이 나타낼 수 있으며, 여기서 T는 이동 행렬 R은 회전 행렬입니다.

    $$ (T \cdot R)^{-1} = R^{-1} \cdot T^{-1} $$

     

    위의 식에서 회전 행렬이 오일러 각을 이용한 것이었다면 아래와 같은 행렬이 나올 것이고,

    $$ \begin{bmatrix} \cos\theta & \sin\theta & 0 & 0 \\ -\sin\theta & \cos\theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos\theta & \sin\theta & 0 \\ 0 & -\sin\theta & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} \cos\theta & 0 & -\sin\theta & 0 \\ 0 & 1 & 0 & 0 \\ \sin\theta & 0 & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & 0 & -x \\ 0 & 1 & 0 & -y \\ 0 & 0 & 1 & -z \\ 0 & 0 & 0 & 1 \end{bmatrix} $$

     

    목표물을 바라보는 행렬이었다면 아래와 같은 행렬이 나올 것입니다.

    여기서 f는 forward 벡터, r은 right 벡터, u는 up 벡터를 뜻하며, pos는 카메라의 위치를 뜻합니다.

    $$ \begin{bmatrix} r.x & r.y & r.z & 0 \\ u.x & u.y & u.z & 0 \\ f.x & f.y & f.z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & 0 & -x \\ 0 & 1 & 0 & -y \\ 0 & 0 & 1 & -z \\ 0 & 0 & 0 & 1 \end{bmatrix}  = \begin{bmatrix} r.x & r.y & r.z & -r\cdot pos \\ u.x & u.y & u.z & -u\cdot pos \\ f.x & f.y & f.z & -f\cdot pos \\ 0 & 0 & 0 & 1 \end{bmatrix} $$

     

    이렇게 해서 두 가지 종류의 View 행렬을 모두 구할 수 있습니다.

     

    Projection 행렬

    Projection 행렬은 원근 투영 행렬과 직교 투영 행렬로 2가지가 있습니다.

    보통 투영 변환에서 사용되며, 투영 변환은 3차원 공간을 2차원 평면에 투영시키는 과정으로 view space에서 NDC(Normalized Device Coordinates) 좌표계로 변화시키는 것을 말합니다.

    여기서 NDC는 \(-1 \le x, y \le 1 \ \ and \ \ 0 \le z \le 1\) 를 만족하는 영역입니다.

     

    원근 투영 행렬(Perspective Projection)

    원근 투영 행렬을 사용하면 원근 효과를 주면서 3차원 공간을 2차원 평면에 투영시키도록 합니다.

    가까울수록 커지고, 멀수록 작아지면서 소실점에 가까워지는 원근법을 적용하기 위해서

    원근 효과를 주는 카메라라면 view space에서의 렌더링 영역으로

    사각뿔의 끝부분이 잘린 모양인 절두체 모양을 사용합니다.

    절두체

    • fov - 시야각
    • near plane - 렌더링 되는 가장 앞쪽 평면
    • far plane - 렌더링 되는 가장 먼 평면

     

    그리고 위에서 투영 변환은 view space에서 NDC로 변환한다 하였는데,

    사실 여기서 투영 변환 행렬을 사용한다고 바로 NDC로 변환되지 않고

    clip space라는 공간을 거치게 됩니다.

     

    clip space는 렌더링 영역에 정점이 있는지 없는지를 판단해서

    정점의 렌더링 여부를 결정하는 데 사용되는 공간을 말합니다.

     

    원근 투영 행렬을 통해서 만들어진 clip space는

    위의 절두체를 직육면체로 변형한 공간을 렌더링 영역으로 사용하며,

    \((x, y, z)\)로 구성된 3차원 좌표가 아닌 \((x, y, z, w)\)로 구성된 3차원 동차 좌표계를 사용합니다.

    여기서 각 좌표값은 다음과 같은 의미를 가집니다.

    • \(x\) : 클립 좌표계 상에서의 x좌표
    • \(y\) : 클립 좌표계 상에서의 y좌표
    • \(z\) : 깊이값 (카메라로부터 얼마나 떨어져 있는지)
    • \(w\) : view space에서의 z좌표

    또한 절두체를 변형해서 만들어진 렌더링 영역은 \((-w, -w, 0)\) ~ \((w, w, w)\)가 범위인데,

    각 정점은 \(w\)값이 다를 수 있으므로 각 정점마다 영역의 크기가 다르게 나올 수 있습니다.

     

    그리고 clip space를 통해서 렌더링 될 정점으로 판별된 정점들은

    좌표를 \(w\)로 나눔으로써 NDC 좌표계로 이동하게 되는데,

    여기서 \(w\)로 나누는 과정을 원근 나누기라고 합니다.

     

    이제 원근 투영 행렬을 유도해 보도록 하겠습니다.

    밑에서 나오는 \((x, y, z)\)는 view space에서의 좌표이며, \((x', y', z', w')\)은 clip space에서의 좌표입니다.

     

    먼저 \(x'\)과 \(y'\)을 구해 보겠습니다.

    클립 공간에서의 정점은 NDC로 변환하는 과정에서 \(w'\)에 의해서 나눠지게 되기 때문에

    NDC 상에서의 좌표를 구한 뒤 \(w'\)을 곱한 것을 구하는 행렬을 만들면 됩니다.

     

    이걸 하기 위해서 NDC 상에서의 \(x, y\)좌표를 구해보도록 하겠습니다.

    이것을 구하기 위해서는 초점거리라는 것을 구해야 합니다.

    초점거리는 카메라로부터 투영될 평면까지의 거리입니다. 

    이 초점거리를 활용해서 비례식을 세우면 NDC 상에서의 좌표를 구할 수 있습니다.

     

    투영될 평면과 near plane, far plane은 동일한 비율을 가지고 있습니다.

    따라서 실제 크기는 별로 상관이 없고, 비율이 중요하기 때문에

    투영될 평면의 높이를 2로 하여서 계산하기 쉽게 해서 구해보도록 하겠습니다.

     

    카메라가 높이가 2인 평면을 바라보고 있다고 할 때 옆에서 보면 아래와 같은 모습일 것입니다.

     

    높이가 2이기 때문에 그에 절반은 1일 것이고, 카메라의 시야각도 절반이 될 것입니다.

    따라서 초점거리를 구하는 식은 다음과 같습니다.

    $$ {1\over d} = tan({\theta\over2}) $$

    $$ d = {1\over tan({\theta\over2})} $$

     

    초점거리를 구했으니 비례식을 세워보겠습니다.

    N : 투영된 정점, V : view space의 정점

     

    위와 같은 그림이 있다고 했을 때, 다음과 같은 비례식을 세울 수 있습니다.

    $$ d : z = N_y : y $$

    $$ N_y * z = d * y $$

    $$ N_y = {d * y \over z} $$

     

    위의 식을 통해서 투영된 평면에서의 y좌표을 얻을 수 있었습니다. 

    \(z\)값에 대한 처리는 나중에 행렬 부분에서 하도록 하겠습니다.

     

    다음은 투영된 평면에서의 x좌표를 구해보도록 하겠습니다.

    근데 여기서 문제가 하나 발생하게 됩니다.

    \(x\)를 그대로 변환하게 되면 NDC에서 모니터 화면으로 넘어가면서

    아래 그림처럼 화면이 가로로 늘어난 것처럼 보이게 됩니다.

     

    이것을 해결하기 위해서 미리 오브젝트를 찌그러트려 놓는 방법이 있습니다.

    미리 오브젝트를 찌그러 트리는 효과를 주기 위해서는 \(x\)를 종회비로 나눠주면 됩니다.

    이렇게 하면 아래 그림처럼 오브젝트가 찌그러지게 되고, 모니터로 넘어올 때는 원래대로 보이게 됩니다.

     

    종횡비를 적용해서 비례식을 세우면 NDC에서의 x좌표는 다음과 같이 나옵니다.

    $$ a(종횡비) = {width\over height} $$

    $$ d : z = N_x : {x\over a} $$

    $$ N_x * z = {d * x\over a} $$

    $$ N_x = {{d * x\over a}\over z} $$

     

    이렇게 해서 NDC에서의 x좌표와 y좌표를 구했으니 NDC에서의 z좌표가 남았는데,

    이건 행렬을 만들면서 구해보도록 하겠습니다.

     

    이제 원근 투영 행렬을 유도해 보도록 하겠습니다.

    먼저 원근 투영 행렬이 아래와 같다고 하겠습니다. 

    $$ \begin{bmatrix} a & b & c & d \\ e & f & g & h \\ i & j & k & l \\ m & n & o & p \end{bmatrix} $$

     

    여기에 \((x, y, z)\)를 곱하면 아래와 같이 나오게 되고, 다음과 같이 전개할 수 있습니다.

    $$ \begin{bmatrix} a & b & c & d \\ e & f & g & h \\ i & j & k & l \\ m & n & o & p \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} x' \\ y' \\ z' \\ w' \end{bmatrix} $$

    $$ \begin{bmatrix} x' \\ y' \\ z' \\ w' \end{bmatrix} = \begin{bmatrix} N_x * w' \\ N_y * w' \\ N_z * w' \\ 1 * w' \end{bmatrix} = \begin{bmatrix} {{d * x\over a}\over z} * w' \\ {d * y \over z} * w'  \\ N_z * w' \\ w' \end{bmatrix} $$

     

    위에서 \(w'\)는 \(z\)랑 똑같다고 했으니 식은 다음과 같이 됩니다.

    $$ \begin{bmatrix} {{d * x\over a}\over z} * w' \\ {d * y \over z} * w'  \\ N_z * w' \\ w' \end{bmatrix} = \begin{bmatrix} {{d * x\over a}\over z} * z \\ {d * y \over z} * z  \\ N_z * z \\ z \end{bmatrix} = \begin{bmatrix} {d * x\over a} \\ d * y \\ N_z * z \\ z \end{bmatrix} $$

     

    위의 식이 원근 투영 행렬과 \((x, y, z ,1)\)을 곱한 결과라고 하면,

    원근 투영 행렬은 아래와 같이 바뀝니다.

    $$ \begin{bmatrix} {d\over a} & 0 & 0 & 0 \\ 0 & d & 0 & 0 \\ i & j & k & l \\ 0 & 0 & 1 & 0 \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} {d * x\over a} \\ d * y \\ N_z * z \\ z \end{bmatrix} $$

     

    남은 건 3행인데, 여기서 \(x, y\)는 깊이값과 관계가 없으므로 \(i, j\)는 0이 됩니다.

    $$ \begin{bmatrix} {d\over a} & 0 & 0 & 0 \\ 0 & d & 0 & 0 \\ 0 & 0 & k & l \\ 0 & 0 & 1 & 0 \end{bmatrix} $$

     

    이제 여기서 \(k\)와 \(l\)은 연립 방정식으로 구할 수 있습니다.

    위의 원근 투영 행렬과 \((x, y, z, 1)\)을 곱하면 아래와 같이 나옵니다.

    $$ \begin{bmatrix} {d\over a} & 0 & 0 & 0 \\ 0 & d & 0 & 0 \\ 0 & 0 & k & l \\ 0 & 0 & 1 & 0 \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} {d * x\over a} \\ d * y \\ k * z + l \\ z \end{bmatrix} $$

     

    위의 결과에서 \(z'\)이 \(k * z + l\)입니다.

    이걸 NDC에서의 z좌표 값으로 바꾸면 \(z\)로 나눠지기 때문에 \(k + {l\over z}\)가 됩니다.

    그리고 이것을 함수 \(g(z)\)로 정의하면 \(g(z) = k + {l\over z}\)이 됩니다.

     

    그리고 여기서 near를 카메라에서 near plane까지의 거리, far를 카메라에서 far plane까지의 거리라고 하면,

    NDC에서의 z값의 범위는 0 ~ 1이기 때문에 \(g(near) = 0, g(far) = 1\)이 됩니다.

    이 식을 이용해서 연립 방정식을 푸는 과정은 다음과 같습니다.

     

    $$ g(near) = k + {l\over near} = 0 $$ 

    $$ g(far) = k + {l\over far} = 1 $$ 

     

    \(g(near)\)를 이용해서 \(l\)을 정의하면 아래처럼 되고,

    $$ k + {l\over near} = 0 $$

    $$ {l\over near} = -k $$

    $$ l = -k * near $$

     

    이걸 \(g(far)\)에 대입하면, \(k\)를 구할 수 있습니다.

    $$ k + {-k * near\over far} = 1 $$

    $$ {k * far\over far} - {k * near\over far} = 1 $$

    $$ {k * far - k * near\over far} = 1 $$

    $$ {k * (far - near)\over far} = 1 $$

    $$ k * (far - near) = far $$

    $$ k = {far\over(far - near)} $$

     

    이렇게 구한 \(k\)를 이용해서 \(l = -k * near\)를 풀면, \(l\)을 구할 수 있습니다.

    $$ l = -{far\over(far - near)} * near $$

    $$ l = -{far * near\over(far - near)} $$

     

    이렇게 해서 \(k\)와 \(l\)을 구했으니 행렬을 완성해 보면 다음과 같은 행렬이 나오게 됩니다.

    $$ \begin{bmatrix} {d\over a} & 0 & 0 & 0 \\ 0 & d & 0 & 0 \\ 0 & 0 & {far\over(far - near)} & -{far * near\over(far - near)} \\ 0 & 0 & 1 & 0 \end{bmatrix} $$

     

    직교 투영 행렬(Orthographic Projection)

    직교 투영 행렬은 3차원의 정점을 특정 평면에 수직으로 내림으로써

    3차원 공간을 2차원 평면에 투영시키도록 합니다.

     

    직교 투영의 경우는 원근 표현을 할 필요가 없기 때문에

    view space에서의 렌더링 영역으로 직육면체를 사용합니다.

     

    직교 투영 행렬에 의해서 나온 결과에서 \(w\)가 항상 1이기 때문에 

    원근 나누기 과정이 없는 것이나 마찬가지입니다.

     

    따라서 직교 투영 행렬에 의해서 생성된 clip space는 NDC와 같은 범위의 영역입니다.

    이 때문에 직교 투영에서는 clip space와 NDC를 동일하게 보기도 합니다.

     

    이제 직교 투영 행렬을 유도해 보도록 하겠습니다.

    아래서 나오는 \((x, y, z)\)는 view space에서의 좌표이며, \((x', y', z', w')\)는 clip space에서의 좌표입니다.

     

    먼저 \(x', y'\)부터 구해보겠습니다.

    clip space의 범위와 NDC의 범위가 같다고 했으니

    \(x'\)과 \(y'\)은 \(-1 ~ 1\)사이의 값입니다.

     

    그리고 \(x\)의 범위는 \(-{width\over2} ~ {width\over2}\) 이므로

    이것을 \(-1 ~ 1\)로 바꾸려면 \(2\over width\)를 곱해야 합니다.

     

    \(y\)도 마찬가지로 \(-{height\over2} ~ {height\over2}\)를 \(-1 ~ 1\)로 바꿔야 하므로

    \(2\over height\)를 곱해야 합니다.

     

    따라서 직교 투영 행렬을 아래와 같다고 할 때, 다음과 같이 바꿀 수 있습니다.

    $$ \begin{bmatrix} a & b & c & d \\ e & f & g & h \\ i & j & k & l \\ m & n & o & p \end{bmatrix} \Rightarrow \begin{bmatrix} {2\over width} & 0 & 0 & 0 \\ 0 & {2\over height} & 0 & 0 \\ i & j & k & l \\ m & n & o & p \end{bmatrix} $$

     

    그리고 위에서 \(w'\)은 1이라고 했기 때문에 행렬은 다음과 같이 바뀝니다.

    $$ \begin{bmatrix} {2\over width} & 0 & 0 & 0 \\ 0 & {2\over height} & 0 & 0 \\ i & j & k & l \\ 0 & 0 & 0 & 1 \end{bmatrix} $$

     

    다음으로 \(x\)와 \(y\)는 \(z\)값에 영향을 주지 않으므로 \(i\)와 \(j\)는 0입니다.

    $$ \begin{bmatrix} {2\over width} & 0 & 0 & 0 \\ 0 & {2\over height} & 0 & 0 \\ 0 & 0 & k & l \\ 0 & 0 & 0 & 1 \end{bmatrix} $$

     

    마지막으로 \(k\)와 \(l\)은 연립 방정식을 통해 구할 수 있습니다.

    먼저 \(z'\)은 \(k * z + l\)이기 때문에 이것을 함수 \(g(z)\)로 만들면 다음과 같아집니다.

    $$ g(z) = k * z + l $$

     

    그리고 여기서 near를 카메라에서 near plane까지의 거리, far를 카메라에서 far plane까지의 거리라고 하면,

    \(g(near) = 0, g(far) = 1\)이 됩니다.

    이걸 연립 방정식으로 풀면 다음과 같은 과정을 통해 구할 수 있습니다.

     

    $$ g(near) = k * near + l = 0 $$

    $$ g(far) = k * far + l = 1 $$

     

    $$ k * near + l = 0 $$

    $$ l = -k * near $$

     

    $$ k * far + (-k * near) = 1 $$

    $$ k * (far - near) = 1 $$

    $$ k = {1\over(far - near)} $$

     

    $$ l = -k * near $$

    $$ l = -{1\over(far - near)} * near $$

    $$ l = -{near\over(far - near)} $$

     

    이렇게 해서 \(k\)와 \(l\)을 구했으니 행렬을 만들어 보면 다음과 같이 나옵니다.

    $$ \begin{bmatrix} {2\over width} & 0 & 0 & 0 \\ 0 & {2\over height} & 0 & 0 \\ 0 & 0 & {1\over(far - near)} & -{near\over(far - near)} \\ 0 & 0 & 0 & 1 \end{bmatrix} $$

     

     

    이번 글은 꽤 길었던 내용이었던 것 같습니다.

    행렬을 만드는 부분들이 특히 더 어려운 내용이어서 쓰는데 좀 걸린 것 같습니다.

    그럼 다음 글에서는 지금까지 알아봤던 행렬을 직접 구현해 보도록 하겠습니다.

    읽어주셔서 감사합니다.

    'Game Development > DirectX' 카테고리의 다른 글

    [DirectX] 4. 삼각형 띄우기  (0) 2024.06.26
    [DirectX] 3-4. Matrix4 클래스 제작  (0) 2024.06.21
    [DirectX] 3-2. World 행렬  (0) 2024.06.17
    [DirectX] 3-1. 행렬  (0) 2024.06.17
    [DirectX] 2-2. Vector3 및 Vector2 클래스 제작  (0) 2024.06.08
Designed by Tistory.