在 C++ 中使用您自己的矩阵 class 进行 OpenGL 对象旋转

OpenGL Object rotation using your own Matrix class in C++

我正在研究 OpenGL,我决定做的一件事是创建我自己的矩阵 class,而不是使用 glm 的矩阵。 Matrix class 有平移、旋转和缩放对象的方法,写在下面:

Matrix4 Matrix4::translate(Matrix4& matrix, Vector3& translation)
{
     Vector4 result(translation, 1.0f);

     result.multiply(matrix);

     matrix.mElements[3 * 4 + 0] = result.x;
     matrix.mElements[3 * 4 + 1] = result.y;
     matrix.mElements[3 * 4 + 2] = result.z;

     return matrix; 
}

Matrix4 Matrix4::rotate(Matrix4& matrix, float angle, Vector3& axis)
{
    if (axis.x == 0 && axis.y == 0 && axis.z == 0)
        return matrix;

    float r = angle;
    float s = sin(r);
    float c = cos(r);
    float omc = 1.0f - cos(r);

    float x = axis.x;
    float y = axis.y;
    float z = axis.z;

    matrix.mElements[0 + 0 * 4] = c + x * x * omc;
    matrix.mElements[1 + 0 * 4] = x * y * omc - z * s;
    matrix.mElements[2 + 0 * 4] = z * x * omc + y * s;

    matrix.mElements[0 + 1 * 4] = x * y * omc + z * s;
    matrix.mElements[1 + 1 * 4] = c + y * y * omc;
    matrix.mElements[2 + 1 * 4] = z * y * omc - x * s;

    matrix.mElements[0 + 2 * 4] = x * z * omc - y * s;
    matrix.mElements[1 + 2 * 4] = y * z * omc + x * s;
    matrix.mElements[2 + 2 * 4] = c + z * z * omc;

    return matrix;
}

Matrix4 Matrix4::scale(Matrix4& matrix, Vector3& scaler)
{

    matrix.mElements[0 + 0 * 4] *= scaler.x;
    matrix.mElements[1 + 0 * 4] *= scaler.x;
    matrix.mElements[2 + 0 * 4] *= scaler.x;

    matrix.mElements[0 + 1 * 4] *= scaler.y;
    matrix.mElements[1 + 1 * 4] *= scaler.y;
    matrix.mElements[2 + 1 * 4] *= scaler.y;

    matrix.mElements[0 + 2 * 4] *= scaler.z;
    matrix.mElements[1 + 2 * 4] *= scaler.z;
    matrix.mElements[2 + 2 * 4] *= scaler.z;

    matrix.mElements[3 + 3 * 4] = 1;

    return matrix;
}

当我在 while 循环中调用平移、旋转和缩放方法时(按此特定顺序),它会执行我想要的操作,即平移对象,然后围绕其本地原点旋转它并缩放它。但是,当我想切换顺序时,我先调用旋转然后再调用翻译,我希望它这样做:

但我的代码不会那样做。相反,它这样做:

我该怎么做才能让我的对象只围绕屏幕中心旋转,而不是围绕它的本地原点旋转? 我唯一的猜测是我在转换后的矩阵上添加旋转计算时做错了,但我仍然不知道它是什么。

编辑:我需要指出的一件事是,如果我省略了旋转方法,我只处理平移和缩放,他们会按照我的期望进行操作,首先是平移,然后是旋转,然后是首先旋转,翻译二阶。

编辑 2:这是我在 while 循环中调用这些函数的方式。

Matrix4 trans = Matrix4(1.0f);
trans = Matrix4::rotate(trans, (float)glfwGetTime(), Vector3(0.0f, 0.0f, 1.0f));
trans = Matrix4::translate(trans, Vector3(0.5f, -0.5f, 0.0f));
trans = Matrix4::scale(trans, Vector3(0.5f, 0.5f, 1.0f));

shader.setUniformMatrix4f("uTransform", trans);

函数 rotate() 没有执行实际的旋转。只生成部分旋转矩阵,覆盖原矩阵
需要构造一个完整的和原矩阵相乘

Matrix4 Matrix4::rotate(const Matrix4& matrix, float angle, const Vector3& axis)
{
    if (axis.x == 0 && axis.y == 0 && axis.z == 0)
        return matrix;

    float r = angle;
    float s = sin(r);
    float c = cos(r);
    float omc = 1.0f - cos(r);

    float x = axis.x;
    float y = axis.y;
    float z = axis.z;

    Matrix4 r;

    r.mElements[0 + 0 * 4] = c + x * x * omc;
    r.mElements[1 + 0 * 4] = x * y * omc - z * s;
    r.mElements[2 + 0 * 4] = z * x * omc + y * s;
    r.mElements[3 + 0 * 4] = 0;

    r.mElements[0 + 1 * 4] = x * y * omc + z * s;
    r.mElements[1 + 1 * 4] = c + y * y * omc;
    r.mElements[2 + 1 * 4] = z * y * omc - x * s;
    r.mElements[3 + 1 * 4] = 0;

    r.mElements[0 + 2 * 4] = x * z * omc - y * s;
    r.mElements[1 + 2 * 4] = y * z * omc + x * s;
    r.mElements[2 + 2 * 4] = c + z * z * omc;
    r.mElements[3 + 2 * 4] = 0;

    r.mElements[0 + 3 * 4] = 0;
    r.mElements[1 + 3 * 4] = 0;
    r.mElements[2 + 3 * 4] = 0;
    r.mElements[3 + 3 * 4] = 1;

    return r * matrix;
}

