본문 바로가기

DX

GLM

glm Math  과 DirectX Math  그리고 DirectX 를 더 편하게 사용하게 해주는 Simple Math 에서는 Vector 의 차이가 있다

 

glm 에서는 column major 를 사용하며 DirectX 와 Simple Math 에서는 Row Vector 를 기본으로 사용한다.

 

 

일단 glm 의 행렬을 이용해서 벡터를 행렬 형식으로 바꾸는 실습을 진행 해보자. 그 후에 나중에 실제 렌더링 파이프라인을 만들 때에는 Simple Math 로 구현한다.

 

glm 을 사용해보는것이 좋은 이유는 나중에 불칸 같은 다른 라이브러리를 사용하게 될 때는 행렬의 메이저가 DirectX 와 다르기 때문에 glm 을 사용해 보는것이 나쁜것이 아니다.

 

먼저 glm 에서 행렬을 사용할려면 아래와 같은 헤더를 포함해야한다. 

#include <glm/gtc/matrix_inverse.hpp> // inverseTranspose
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/transform.hpp> // translate, rotate, scale

 

 

 

먼저 2x2 행렬로 연습을 해본다면 

mat2 를 만들면 되는데 여기서 초기화 할 때 인수가 없으면 단위 행렬로 생성 되어진다.

using namespace glm;

