Поиск…


Введение в матрицы

Когда вы программируете в OpenGL или любой другой графической апи, вы попадаете в кирпичную стену, когда вы не так хороши в математике. Здесь я объясню пример кода, как вы можете добиться движения / масштабирования и многих других интересных вещей с помощью своего 3D-объекта.

Давайте возьмем реальный случай жизни ... Вы сделали потрясающий (трехмерный) куб в OpenGL, и вы хотите переместить его в любом направлении.

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

В игровых машинах, таких как Unity3d, это было бы легко. Вы просто вызываете transform.Translate () и выполняете его, но OpenGL не включает математическую библиотеку.

Хорошая математическая библиотека - это glm, но для того, чтобы получить мою точку зрения, я буду кодировать все (важные) математические методы для вас.

Сначала мы должны понимать, что 3D-объект в OpenGL содержит много информации, существует много переменных, которые зависят друг от друга. Разумным способом управления всеми этими переменными является использование матриц.

Матрица представляет собой набор переменных, написанных в столбцах и строках. Матрица может быть 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
        };
    }

}

Сначала мы замечаем очень странную вещь в конструкторе по умолчанию матрицы 4 на 4. Когда он называется, он не запускает все на ноль, но вроде:

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

Все матрицы должны начинаться с диагоналей. (только потому, что>. <)

Хорошо, давайте объявим в нашем эпическом кубе матрицу размером 4 на 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. Как мы можем объединить его с нашей матрицей позиций?

Ну, мы можем сделать перспективу (мы все-таки используем три измерения). Код

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 x 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