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

如何在Qt C++ OpenGL ES 2.0中从变换矩阵提取缩放、旋转和平移

在Qt C++ OpenGL ES 2.0中提取变换矩阵的缩放、旋转、平移分量

核心原理说明

OpenGL ES 2.0和glMatrix库均采用列主序的4x4仿射变换矩阵,结构如下(索引从0开始):

[ Xx, Yx, Zx, Tx ]
[ Xy, Yy, Zy, Ty ]
[ Xz, Yz, Zz, Tz ]
[  0,  0,  0,  1 ]

其中:

  • Tx, Ty, Tz 是平移分量
  • 前三列[Xx,Xy,Xz]、[Yx,Yy,Yz]、[Zx,Zy,Zz]是包含缩放+旋转的轴向量,列向量的模长即为对应轴的缩放因子,归一化后得到纯旋转矩阵。

1. 提取平移分量

直接取矩阵第4列的前三个元素,Qt的QMatrix4x4可通过column(3)快速获取:

QVector3D extractTranslation(const QMatrix4x4& mat)
{
    return QVector3D(mat.column(3).x(), mat.column(3).y(), mat.column(3).z());
}

2. 提取缩放分量

计算前三列向量的模长,同时处理负缩放(镜像变换)的符号:

#include <QtMath>

QVector3D extractScale(const QMatrix4x4& mat)
{
    // 计算各轴的缩放模长
    float scaleX = mat.column(0).length();
    float scaleY = mat.column(1).length();
    float scaleZ = mat.column(2).length();

    // 处理负缩放的符号:通过矩阵行列式判断是否存在镜像变换
    const float det = mat.determinant();
    if (det < 0) {
        // 行列式为负说明有奇数次镜像,保留负缩放的符号
        scaleX = mat.column(0).x() < 0 ? -scaleX : scaleX;
        scaleY = mat.column(1).y() < 0 ? -scaleY : scaleY;
        scaleZ = mat.column(2).z() < 0 ? -scaleZ : scaleZ;
    } else {
        // 行列式为正,根据轴向量的原始方向保留符号
        scaleX = qSign(mat.column(0).x()) * scaleX;
        scaleY = qSign(mat.column(1).y()) * scaleY;
        scaleZ = qSign(mat.column(2).z()) * scaleZ;
    }

    return QVector3D(scaleX, scaleY, scaleZ);
}

3. 提取旋转分量

先去除缩放影响(归一化前三列),再将纯旋转矩阵转换为四元数(避免欧拉角的万向锁问题):

QQuaternion extractRotation(const QMatrix4x4& mat)
{
    QMatrix4x4 rotMat = mat;
    const QVector3D scale = extractScale(mat);

    // 归一化列向量,移除缩放分量
    if (!qFuzzyIsNull(scale.x()))
        rotMat.setColumn(0, rotMat.column(0) / scale.x());
    if (!qFuzzyIsNull(scale.y()))
        rotMat.setColumn(1, rotMat.column(1) / scale.y());
    if (!qFuzzyIsNull(scale.z()))
        rotMat.setColumn(2, rotMat.column(2) / scale.z());

    // 从正交旋转矩阵生成四元数
    return rotMat.toQuaternion();
}

4. 关键帧线性插值实现

对各分量分别插值后重新组合变换矩阵:

  • 缩放/平移:用线性插值(QVector3D::lerp)
  • 旋转:用球面线性插值(QQuaternion::slerp,避免万向锁)
QMatrix4x4 interpolateKeyframeTransform(const QMatrix4x4& startMat, const QMatrix4x4& endMat, float t)
{
    // 插值缩放
    const QVector3D startScale = extractScale(startMat);
    const QVector3D endScale = extractScale(endMat);
    const QVector3D interpolatedScale = QVector3D::lerp(startScale, endScale, t);

    // 插值旋转
    const QQuaternion startRot = extractRotation(startMat);
    const QQuaternion endRot = extractRotation(endMat);
    // 避免插值走最长路径
    QQuaternion interpolatedRot = QQuaternion::slerp(startRot, endRot, t);
    if (QQuaternion::dotProduct(startRot, endRot) < 0) {
        interpolatedRot = QQuaternion::slerp(startRot, -endRot, t);
    }

    // 插值平移
    const QVector3D startTrans = extractTranslation(startMat);
    const QVector3D endTrans = extractTranslation(endMat);
    const QVector3D interpolatedTrans = QVector3D::lerp(startTrans, endTrans, t);

    // 构建最终变换矩阵
    QMatrix4x4 result;
    result.scale(interpolatedScale);
    result.rotate(interpolatedRot);
    result.translate(interpolatedTrans);

    return result;
}

注意事项

  • 上述代码仅适用于仿射变换矩阵(最后一行固定为0,0,0,1),这是OpenGL ES 2.0动画场景的标准矩阵形式。
  • 如果矩阵包含剪切变换,上述提取方法失效(关键帧动画一般不会引入剪切)。

内容的提问来源于stack exchange,提问作者8Observer8

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 02:34:51