最近遇到一个问题,模型上面的一个骨骼因为模型缩放之后它的变换矩阵各种不对。搞得有点迷糊,所以这里记录一下
矩阵变换
已知下面这样的变换
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} MBoneLocal∗MLocal−>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是父子关系
圆柱体的变换矩阵
圆锥的变换矩阵
解除父子关系圆锥的坐标从缩放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);
测试结果:
模型缩放的错误想法
模型缩放之后,公式中的$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=MModel∗MR
但是,在代码里面这里明显不对,所以一直不理解!
把书翻了一遍,看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=MR∗MModel这样是对的。其实不完全是,这样写,是两次变换!但是为什么是对的,只是相当于乘了两个缩放而已 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=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=MBoneWorld∗MLocal−>World−1=MBoneWorld∗MModel−1
(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=MBoneLocal∗MModelNew
- 代码问题
这里面的一开始的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=MLocal1∗MBoneLocal∗MModel
之前写的一份功能生效的代码:
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=MLocal1∗MBoneLocalNew∗MModel
其中后面两个矩阵相乘相当于: 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=M1∗M2=S1R1T1∗S2R2T2,而代码里面是直接把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=M1∗M2=S1R1T1∗S2R2T2中的 T 1 ∗ S 2 T_1*S_2 T1∗S2做了,并且S_2是单位矩阵。所以呢?虽然大部分时候都是对的,但是如果模型原来的缩放不是单位矩阵,比如缩放0.5,你现在要放大1.5,那么就bug了
参考
[1]物惯(子到父节点)变换顺序原因和不同坐标系下的变换顺序详解
[2]为什么转换顺序非常重要