有时候会需要将 Unity 里的节点 position, rotation 和 scale 数值和 maya 的原始数据做对比。这时候就需要进行坐标系转换。本文提供了一种从 unity transform 数据获取 maya local transform matrix 的方式。
通常这已足够,若需进一步还原面板数据需要拆解 maya transform matrix,需要参考这个 链接 ,很复杂,一般用不到。
从下图可以看到,这两个坐标系实际上只需要进行一个 x 轴的反向,这是左右手系转换的一种情况。但是,如果在maya中xyz轴的旋转序和 unity 默认的 zxy 不同时,就不仅是 x 轴反向这样简单了。对于这一类坐标系转换问题,可以用一种通用的解法去求解。

首先重新铺垫一些基础概念。
(1)旋转矩阵是绕一个不变坐标系的轴进行旋转的(extrinsic rotation)。
关于 intrinsic/extrinsic rotation 的差异可以参考这篇文章 ([doc])。(https://www.cnblogs.com/hysteresis/p/13892942.html)
但如果多个旋转矩阵相乘时,由于后者是在前者变换的基础上进行变换,因此最终是一个 intrinsic rotation。欧拉角是在变动的轴上计算的。
(2)对于一个旋转矩阵,其旋转的方向是轴指向观察者的时候的逆时针方向。
wiki 对于二维的情况说右手系是逆时针,左手系是顺时针旋转。其实可以从另一个角度考虑,绕轴永远是逆时针,只是右手系和左手系的 z 轴反向了。
(3)对于 unity,其旋转序为 zxy。矩阵相乘顺序为左乘(右侧开始先结合,注意矩阵乘法不满足交换律,有些引擎的结合顺序是不同的),构建旋转矩阵的顺序为 Rz * Rx * Ry。
//验证手算的 LocalTransformMatrix 是否等于 transform.localToWorldMatrix * transform.parent.worldToLocalMatrix
private void CheckLocalTransformMatrix()
{
Matrix4x4 tsfm_mat = transform.parent.worldToLocalMatrix * transform.localToWorldMatrix;
var t_mat = Matrix4x4.TRS(transform.localPosition, Quaternion.identity, Vector3.one);
var s_mat = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, transform.localScale);
var rot_mat_check = GenerateRotationMatrix(transform.localRotation.eulerAngles, 2, 0, 1); //zxy order
Matrix4x4 tsfm_mat_rebuild = t_mat * rot_mat_check * s_mat;
PrintMatrix(tsfm_mat);
Debug.Log("CheckLocalTransformMatrix");
PrintMatrix(tsfm_mat_rebuild);
}
//构建完整的旋转矩阵
//xyz : euler angles in degree
//order0, order1, order2 : axis number, x=0, y=1, z=2
//exp. "zxy" order : order0=2, order1=0, order2=1
public Matrix4x4 GenerateRotationMatrix(Vector3 xyz, int order0, int order1, int order2)
{
var m0 = GenerateRotationMatrixSingle(xyz[order0], order0);
var m1 = GenerateRotationMatrixSingle(xyz[order1], order1);
var m2 = GenerateRotationMatrixSingle(xyz[order2], order2);
return m2 * m1 * m0;
}
//构建绕单一轴的旋转矩阵,与 wiki 一致。
//https://en.wikipedia.org/wiki/Rotation_matrix
//axis : x=0, y=1, z=2
//theta : degree
public Matrix4x4 GenerateRotationMatrixSingle(float theta, int axis)
{
Matrix4x4 mat = new Matrix4x4();
if (axis < 0 || axis > 2)
{
Debug.LogError("axis must within 0-2");
return mat;
}
float theta_rad = theta * Mathf.PI / 180;
mat[axis, axis] = 1;
mat[3, 3] = 1;
float sin_theta = Mathf.Sin(theta_rad);
float cos_theta = Mathf.Cos(theta_rad);
if (axis == 0)
{
mat[1, 1] = cos_theta;
mat[1, 2] = -sin_theta;
mat[2, 1] = sin_theta;
mat[2, 2] = cos_theta;
}
else if (axis == 1)
{
mat[0, 0] = cos_theta;
mat[0, 2] = sin_theta;
mat[2, 0] = -sin_theta;
mat[2, 2] = cos_theta;
}
else if (axis == 2)
{
mat[0, 0] = cos_theta;
mat[0, 1] = -sin_theta;
mat[1, 0] = sin_theta;
mat[1, 1] = cos_theta;
}
return mat;
}
接下来我们从 unity 的数据还原一个 maya 的 local matrix。
maya 面板,working unit 已经设置为 m。没有 joint orientation。


还原 maya local matrix 的代码如下:
private void RebuildMayaLocalMatrix()
{
var maya_local_matrix = GetMayaLocalMatrix3(transform.localPosition,
transform.localRotation.eulerAngles, transform.localScale);
PrintMatrix(maya_local_matrix);
}
private Matrix4x4 GetMayaLocalMatrix(Vector3 lp, Vector3 lr_euler, Vector3 ls)
{
lp.x = -lp.x;
var mat_t = Matrix4x4.TRS(lp, Quaternion.identity, Vector3.one);
var m0 = GenerateRotationMatrixSingle(-lr_euler[2], 2);
var m1 = GenerateRotationMatrixSingle(lr_euler[0], 0);
var m2 = GenerateRotationMatrixSingle(-lr_euler[1], 1);
var mat_r = m2 * m1 * m0;
var mat_s = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, ls);
var mat = mat_t * mat_r * mat_s;
return mat;
}
最终输出:
0.8137977 -0.8819392 0.3785224 4
0.4698464 1.765128 0.01802806 3
-0.3420201 0.3263524 0.9254165 2
0 0 0 1
来打印 maya 的 local matrix 验证一下。
from maya.api.OpenMaya import MMatrix
import maya.cmds as cmds
node_matrix = MMatrix(cmds.xform(cmds.ls(sl=1,sn=True), q=True, matrix=True, ws=False))
print(node_matrix)
输出。注意这里看到 translation 的一列单位还是 cm。
(((0.813798, 0.469846, -0.34202, 0), (-0.881939, 1.76513, 0.326352, 0), (0.378522, 0.0180283, 0.925417, 0), (400, 300, 200, 1)))
注意 MMatrix 的打印是列优先的。转换之后和 unity 还原的 local matrix 结果完全一致。
附加一个好用的工具集。
这个工具 (convertRotationOrder) 可以方便地将 maya 的一种旋转顺序改为另一种。