수색…


행렬 소개

OpenGL이나 다른 그래픽 API로 프로그래밍 할 때 수학에서 그다지 좋지 않을 때 벽돌 벽을 칠 것입니다. 여기 예제 코드를 사용하여 3D 오브젝트로 움직임 / 스케일링 및 다른 많은 멋진 것들을 얻을 수있는 방법을 설명 할 것입니다.

실제 상황을 생각해 봅시다. OpenGL에서 멋진 (3 차원) 큐브를 만들었 으면 어떤 방향 으로든 옮기고 싶습니다.

glUseProgram(cubeProgram)
glBindVertexArray(cubeVAO)
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)

Unity3d와 같은 게임 엔진에서 이것은 쉬울 것입니다. transform.Translate ()를 호출하면 끝나고 OpenGL에는 수학 라이브러리가 포함되지 않습니다.

좋은 수학 라이브러리는 glm 이지만, 필자는 모든 중요한 수학적 방법을 코딩 할 것이다.

먼저 우리는 OpenGL의 3d 객체가 많은 정보를 가지고 있다는 것을 이해해야합니다. 서로 의존하는 많은 변수가 있습니다. 이 모든 변수를 관리하는 현명한 방법은 행렬을 사용하는 것입니다.

행렬은 열과 행으로 작성된 변수의 모음입니다. 행렬은 1x1, 2x4 또는 임의의 수입니다.

[1|2|3]
[4|5|6]
[7|8|9] //A 3x3 matrix

당신은 그들과 함께 정말 멋진 것들을 할 수 있습니다 ...하지만 그들은 어떻게 내 큐브를 움직이는 나를 도울 수 있습니까? 이것을 실제로 이해하기 위해서는 우선 여러 가지를 알아야합니다.

  • 어떻게 한 위치에서 행렬을 만드나요?
  • 행렬을 어떻게 변환합니까?
  • OpenGL에 어떻게 전달합니까?

중요한 모든 행렬 데이터와 메소드를 포함하는 클래스를 만들어 봅시다 (C ++로 작성)

template<typename T>
//Very simple vector containing 4 variables
struct Vector4{
    T x, y, z, w;
    Vector4(T x, T y, T z, T w) : x(x), y(y), z(z), w(w){}
    Vector4(){}

    Vector4<T>& operator=(Vector4<T> other){
        this->x = other.x;
        this->y = other.y;
        this->z = other.z;
        this->w = other.w;
        return *this;
    }
}

template<typename T>
struct Matrix4x4{
     /*!
     *  You see there are columns and rows like this
     */
    Vector4<T> row1,row2,row3,row4;

    /*!
     *  Initializes the matrix with a identity matrix. (all zeroes except the ones diagonal)
     */
    Matrix4x4(){
        row1 = Vector4<T>(1,0,0,0);
        row2 = Vector4<T>(0,1,0,0);
        row3 = Vector4<T>(0,0,1,0);
        row4 = Vector4<T>(0,0,0,1);
    }

    static Matrix4x4<T> identityMatrix(){
        return Matrix4x4<T>(
                        Vector4<T>(1,0,0,0),
                        Vector4<T>(0,1,0,0),
                        Vector4<T>(0,0,1,0),
                        Vector4<T>(0,0,0,1));
    }



    Matrix4x4(const Matrix4x4<T>& other){
        this->row1 = other.row1;
        this->row2 = other.row2;
        this->row3 = other.row3;
        this->row4 = other.row4;
    }

    Matrix4x4(Vector4<T> r1, Vector4<T> r2, Vector4<T> r3, Vector4<T> r4){
        this->row1 = r1;
        this->row2 = r2;
        this->row3 = r3;
        this->row4 = r4;
    }

      /*!
     *  Get all the data in an Vector
     *  @return rawData The vector with all the row data
     */
    std::vector<T> getRawData() const{
        return{
            row1.x,row1.y,row1.z,row1.w,
            row2.x,row2.y,row2.z,row2.w,
            row3.x,row3.y,row3.z,row3.w,
            row4.x,row4.y,row4.z,row4.w
        };
    }

}

