Computer Graphics/SCOP

SCOP - 6. glm 대체하는 sglm 만들기

surkim 2024. 10. 4. 10:47

드디어 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;
}
  • 역할: 카메라의 위치와 방향에 따라 적절한 뷰 행렬을 생성한다.
  • 구현: eyecenter의 벡터 차이로 카메라의 앞쪽 방향(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;
}
  • 역할: 시야각과 화면 비율 등을 바탕으로 원근 투영을 계산한다.
  • 구현: fovyaspect를 사용해 원근 변환을 적용하며, 투영 행렬의 요소를 계산하여 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 클래스도 구현하여 다양한 벡터 및 행렬 연산을 지원할 수 있도록 확장했다.