您必须通过矩阵乘法连接矩阵。

矩阵乘法 C = A * B 的工作原理如下:

Matrix4x4 A, B, C;

// C = A * B
for ( int k = 0; k < 4; ++ k )
    for ( int j = 0; j < 4; ++ j )
        C[k][j] = A[0][j] * B[k][0] + A[1][j] * B[k][1] + A[2][j] * B[k][2] +  A[3][j] * B[k][3];

我建议像这样创建指定矩阵 class:

#include <array>

class Matrix4
{
public:

    std::array<float, 16> mElements{
      1, 0, 0, 0, 
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1 };

    const float * dataPtr( void ) const { return mElements.data(); }

    Matrix4 & multiply( const Matrix4 &mat );
    Matrix4 & translate( const Vector3 &translation );
    Matrix4 & scale( const Vector3 &scaler );
    Matrix4 & rotate( float angle, const Vector3 &axis );
};

实现矩阵乘法。请注意,您必须将结果存储在缓冲区中。 如果您将结果直接写回矩阵成员,那么您将更改元素,稍后将在嵌套循环中再次读取这些元素,结果将不正确:

Matrix4& Matrix4::multiply( const Matrix4 &mat )
{
    // multiply the existing matrix by the new and store the result in a buffer
    const float           *A = dataPtr();
    const float           *B = mat.dataPtr();
    std::array<float, 16>  C;

    for ( int k = 0; k < 4; ++ k ) {
        for ( int j = 0; j < 4; ++ j ) {
            C[k*4+j] =
                A[0*4+j] * B[k*4+0] +
                A[1*4+j] * B[k*4+1] +
                A[2*4+j] * B[k*4+2] +
                A[3*4+j] * B[k*4+3];
        }
    }

    // copy the buffer to the attribute
    mElements = C;

    return *this;
}

像这样调整平移、旋转和缩放的方法:

Matrix4 & Matrix4::translate( const Vector3 &translation )
{
     float x = translation.x;
     float y = translation.y;
     float z = translation.z;

     Matrix4 transMat;
     transMat.mElements = {
          1.0f, 0.0f, 0.0f, 0.0f,
          0.0f, 1.0f, 0.0f, 0.0f,
          0.0f, 0.0f, 1.0f, 0.0f,
          x,    y,    z,    1.0f };

     return multiply(transMat);
}

Matrix4 & Matrix4::rotate( float angle, const Vector3 &axis )
{
    float x = axis.x;
    float y = axis.y;
    float z = axis.z;

    float c = cos(angle);
    float s = sin(angle);

    Matrix4 rotationMat;
    rotationMat.mElements = {
       x*x*(1.0f-c)+c,   x*y*(1.0f-c)-z*s, x*z*(1.0f-c)+y*s, 0.0f,
       y*x*(1.0f-c)+z*s, y*y*(1.0f-c)+c,   y*z*(1.0f-c)-x*s, 0.0f,
       z*x*(1.0f-c)-y*s, z*y*(1.0f-c)+x*s, z*z*(1.0f-c)+c,   0.0f,
       0.0f,             0.0f,             0.0f,             1.0f };

    return multiply(rotationMat);
}

Matrix4 & Matrix4::scale( const Vector3 &scaler )
{
    float x = scaler.x;
    float y = scaler.y;
    float z = scaler.z;

    Matrix4 scaleMat;
    scaleMat.mElements = {
        x,    0.0f, 0.0f, 0.0f,
        0.0f, y,    0.0f, 0.0f,
        0.0f, 0.0f, z,    0.0f,
        0.0f, 0.0f, 0.0f, 1.0f };

    return multiply(scaleMat);
}

如果你像这样使用矩阵class,

float   angle_radians = ....;
Vector3 scaleVec{ 0.2f, 0.2f, 0.2f };
Vector3 transVec{ 0.3f, 0.3f, 0.0f };
Vector3 rotateVec{ 0.0f, 0.0f, 1.0f };

Matrix4 model;
model.rotate( angle_rad, rotateVec );
model.translate( transVec );
model.scale( scaleVec );

那么结果会是这样的: