自定义Matrix4实现OpenGL变换故障:平移缩放失效仅旋转正常
嘿,我仔细看了你的代码和问题描述,发现几个关键问题导致除了旋转之外的功能都失效了,咱们一步步来解决:
你现在每帧都在同一个transform实例上调用Translate、Rotate、Scale,这相当于把每帧的变换无限制叠加,而且你的Rotate函数直接覆盖了矩阵的大部分内容,导致之前的平移/缩放效果被完全冲掉!
正确的做法是每帧重新从单位矩阵构建变换矩阵,而不是在旧矩阵上累加。另外,矩阵变换的顺序非常关键——因为矩阵乘法是右结合的,正确的顺序应该是:先缩放,再旋转,最后平移(对应代码里先调用Scale,再Rotate,最后Translate)。
修改后的OnUserUpdate应该是这样(注意g要改成类成员变量,不然每帧都会重置成1,旋转速度不对):
// 把g定义成类的成员变量,比如: // float g = 1.0f; void OnUserUpdate() override { // 每帧调用 Matrix4 transform = Matrix4(); // 每帧都从单位矩阵重新开始! float d = 5; // 变换顺序:缩放 → 旋转 → 平移 transform.Scale(Vector3(0.5f, 0.5f, 0.5f)); transform.Rotate(Vector3(0.0f, 0.0f, 1.0f), ToRadians(g)); transform.Translate(Vector3(d * DeltaTime, d * DeltaTime, 0.0f)); g += 0.01f; Shaders.SetMat4(shader, "transform", transform); }
你的几个变换函数都有问题,这是功能失效的直接原因:
1. 低级笔误:Translate和Scale里的z值错误
在Translate和Scale函数里,你都写了float z = v.y;,这明显是复制粘贴的笔误,应该改成float z = v.z;,不然z轴的变换会完全错误!
2. Rotate函数的致命错误:直接覆盖矩阵而非矩阵乘法
你现在的Rotate函数是直接把当前矩阵替换成旋转矩阵,而不是将旋转矩阵和当前矩阵做乘法叠加。这就导致之前的缩放、平移效果被直接覆盖,自然只有旋转生效。
解决方法是先实现矩阵乘法,再用乘法来叠加旋转:
首先给Matrix4添加矩阵乘法函数(OpenGL用列主序,所以要按列主序计算):
// 矩阵乘法:this = this × other(右乘变换矩阵,符合OpenGL的变换逻辑) Matrix4& Multiply(const Matrix4& other) { float temp[16]; // 按列主序计算矩阵乘法 for (int col = 0; col < 4; ++col) { for (int row = 0; row < 4; ++row) { temp[row + col * 4] = m[row + 0 * 4] * other.m[0 + col * 4] + m[row + 1 * 4] * other.m[1 + col * 4] + m[row + 2 * 4] * other.m[2 + col * 4] + m[row + 3 * 4] * other.m[3 + col * 4]; } } Set(temp); return *this; }
然后修改Rotate函数,先构造旋转矩阵,再和当前矩阵相乘:
void Rotate(Vector3 axis, float rad) { // 注意这里参数是弧度,你已经用ToRadians转好了 // 先归一化旋转轴,防止非单位轴导致错误 float length = sqrt(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); if (length > 0.0001f) { // 避免除以0 axis.x /= length; axis.y /= length; axis.z /= length; } float c = cosf(rad); float s = sinf(rad); float oc = 1.0f - c; // 构造列主序的旋转矩阵 Matrix4 rot; rot.m[0] = c + axis.x*axis.x*oc; rot.m[1] = axis.y*axis.x*oc + axis.z*s; rot.m[2] = axis.z*axis.x*oc - axis.y*s; rot.m[3] = 0.0f; rot.m[4] = axis.x*axis.y*oc - axis.z*s; rot.m[5] = c + axis.y*axis.y*oc; rot.m[6] = axis.z*axis.y*oc + axis.x*s; rot.m[7] = 0.0f; rot.m[8] = axis.x*axis.z*oc + axis.y*s; rot.m[9] = axis.y*axis.z*oc - axis.x*s; rot.m[10] = c + axis.z*axis.z*oc; rot.m[11] = 0.0f; rot.m[12] = 0.0f; rot.m[13] = 0.0f; rot.m[14] = 0.0f; rot.m[15] = 1.0f; // 将当前矩阵与旋转矩阵相乘,叠加变换 Multiply(rot); }
3. Translate和Scale函数的错误实现
你现在的Translate和Scale函数的计算逻辑不符合矩阵变换的规则,正确的做法是构造对应的变换矩阵,再通过乘法叠加:
修改后的Translate:
void Translate(Vector3 v) { Matrix4 trans; // 列主序的平移矩阵,第四列存储平移量 trans.m[12] = v.x; trans.m[13] = v.y; trans.m[14] = v.z; // 其余元素保持单位矩阵的默认值 Multiply(trans); }
修改后的Scale:
void Scale(Vector3 v) { Matrix4 scaleMat; // 列主序的缩放矩阵,对角元素存储缩放因子 scaleMat.m[0] = v.x; scaleMat.m[5] = v.y; scaleMat.m[10] = v.z; // 其余元素保持单位矩阵的默认值 Multiply(scaleMat); }
矩阵乘法是右结合的,对于顶点v,最终的变换公式是:v' = 平移矩阵 × 旋转矩阵 × 缩放矩阵 × v
如果顺序反了,比如先平移再旋转,那么平移后的点会绕原点旋转,导致你的矩形绕原点转圈,而不是自己旋转着移动到角落。你之前调整顺序没用,就是因为Rotate函数直接覆盖了矩阵,顺序根本没起到作用。
- 确保
PI是正确的浮点值定义,比如#define PI 3.1415926535f,不然ToRadians的转换会出错。 - 确认你的
Vector3构造函数能正确初始化x、y、z三个分量。 - 你的
Shaders.SetMat4应该是按列主序传递矩阵的,这和我们的实现一致,符合OpenGL的要求。
把这些修改完之后,你的矩形应该就能同时实现缩放、逆时针旋转,并且向角落移动的效果了!
内容的提问来源于stack exchange,提问作者user10692448

