드디어 glm
라이브러리를 완전히 대체할 sglm
라이브러리를 개발하고 적용했다. 이로써 SCOP 프로젝트에서 사용하던 모든 외부 라이브러리가 대체되어 직접 구현한 코드로만 구성되었다.
하지만 이를 구현하는 과정에서 몇 가지 문제가 있었다. 특히, 행렬 변환 시 OpenGL의 열 우선 저장 방식과 3D 그래픽에서의 다양한 변환들이 어려움을 줬다. 이 포스트에서는 이러한 문제를 해결한 경험과 함께 sglm
의 주요 변환 함수인 translate
, lookAt
, perspective
의 의미와 역할에 대해 서술하려 한다.
행렬 저장 방식에서의 문제
행렬 변환을 구현하는 과정에서 가장 어려웠던 부분은 행렬이 OpenGL에서는 열 우선(column-major) 방식으로 저장된다는 것이었다. 처음에는 일반적인 수학적인 방식인 행 우선(row-major) 방식으로 구현했기 때문에, 변환 시 렌더링이 제대로 그려지지 않거나 이동, 회전이 이상하게 동작하지 않았다. 이를 해결하기 위해 행렬의 구조와 데이터 저장 방식을 올바르게 이해하고 수정하는 과정이 필요했다. 이 경험은 3D 수학과 그래픽스의 차이점에 대해 많은 것을 깨닫게 해주었다.
변환 함수들: translate
, lookAt
, perspective
sglm
의 주요 변환 함수를 살펴보자. translate
, lookAt
, perspective
등은 그래픽스에서 필수적인 변환을 담당하며, 각각의 의미와 구현 방식은 다음과 같다.
1. translate
: 위치 이동 변환
translate
함수는 객체의 위치를 이동시키는 행렬을 생성한다. 주어진 3D 벡터 v
에 따라 x, y, z 방향으로 객체를 이동시키는 역할을 하며, 내부적으로는 단위 행렬에 변위 벡터를 더해 변환 행렬을 만든다.
mat4 translate(const mat4& m, const vec3& v) {
mat4 translateMat(1.0f); // 단위 행렬로 초기화
translateMat.m[3][0] = v.x;
translateMat.m[3][1] = v.y;
translateMat.m[3][2] = v.z;
// 원래 행렬에 곱하여 이동 변환 적용
return m * translateMat;
}
- 역할: 객체를 지정한 방향으로 이동시킨다.
- 구현: 단위 행렬에
v
를 적용하여 이동 변환 행렬을 생성하고 원래 행렬에 곱한다.
2. lookAt
: 카메라 뷰 변환
lookAt
함수는 카메라가 특정 위치를 바라보는 뷰 행렬을 생성한다. 카메라의 위치(eye
), 카메라가 바라보는 대상(center
), 그리고 카메라의 위쪽 방향(up
)을 인자로 받아 적절한 뷰 변환 행렬을 만들어낸다.
mat4 lookAt(const vec3& eye, const vec3& center, const vec3& up) {
vec3 f = (center - eye).normalize();
vec3 s = cross(f, up).normalize();
vec3 u = cross(s, f);
mat4 result(1.0f); // 단위 행렬로 초기화
result.m[0][0] = s.x;
result.m[0][1] = s.y;
result.m[0][2] = s.z;
result.m[1][0] = u.x;
result.m[1][1] = u.y;
result.m[1][2] = u.z;
result.m[2][0] = -f.x;
result.m[2][1] = -f.y;
result.m[2][2] = -f.z;
result.m[3][0] = -dot(s, eye);
result.m[3][1] = -dot(u, eye);
result.m[3][2] = dot(f, eye);
return result;
}
- 역할: 카메라의 위치와 방향에 따라 적절한 뷰 행렬을 생성한다.
- 구현:
eye
와center
의 벡터 차이로 카메라의 앞쪽 방향(f
)을 구하고,up
벡터와의 외적을 통해 오른쪽 방향(s
)과 위쪽 방향(u
)을 계산한다.
3. perspective
: 원근 투영 변환
perspective
함수는 3D 공간의 객체를 카메라를 통해 화면에 투영할 때 사용되는 원근 투영 행렬을 생성한다. 화면 비율(aspect
), 시야각(fovy
), 근거리(near
), 원거리(far
)의 인자로 투영 행렬을 만들어낸다.
mat4 perspective(float fovy, float aspect, float near, float far) {
float f = 1.0f / tan(fovy / 2.0f);
mat4 result(0.0f); // 모든 원소를 0으로 초기화
result.m[0][0] = f / aspect;
result.m[1][1] = f;
result.m[2][2] = (far + near) / (near - far);
result.m[2][3] = -1.0f;
result.m[3][2] = (2.0f * far * near) / (near - far);
return result;
}
- 역할: 시야각과 화면 비율 등을 바탕으로 원근 투영을 계산한다.
- 구현:
fovy
와aspect
를 사용해 원근 변환을 적용하며, 투영 행렬의 요소를 계산하여 3D 객체가 카메라에 투영되는 방식을 정의한다.
vec3 클래스
sglm
라이브러리에서 glm
의 다양한 벡터들을 대체하기 위해 직접 구현한 vec3
클래스는 3D 공간에서 벡터 연산을 지원한다.
class vec3 {
public:
float x, y, z;
vec3() : x(0), y(0), z(0) {}
vec3(float s) : x(s), y(s), z(s) {}
vec3(float x, float y, float z) : x(x), y(y), z(z) {}
// 벡터 덧셈
vec3 operator+(const vec3& v) const {
return vec3(x + v.x, y + v.y, z + v.z);
}
// 벡터 뺄셈
vec3 operator-(const vec3& v) const {
return vec3(x - v.x, y - v.y, z - v.z);
}
// 스칼라 곱셈
vec3 operator*(float s) const {
return vec3(x * s, y * s, z * s);
}
// 벡터의 길이 계산
float length() const {
return sqrt(x * x + y * y + z * z);
}
// 벡터 정규화
vec3 normalize() const {
float len = length();
if (len == 0) return *this;
return vec3(x / len, y / len, z / len);
}
};
vec3
는 3D 공간에서 벡터 연산을 지원하며, 덧셈, 뺄셈, 스칼라 곱셈 등의 기본 연산을 제공한다.- 벡터의 길이를 계산하거나 정규화하는 기능도 포함되어 있어 그래픽스 연산 시 편리하게 사용된다.
이렇게 sglm
라이브러리의 벡터 클래스를 구현함으로써 glm
을 대체했으며, 각 벡터는 2D, 3D, 4D 벡터 연산을 지원하도록 만들어졌다.
이와 같이 vec2
, vec4
, mat4
클래스도 구현하여 다양한 벡터 및 행렬 연산을 지원할 수 있도록 확장했다.
'Computer Graphics > SCOP' 카테고리의 다른 글
SCOP - 7. 평가 피드백 (0) | 2024.10.09 |
---|---|
SCOP - 5. stb 대체하는 bmp 로더 만들기 (0) | 2024.09.29 |
SCOP - 4. assimp 대체 라이브러리 만들기 (1) (0) | 2024.09.29 |
SCOP - 3. assimp 대체 라이브러리 만들기 (0) (0) | 2024.09.29 |
SCOP - 2. 랜덤한 vertex 색 적용 및 texture 적용 (0) | 2024.09.25 |