You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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);
}
二、Matrix4类的实现错误

你的几个变换函数都有问题,这是功能失效的直接原因:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:30:56