Unity 与 Maya 之间坐标系转换

有时候会需要将 Unity 里的节点 position, rotation 和 scale 数值和 maya 的原始数据做对比。这时候就需要进行坐标系转换。本文提供了一种从 unity transform 数据获取 maya local transform matrix 的方式。

通常这已足够,若需进一步还原面板数据需要拆解 maya transform matrix,需要参考这个 链接 ,很复杂,一般用不到。

从下图可以看到,这两个坐标系实际上只需要进行一个 x 轴的反向,这是左右手系转换的一种情况。但是,如果在maya中xyz轴的旋转序和 unity 默认的 zxy 不同时,就不仅是 x 轴反向这样简单了。对于这一类坐标系转换问题,可以用一种通用的解法去求解。

maya-unity-compare.png

首先重新铺垫一些基础概念。
(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。

存在 joint orientation 的情况下,如果要确切还原欧拉角,需要根据 maya rotation 组合的方式进一步分解旋转矩阵。但我们这里只需要还原 maya 的 local matrix ,而 joint orientation 已经包含在 local matrix 里了,所以有没有 joint orientation 并不重要。
maya坐标系下attribute.png

将 maya object 按 fbx 导出,导出单位为 cm,导入到 unity 之后其 attributes 会改变为下图。
unity坐标系下attribute.png

还原 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 的一种旋转顺序改为另一种。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容