在 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 );
那么结果会是这样的:
我正在研究 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 );
那么结果会是这样的: