如何在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
相关产品推荐
相关产品推荐