int main() {
    /*
     * glm 설치
     * vcpkg install glm:x64-windows
     */

    // glm::mat2 (2x2 column-major matrix)
    mat2 A = mat2(1, 2, 3, 4);

 

중요한건 glm 은 column-major 이기 때문에 아래와 같이 열 단위로 읽는 것이다.

 

 cout << to_string(A) << endl;
 // mat2x2((1.000000, 2.000000),
 //        (3.000000, 4.000000))
 // 우리가 생각하는 행렬 (column-major)
 // |1 3|
 // |2 4|
 // 1,2,3,4 순으로 cout 되어지지만 실제 우리가 생각해야할 행렬은 위와 같다. 
 //열이 메이저이기 때문이다.

 

 

그래서 전치하면 아래 처럼 2,3 만 자리가 바뀐다.

그리고 행렬을 배열의 인덱스처럼 가져올 수 있는데 이때 Column major 이기 때문에 열 단위로 가져오게 된다.

    cout << to_string(transpose(A)) << endl;
    // mat2x2((1.000000, 3.000000),
    //        (2.000000, 4.000000))

    cout << to_string(A[1]) << endl;
    // vec2(3.000000, 4.000000)

 

 

다음은 이제 그래픽스에서 사용하는 실제 mat4 행렬이다. 무식하게 인수를 넣은 것은 예시이기 때문이고 실제로 이렇게 코딩하지는 않는다.

    // glm::mat4 (column-major)
    mat4 m = mat4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
    // mat4x4((1.000000, 2.000000, 3.000000, 4.000000),
    //        (5.000000, 6.000000, 7.000000, 8.000000),
    //        (9.000000, 10.000000, 11.000000, 12.000000),
    //        (13.000000, 14.000000, 15.000000, 16.000000))
    // 우리가 생각하는 행렬 (column-major)
    // |1 5  9 13|
    // |2 6 10 14|
    // |3 7 11 15|
    // |4 8 12 16|

 

 

밑을 보면 이건 이동행렬을 mat4 로 만들었다. glm 과 dx 는 major 기준이 다르기 때문에 같은 이동 행렬이어도 표현식이 다르다. 정확하게 전치 되어진 모습으로 보여진다.

 mat4 translation = translate(vec3(1.0f, 2.0f, 3.0f));

 // 메모리에 어떤 순서로 저장되는지 확인
 // cout << "Transtion Matrix" << endl;
 // for (int i = 0; i < 16; i++) {
 //     cout << ((float *)&translation)[i] << " ";
 // }
 // cout << endl;
 // 출력결과: 1 0 0 0 0 1 0 0 0 0 1 0 1 2 3 1

 cout << to_string(translation) << endl;
 // mat4x4((1.000000, 0.000000, 0.000000, 0.000000),
 //        (0.000000, 1.000000, 0.000000, 0.000000),
 //        (0.000000, 0.000000, 1.000000, 0.000000),
 //        (1.000000, 2.000000, 3.000000, 1.000000))
 // Column-Major (GLM)
 // |1 0 0 1|
 // |0 1 0 2|
 // |0 0 1 3|
 // |0 0 0 1|
 // Row-Major (DX)
 // |1 0 0 0|
 // |0 1 0 0|
 // |0 0 1 0|
 // |1 2 3 1|

 

 

이동 시 vec4 의 끝자리 w 자리가 1 이어야만 적용된다. 당연히 myVector 는 4,5,6,0 이기 때문에 이동이 적용되지 않는 것을 알 수 있고, glm 에서는 행렬 * 벡터로 행렬 연산을 표현한다.

 

inverse 로 값을 -로 변경 가능하다.

 

rotate 로 회전 행렬을 표현 할 수 도 있는데 회전 행렬의 축이 vec3(1,0,0) 따라서 x 축만 회전을 주겠다는 의미이다.

그래서 아래와 같이 x축 부분만 cos sin 값이 보여진다.

 

회전 행렬의 전치 행렬은 역회전이다. 아래 회전행렬을 60도 회전을 의미하는데 전치시 -60가 된다. 따라서 두 행렬을 곱하면 

60 - 60 으로 0이 된다. 그러니까 회전 행렬의 역함수를 구할 때는 inverse 가 아닌 전치를 하는 것이 연산이 적다. 

 vec4 myPoint = vec4(4, 5, 6, 1);
 vec4 myVector = vec4(4, 5, 6, 0);

 cout << to_string(translation * myPoint) << endl;
 // vec4(5.000000, 7.000000, 9.000000, 1.000000)

 cout << to_string(translation * myVector) << endl;
 // vec4(4.000000, 5.000000, 6.000000, 0.000000)

 cout << to_string(glm::inverse(translation)) << endl;
 // mat4x4((1.000000, -0.000000, 0.000000, -0.000000),
 //        (-0.000000, 1.000000, -0.000000, 0.000000),
 //        (0.000000, -0.000000, 1.000000, -0.000000),
 //        (-1.000000, -2.000000, -3.000000, 1.000000))

 mat4 rotationX = rotate(glm::pi<float>() / 3.0f, vec3(1.0f, 0.0f, 0.0f));
 cout << to_string(rotationX) << endl;
 // mat4x4((1.000000, 0.000000, 0.000000, 0.000000),
 //        (0.000000, 0.500000, 0.866025, 0.000000),
 //        (0.000000, -0.866025, 0.500000, 0.000000),
 //        (0.000000, 0.000000, 0.000000, 1.000000))
 
  cout << to_string(glm::transpose(rotationX)) << endl;
 // mat4x4((1.000000, 0.000000, 0.000000, 0.000000),
 //        (0.000000, 0.500000, -0.866025, 0.000000),
 //        (0.000000, 0.866025, 0.500000, 0.000000),
 //        (0.000000, 0.000000, 0.000000, 1.000000))

 // 회전 행렬의 전치 행렬은 회전의 역행렬과 동일합니다.
 cout << to_string(glm::transpose(rotationX) * rotationX) << endl;
 // mat4x4((1.000000, 0.000000, 0.000000, 0.000000),
 //        (0.000000, 1.000000, 0.000000, 0.000000),
 //        (0.000000, 0.000000, 1.000000, 0.000000),
 //        (0.000000, 0.000000, 0.000000, 1.000000))

 

먼저 () 를 이용해 행렬끼리 먼저 계산한다. 그래야 연산량이 적다. srt 행렬 같은거

그리고 행렬 곱은 순서에 따라 값이 다르기에 srt 를 맞춰줘야한다.

 

마지막으로 Column 을 Row major 처럼 할 수 있다.

벡터를 연산 앞 순서로 보내고 뒤에 행렬을 전치해서 곱해주면 된다.

 // 순서 주의: 회전 후 이동
 cout << to_string((translation * rotationX) * vec4(1.0f, 0.0f, 0.0f, 1.0f))
      << endl;
 // vec4(2.000000, 2.000000, 3.000000, 1.000000)

 // 순서 주의: 이동 후 회전
 cout << to_string((rotationX * translation) * vec4(1.0f, 0.0f, 0.0f, 1.0f))
      << endl;
 // vec4(2.000000, -1.598076, 3.232051, 1.000000)

 // Transpose로 row-major로 변경가능
 cout << to_string(vec4(1.0f, 0.0f, 0.0f, 1.0f) *
                   glm::transpose(translation * rotationX))
      << endl;
 // vec4(2.000000, 2.000000, 3.000000, 1.000000)

 

 

 

 

 

 

'DX' 카테고리의 다른 글

DirectX Math  (0) 2025.06.06
조명, 노멀 벡터 변환 (GLM)  (0) 2025.06.06
좌표계 변환  (0) 2025.05.11
Affine Transformation  (0) 2025.05.11
선형 변환 스케일, 회전  (0) 2025.05.11