ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [DirectX] 3-1. 행렬
    Game Development/DirectX 2024. 6. 17. 13:48

    이번 글에서는 앞으로 나올 행렬이라는 것에 대해서 작성해 보도록 하겠습니다.

     

     

     

    먼저 행렬이 무엇인지부터 알아보도록 하겠습니다.

    행렬은 숫자들을 직사각형 형태로 행과 열에 따라 나열한 것입니다.

    행렬은 행 \(\times\) 열 형태로 크기를 나타내며, \(n \times m\) 행렬이라고 하면 \(n\)행 \(m\)열의 직사각형을 뜻합니다.

    예를 들어 \(2 \times 3\) 행렬은 2행 3열을 뜻하며 아래와 같이 작성할 수 있습니다.

    $$ \begin{bmatrix} a & b & c \\ d & e & f \end{bmatrix} $$

     

     

     

    행렬들도 종류가 여러 개가 존재하는데, 그 중 제가 구현한 3가지 정도만 알아보도록 하겠습니다.

     

    첫 번째는 단위행렬입니다.

    단위행렬은 \(i\)행 \(i\)열만 1이고, 나머지는 모두 0인 행렬입니다.

    $$ \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} $$

     

    두 번째는 전치 행렬입니다.

    어떤 행렬의 전치 행렬은 \(i\)행 \(i\)열을 기준으로 대칭 이동한 행렬입니다.

    $$ \begin{bmatrix} 1 & a & b & c \\ d & 1 & e & f \\ g & h & 1 & i \\ j & k & l & 1 \end{bmatrix} \Rightarrow \begin{bmatrix} 1 & d & g & j \\ a & 1 & h & k \\ b & e & 1 & l \\ c & f & i & 1 \end{bmatrix} $$

     

    마지막은 역행렬입니다.

    역행렬은 숫자에서의 역수와 비슷한 개념입니다. 

    어떤 숫자와 그 수의 역수를 곱하면 1이 나오듯이, 

    어떤 행렬과 그 행렬의 역행렬을 곱하면 단위 행렬이 나옵니다.

    이렇게 행렬과 곱했을 때 단위행렬이 나오는 행렬을 역행렬이라고 하며,

    어떤 행렬 \(M\)의 역행렬은 \(M^{-1}\)로 표현합니다.

     

     

     

    다음으로 알아볼 것은 행렬의 연산입니다.

    행렬에도 당연하게도 연산들이 존재하는데,

    저는 행렬 곱셈만 사용했기 때문에 행렬 곱셈에 대해서만 설명하도록 하겠습니다.

     

    행렬 곱셈은 두 행렬을 이용해서 하나의 행렬을 만드는 연산입니다.

    행렬 \(A\)와 \(B\)를 행렬 곱셈을 한다고 할 때, \(AB\) 형태로 나타내며, \(A\)의 열의 개수와 \(B\)의 행의 개수가 일치해야 합니다.

    행렬 곱셈을 하는 방식은 다음과 같습니다.

    \(A\)와 \(B\)가 \(3 \times 3\) 행렬일 때 결과 행렬을 \(M\), 행렬 요소의 행을 \(i\), 행렬 요소의 열을 \(j\)라고 하면,

    \(M_{ij}\)은 다음과 같이 정의됩니다.

    $$ M_{ij} = A_{i1} * B_{1j} + A_{i2} * B_{2j} + A_{i3} * B_{3j} $$

     

    이렇게 정의된 식을 잘 보면, 어디선가 보았던 식입니다.

    저저번 글에서 설명했던, 벡터 연산에서 보안던 내적의 식과 같다는 것을 알 수 있습니다.

    따라서 행렬의 곱셈에서 결과 행렬의 각 요소는 앞 행렬의 행과 뒤 행렬의 열을 내적 한 것이라고도 할 수 있습니다.

     

    이렇게 행렬을 곱하는 이유는 여러 행렬을 곱함으로써

    어떤 요소에 행렬을 여러 번 곱해야 할 것을 한 번만 곱하는 것으로 줄일 수 있기 때문입니다.

     

    예를 들어서 행렬과 곱해지는 요소가 n개 있고, 곱해야 하는 행렬이 m개가 있다고 하면,

    각 요소에 행렬을 따로 곱하면 총 n * m번 연산을 하지만 행렬을 곱해놓은 뒤 요소에 곱하면 m + n번으로 연산량이 줄어들게 됩니다. 

    이렇게 연산을 줄이는 것은 뒤에서 알아볼 변환행렬들에도 사용이 됩니다.

     

     

     

    다음으로 행렬 곱셈의 성질에 대해서 알아보겠습니다.

    행렬 곱셈에는 여러 가지 성질이 있는데, 그중 하나는  \(n \times m\) 행렬과 \(m \times k\) 행렬을 곱셈을 하면 결과 행렬의 크기가 \(n \times k\)라는 것입니다.

    그 이유는 앞의 행렬의 행의 개수 당, 뒤의 행렬이 열의 개수만큼 내적 연산이 실행되기 때문입니다.

     

    또 다른 성질로는 행렬 곱셈은 결합법칙은 성립하지만, 교환법칙은 성립하지 않는다는 것입니다.

    이 때문에 생겨난 것이 pre-multiplication과 post-multiplication입니다.

     

    이 두 가지는 행렬과 벡터를 곱할 때 벡터가 앞(왼쪽)에 있는지, 뒤(오른쪽)에 있는지를 말하며,

    이것은 행렬의 적용순서에도 영향을 미칩니다.

     

    예를 들어 어떤 벡터와 여러 행렬을 곱해놓은 행렬을 곱한다고 할 때,

    벡터가 앞에 있으면 행렬이 곱해진 순서대로 벡터에 적용이 되며,

    벡터가 뒤에 있으면 행렬이 곱해진 역순으로 벡터에 적용이 됩니다.

    참고로 벡터와 행렬을 곱하면, 벡터를 \(1 \times n\) 혹은 \(n \times 1\) 크기의 행렬이라 가정합니다.

     

     

     

    마지막으로 행렬 곱셈을 역행렬에 대해서 알아보도록 하겠습니다.

    어떤 두 행렬을 곱한 것의 역행렬의 구하는 경우에는

    아래의 식처럼 곱을 구성하는 두 행렬의 역행렬을 구해서 곱하는 순서를 뒤집으면 됩니다.

    $$ (N \cdot M)^{-1} = M^{-1} \cdot N^{-1} $$

    이것은 몇개의 행렬을 곱하든 똑같이 적용됩니다.

     

     

     

    이렇게 해서 행렬 곱셈에 대해서 알아보았고,

    마지막으로 row-major와 column-major에 대해서 알아보도록 하겠습니다.

    컴퓨터에 행렬을 저장하게 되면 배열을 활용하게 되는데, 열은 메모리에 1자로 저장하게 됩니다.

    이것 때문에 나온 것이 row-major와 column-major인데, 이 두 가지 개념은 행렬을 어떤 것을 우선해서 저장할지를 설명하는 것입니다.

     

    먼저 row-major부터 보도록 하겠습니다.

    row-major는 행을 우선해서 저장하는 방식입니다.

    아래와 같은 행렬이 있다고 할 때, 행을 따르면서 메모리에 저장합니다.

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

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

    위와 같은 순서대로 저장하게 됩니다. 

    그리고 벡터를 저장할 때는 \([1 * n]\) 형태의 행렬로 저장됩니다. 

     

    다음으로 column-major입니다.

    column-major는 열을 우선해서 저장하는 방식입니다.

    위에서 봤던 행렬을 아래와 같은 순서로 저장합니다.

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

     

    그리고 벡터를 저장할 때는 \([1 * n]\) 형태의 행렬로 저장됩니다. 

     

    row-major에서 column-major로 바꾸는 경우나, 그 반대의 경우는 행렬을 전치행렬 구하면 됩니다. 

     

     

     

    이걸 설명한 이유는 지금 사용하고 있는 DirectX에서 제공하는 행렬은 row-major를 사용하며, 나중에 사용할 hlsl이라는 쉐이딩 언어는 기본적으로 column-major를 사용하기 때문에 이것에 대한 개념이 필요했기 때문입니다.

     

    그런데 저는 행렬을 직접 구현할 것이기 때문에 조금 독특한 방식을 사용할 것입니다.

    DirectX에서 행렬을 column-major의 형태로 저장하고, Post-Multiplication을 사용할 것입니다. 

     

    그 이유는 다음과 같습니다. 

    나중에 사용할 hlsl의 mul함수는 먼저들어온 값이 벡터이면,

    이 벡터를 row-major로 취급해서 Pre-Multiplication으로 계산합니다.

    이때 행렬이 row-major일때의 결과랑 똑같아야 하는데,

    DirectX에서 hlsl로 넘어갈 때 행렬이 전치되서 넘어가게 되기 때문에 column-major를 사용할 것입니다. 

     

    따라서 뒤에서 나오는 행렬들은 모두 column-major 기준으로 설명드리겠습니다. 

     

    이것으로 행렬에 관한 설명을 마치고, 다음 글에서는 변환 행렬들에 대해서 알아보도록 하겠습니다. 

    읽어주셔서 감사합니다.

Designed by Tistory.