矩阵变换的一次释疑


最近遇到一个问题,模型上面的一个骨骼因为模型缩放之后它的变换矩阵各种不对。搞得有点迷糊,所以这里记录一下

矩阵变换

已知下面这样的变换

M B o n e L o c a l ∗ M L o c a l − > W o r l d = M B o n e W o r l d M_{BoneLocal} * M_{Local->World} = M_{BoneWorld} MBoneLocalMLocal>World=MBoneWorld

其中, M L o c a l − > W o r l d = M M o d e l M_{Local->World} = M_{Model} MLocal>World=MModel

  • 测试UE4

Cylinder和Cone是父子关系

圆柱体的变换矩阵
cylinder
圆锥的变换矩阵

cone_local
解除父子关系圆锥的坐标从缩放1.5前后如下
cone_world_1
cone_world_1.5
小结:

  • 可以看到子物体在世界坐标系的变换矩阵是有缩放信息,并且坐标也是缩放过后的
  • 写个简单的代码测试一下,也是这样的结果
    XMMATRIX xmModelScale = XMMatrixScaling(1.0f, 1.0f, 1.0f);
    XMMATRIX xmModelTrans = XMMatrixTranslation(0.0f, 100.0f, 100.0f);
    XMMATRIX xmModel = XMMatrixMultiply(xmModelScale, xmModelTrans);
    XMFLOAT4X4 f4x4Model;
    XMStoreFloat4x4(&f4x4Model, xmModel);

    XMMATRIX xmLocalTrans = XMMatrixTranslation(0.0f, 0.0f, 200.0f);
    XMMATRIX xmBone = XMMatrixMultiply(xmLocalTrans, xmModel);
    XMFLOAT4X4 f4x4Bone;
    XMStoreFloat4x4(&f4x4Bone, xmBone);

    xmModelScale = XMMatrixScaling(1.5f, 1.5f, 1.5f);
    xmModel = XMMatrixMultiply(xmModelScale, xmModelTrans);
    xmBone = XMMatrixMultiply(xmLocalTrans, xmModel);
    XMStoreFloat4x4(&f4x4Bone, xmBone);

测试结果:
test

模型缩放的错误想法

模型缩放之后,公式中的$M_{Local->World} $是带了缩放的。但是一开始的想法是把原来的矩阵乘以一个新缩放矩阵就可以了:
M M o d e l N e w = M M o d e l ∗ M R M_{Model_New} = M_{Model} * M_R MModelNew=MModelMR

但是,在代码里面这里明显不对,所以一直不理解!

把书翻了一遍,看DX11里面介绍组合变换是: M = S R T M = SRT M=SRT

新的理解如下:

  • M = S R T M=SRT M=SRT可以看出来变换矩阵是已经包含了缩放、旋转和平移。而且,顺序很重要,下一小节单独再分析下
  • 但是我上面的想法是相当于变换了两次,两个矩阵里面都有缩放、旋转、平移。**这样最重要的一点是:第二次以为的缩放会把前一个矩阵的位置信息给缩放了!**这也是我很疑惑为什么把缩放矩阵放在前面 M M o d e l N e w = M R ∗ M M o d e l M_{Model_New} = M_R*M_{Model} MModelNew=MRMModel这样是对的。其实不完全是,这样写,是两次变换!但是为什么是对的,只是相当于乘了两个缩放而已 M = S S R T M=SSRT M=SSRT,并且后面一个缩放矩阵刚好是单位矩阵这么巧罢了。
  • 所以,一般做法是拿到缩放、旋转和平移的分量,然后再构造新的矩阵
                    XMMATRIX matAnimActorWorld = XMMatrixTransformation(
                        g_XMZero,
                        g_XMIdentityR3,
                        g_XMOne,
                        g_XMZero,
                        XMLoadFloat4(&f4AnimActorRotation),
                        XMLoadFloat3(&f3AnimActorPosition)
                    );

旋转顺序

很多地方可以看到 M = S R T M=SRT M=SRT表示的是缩放->旋转->平移这样的顺序,这个顺序很重要,为社么?至于OpenGL里面的顺序是反的,只是矩阵的行列不一样,就不多说了

参考[2]提到了重要的原因是,这些变换是针对坐标系原点来的!

  • DX11里面的经典的图
    在这里插入图片描述
    图(a)先旋转再平移,图(b)先平移再选装

  • 自己测试

    XMMATRIX skullScale = XMMatrixScaling(0.5f, 0.5f, 0.5f);
    XMMATRIX skullOffset = XMMatrixTranslation(0.0f, 1.0f, 0.0f);
    XMStoreFloat4x4(&mSkullWorld, XMMatrixMultiply(skullScale, skullOffset));

M=RT
M=RT
M=TR:可以看到相对于原点缩放,先平移之后,原点缩一半就下降了一半
M=TR

问题解决

再来解决遇到的问题,已知模型缩放前的世界坐标系的变换矩阵 M M o d e l M_{Model} MModel,和骨骼的世界坐标系的变换矩阵 M B o n e W o r l d M_{BoneWorld} MBoneWorld,现在模型有缩放,如何求骨骼的新的世界坐标系的变换?

(1)求出Local矩阵

M B o n e L o c a l = M B o n e W o r l d ∗ M L o c a l − > W o r l d − 1 = M B o n e W o r l d ∗ M M o d e l − 1 M_{BoneLocal} = M_{BoneWorld} * {M_{Local->World}}^{-1} = M_{BoneWorld} * {M_Model}^{-1} MBoneLocal=MBoneWorldMLocal>World1=MBoneWorldMModel1