먼저 4x4 행렬의 기본 생성자에서 매우 특이한 점을 발견하게됩니다. 전화가 왔을 때 모두 0으로 시작하지는 않지만 다음과 같습니다.

[1|0|0|0]
[0|1|0|0]
[0|0|1|0]
[0|0|0|1] //A identity 4 by 4 matrix

모든 행렬은 대각선에있는 행렬로 시작해야합니다. (왜냐하면>. <)

좋아, 서사시 큐브에 4 x 4 매트릭스를 선언합시다.

glUseProgram(cubeProgram)
Matrix4x4<float> position;
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, cubeData);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)

이제 우리는 실제로 우리가 마침내 약간의 수학을 시작할 수있는 모든 변수를가집니다! 번역을 해봅시다. Unity3d에서 프로그래밍했다면 Transform.Translate 함수를 기억할 것입니다. 우리 자신의 행렬 클래스에서 그것을 구현해 보겠습니다.

 /*!
 *  Translates the matrix to
 *  @param vector, The vector you wish to translate to
 */
static Matrix4x4<T> translate(Matrix4x4<T> mat, T x, T y, T z){
    Matrix4x4<T> result(mat);
    result.row1.w += x;
    result.row2.w += y;
    result.row3.w += z;
    return result;
}

이것은 큐브를 움직이는데 필요한 모든 수학입니다 (회전이 아니거나 스케일링이 아닙니다). 그것은 모든 각도에서 작동합니다. 실제 시나리오에서이를 구현합시다.

glUseProgram(cubeProgram)
Matrix4x4<float> position;
position = Matrix4x4<float>::translate(position, 1,0,0);
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, &position.getRawData()[0]);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)

우리의 쉐이더는 우리의 놀라운 매트릭스를 사용해야합니다.

#version 410 core
uniform mat4 mv_matrix;
layout(location = 0) in vec4 position;

void main(void){
    gl_Position = v_matrix * position;
}

그리고 그것은 작동해야합니다 ....하지만 이미 우리 프로그램에 버그가있는 것 같습니다. z 축을 따라 움직일 때, 물체는 곧바로 사라지는 것처럼 보입니다. 이것은 우리가 투영 행렬을 가지고 있지 않기 때문입니다. 이 버그를 해결하려면 두 가지를 알아야합니다.

  1. 투영 행렬은 어떻게 생겼습니까?
  2. 위치 매트릭스와 어떻게 조합 할 수 있습니까?

그럼 우리는 원근법을 만들 수 있습니다 (우리는 결국 3 차원을 사용합니다).

template<typename T>
Matrix4x4<T> perspective(T fovy, T aspect, T near, T far){
    
    T q = 1.0f / tan((0.5f * fovy) * (3.14 / 180));
    T A = q / aspect;
    T B = (near + far) / (near - far);
    T C = (2.0f * near * far) / (near - far);
    
    return Matrix4x4<T>(
        Vector4<T>(A,0,0,0),
        Vector4<T>(0,q,0,0),
        Vector4<T>(0,0,B,-1),
        Vector4<T>(0,0,C,0));
}

그것은 무서운 것처럼 보이지만,이 방법은 거리 (그리고 얼마나 가까운 거리)와 시야각을 얼마나 멀리 바라보기위한 매트릭스를 실제로 계산합니다.

이제 투영 행렬과 위치 행렬이 있습니다. 어떻게 결합할까요? 재미있는 점은 실제로 두 행렬을 서로 곱할 수 있다는 것입니다.

/*!
 *  Multiplies a matrix with an other matrix
 *  @param other, the matrix you wish to multiply with
 */
