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 |