(2)重新构造缩放后的矩阵,把 M = S R T M=SRT M=SRT中的S修改成新的缩放因数

M M o d e l N e w = M M o d e l 重 新 构 造 , 把 缩 放 值 单 独 算 出 来 M_{ModelNew} = M_{Model}重新构造,把缩放值单独算出来 MModelNew=MModel

(3)用公式算出Bone在世界坐标系的变换矩阵

M B o n e W o r l d N e w = M B o n e L o c a l ∗ M M o d e l N e w M_{BoneWorldNew} = M_{BoneLocal} * M_{ModelNew} MBoneWorldNew=MBoneLocalMModelNew

  • 代码问题

这里面的一开始的matModelMatrix是Bone上面的零件在Bone上面的变换矩阵,公式就变成了: M W o r l d = M L o c a l 1 ∗ M B o n e L o c a l ∗ M M o d e l M_{World} = M_{Local1} * M_{BoneLocal} * M_{Model} MWorld=MLocal1MBoneLocalMModel
之前写的一份功能生效的代码:

                    XMMATRIX matWorldInverse = XMMatrixInverse(NULL, matAnimActorWorld);
                    XMMATRIX mat = XMLoadFloat4x4(&f4x4BoneMatrix);
                    mat = XMMatrixMultiply(mat, matWorldInverse);
                    XMStoreFloat4x4(&f4x4BoneMatrix, mat);

                    XMVECTOR xTrans, xScaling, xRotation;
                    XMFLOAT3 f3Scaling;
                    XMFLOAT4 f4Trans;
                    XMMatrixDecompose(&xScaling, &xRotation, &xTrans, XMLoadFloat4x4(&f4x4BoneMatrix));

                    XMStoreFloat4((XMFLOAT4*)&f4Trans, xTrans);
                    f4Trans.x = f4Trans.x * fRenderScale;
                    f4Trans.y = f4Trans.y * fRenderScale;
                    f4Trans.z = f4Trans.z * fRenderScale;

                    XMStoreFloat4x4(&f4x4BoneMatrix,
                        XMMatrixAffineTransformation(xScaling, g_XMZero, xRotation, XMLoadFloat4(&f4Trans)));
                    matModelMatrix = XMMatrixMultiply(matModelMatrix, XMMatrixMultiply(XMLoadFloat4x4(&f4x4BoneMatrix), matAnimActorWorld));
                    XMStoreFloat4x4(&f4x4BoneMatrix, matModelMatrix);

所以就是零件->Bone->世界坐标系:

M W o r l d = M L o c a l 1 ∗ M B o n e L o c a l N e w ∗ M M o d e l M_{World} = M_{Local1} * M_{BoneLocalNew} * M_{Model} MWorld=MLocal1MBoneLocalNewMModel

其中后面两个矩阵相乘相当于: M = M 1 ∗ M 2 = S 1 R 1 T 1 ∗ S 2 R 2 T 2 M = M1*M2=S_1R_1T_1 * S_2R_2T_2 M=M1M2=S1R1T1S2R2T2,而代码里面是直接把M1的位移缩放了,这样的结果就是最终的矩阵的坐标是对的,但是缩放信息没有带出来。正确的写法:

                    XMMatrixDecompose(&xScaling, &xRotation, &xTrans, matAnimActorWorld);
                    XMStoreFloat3((XMFLOAT3*)&f3Scaling, xScaling);
                    f3Scaling.x = f3Scaling.x * fRenderScale;
                    f3Scaling.y = f3Scaling.y * fRenderScale;
                    f3Scaling.z = f3Scaling.z * fRenderScale;
                    matAnimActorWorld = XMMatrixAffineTransformation(XMLoadFloat3(&f3Scaling), g_XMZero, xRotation, xTrans);
                    matModelMatrix = XMMatrixMultiply(matModelMatrix, XMMatrixMultiply(mat, matAnimActorWorld));
  • 另外一种写法的错误
                    XMVECTOR xTrans, xScaling, xRotation;
                    XMFLOAT3 f3Scaling;
                    XMFLOAT4 f4Trans;
                    XMMatrixDecompose(&xScaling, &xRotation, &xTrans, matBone);
                    XMStoreFloat3((XMFLOAT3*)&f3Scaling, xScaling);
                    f3Scaling.x = f3Scaling.x * m_fRenderScale;
                    f3Scaling.y = f3Scaling.y * m_fRenderScale;
                    f3Scaling.z = f3Scaling.z * m_fRenderScale;

                    XMStoreFloat4((XMFLOAT4*)&f4Trans, xTrans);
                    f4Trans.x = f4Trans.x * m_fRenderScale;
                    f4Trans.y = f4Trans.y * m_fRenderScale;
                    f4Trans.z = f4Trans.z * m_fRenderScale;

                    matBone = XMMatrixAffineTransformation(XMLoadFloat3(&f3Scaling), g_XMZero, xRotation, XMLoadFloat4(&f4Trans));

相当于把 M = M 1 ∗ M 2 = S 1 R 1 T 1 ∗ S 2 R 2 T 2 M = M1*M2=S_1R_1T_1 * S_2R_2T_2 M=M1M2=S1R1T1S2R2T2中的 T 1 ∗ S 2 T_1*S_2 T1S2做了,并且S_2是单位矩阵。所以呢?虽然大部分时候都是对的,但是如果模型原来的缩放不是单位矩阵,比如缩放0.5,你现在要放大1.5,那么就bug了

参考

[1]物惯(子到父节点)变换顺序原因和不同坐标系下的变换顺序详解

[2]为什么转换顺序非常重要

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值