static Matrix4x4<T> multiply(const Matrix4x4<T>& first,const Matrix4x4<T>& other){
    //generate temporary matrix
    Matrix4x4<T> result;
    //Row 1
    result.row1.x = first.row1.x * other.row1.x + first.row1.y * other.row2.x + first.row1.z * other.row3.x + first.row1.w * other.row4.x;
    result.row1.y = first.row1.x * other.row1.y + first.row1.y * other.row2.y + first.row1.z * other.row3.y + first.row1.w * other.row4.y;
    result.row1.z = first.row1.x * other.row1.z + first.row1.y * other.row2.z + first.row1.z * other.row3.z + first.row1.w * other.row4.z;
    result.row1.w = first.row1.x * other.row1.w + first.row1.y * other.row2.w + first.row1.z * other.row3.w + first.row1.w * other.row4.w;
    
    //Row2
    result.row2.x = first.row2.x * other.row1.x + first.row2.y * other.row2.x + first.row2.z * other.row3.x + first.row2.w * other.row4.x;
    result.row2.y = first.row2.x * other.row1.y + first.row2.y * other.row2.y + first.row2.z * other.row3.y + first.row2.w * other.row4.y;
    result.row2.z = first.row2.x * other.row1.z + first.row2.y * other.row2.z + first.row2.z * other.row3.z + first.row2.w * other.row4.z;
    result.row2.w = first.row2.x * other.row1.w + first.row2.y * other.row2.w + first.row2.z * other.row3.w + first.row2.w * other.row4.w;
    
    //Row3
    result.row3.x = first.row3.x * other.row1.x + first.row3.y * other.row2.x + first.row3.z * other.row3.x + first.row3.w * other.row4.x;
    result.row3.y = first.row3.x * other.row1.y + first.row3.y * other.row2.y + first.row3.z * other.row3.y + first.row3.w * other.row4.y;
    result.row3.z = first.row3.x * other.row1.z + first.row3.y * other.row2.z + first.row3.z * other.row3.z + first.row3.w * other.row4.z;
    result.row3.w = first.row3.x * other.row1.w + first.row3.y * other.row2.w + first.row3.z * other.row3.w + first.row3.w * other.row4.w;
    
    //Row4
    result.row4.x = first.row4.x * other.row1.x + first.row4.y * other.row2.x + first.row4.z * other.row3.x + first.row4.w * other.row4.x;
    result.row4.y = first.row4.x * other.row1.y + first.row4.y * other.row2.y + first.row4.z * other.row3.y + first.row4.w * other.row4.y;
    result.row4.z = first.row4.x * other.row1.z + first.row4.y * other.row2.z + first.row4.z * other.row3.z + first.row4.w * other.row4.z;
    result.row4.w = first.row4.x * other.row1.w + first.row4.y * other.row2.w + first.row4.z * other.row3.w + first.row4.w * other.row4.w;
    
    return result;
}

Ooef .. 실제로 더 많이 무서워 보이는 코드가 많습니다. 그것은 for 회 돌이에서 수행 될 수 있지만, 아마도 (아마도 실수로) 이것은 결코 매트릭스로 작업 한 적이없는 사람들에게 이것이 더 명확해질 것이라고 생각했습니다.

코드를보고 반복되는 패턴을 확인하십시오. 열에 행을 더하고 계속 추가하십시오 (모든 크기 행렬에 대해 동일 함).

* 행렬 곱셈은 일반적인 곱셈과 같지 않습니다. AXB! = B × A *

이제 우리는 어떻게 이것을 투영하여 우리의 실재 코드에 우리의 위치 매트릭스에 추가 할 수 있는지 알 것입니다 :

glUseProgram(cubeProgram)
Matrix4x4<float> position;
position = Matrix4x4<float>::translate(position, 1,0,0);
position = Matrix4x4<float>::multiply(Matrix<float>::perspective<float>(50, 1 , 0.1f, 100000.0f), position);
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, &position.getRawData()[0]);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)

이제 우리의 버그는 부숴지고 우리 큐브는 꽤 서사적입니다. 큐브의 크기를 조정하려면 수식이 다음과 같습니다.

 /*!
 *  Scales the matrix with given vector
 *  @param s The vector you wish to scale with
 */
static Matrix4x4<T> scale(const Matrix4x4<T>& mat, T x, T y, T z){
    Matrix4x4<T> tmp(mat);
    tmp.row1.x *= x;
    tmp.row2.y *= y;
    tmp.row3.z *= z;
    return tmp;
}

대각선 변수 만 조정하면됩니다.

회전의 경우 Quaternions를 자세히 살펴볼 필요가 있습니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow