ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Maya到UE坐标系转换全解析:Y-up与Z-up的轴映射与实操方案

Maya到UE坐标系转换全解析:Y-up与Z-up的轴映射与实操方案 做游戏资产管线的朋友第一课多半不是建模也不是引擎操作而是被坐标系折腾到怀疑人生——尤其是你的生产管线是Maya终端是UE的时候。我在Maya里认认真真摆好的一套场景满怀期待导进UE结果模型横七竖八要么躺在地上要么直接脸朝北方同事看了一眼还问是不是故意做了个“后现代抽象关卡设计”。排查一圈才发现问题根子不在建模而在Maya和UE的坐标系定义压根不是一回事。这篇内容就是把Maya到UE坐标系转换的原理、数学本质、三种实操方案、脚本工具和几十次实测下来踩过的坑全部摊开讲。不管你是刚入行的场景美术还是要做数据交互管线的技术美术看完基本都能把坐标转换这件事捋顺至少不会再被“Y轴向上还是Z轴向上”这种问题半夜叫醒。1. 项目背景为什么Maya和UE的坐标系不一样1.1 一个让我“翻车到天亮”的真实案例去年做一个虚拟制片预演项目制片那边给的原始资产都在Maya里从道具到摄像机路径全部是按Maya习惯摆放的。我在Maya里打开一切正常镜头调度、角色站位、空间关系全都没问题。结果把同一个FBX导入UE之后整个场景像是被推了一把侧翻的桌子——所有道具贴地横飞路灯杆变成了路灯“横杆”连地面都立起来了。我当时第一反应是“文件导错了”反复导了三四遍还是老样子。后来一个做了八年引擎管线的老哥路过瞟了一眼说“你把Y-up的东西直接扔给Z-up的引擎它不倒才怪。” 那一刻我才意识到坐标轴选型这东西看起来只是三个字母的差异一旦在项目里踩中代价就是几个通宵。1.2 Maya右手Y-Up与UE左手Z-Up的坐标系差异先把定义掰清楚。三维软件里的“右手坐标系”和“左手坐标系”通俗理解就是你伸出右手的拇指、食指和中指让它们两两垂直拇指指向X方向、食指指向Y方向、中指指向Z方向这套方向定义就是右手系换成左手就是左手系。Maya采用的正是右手坐标系并且把Y轴作为垂直向上的轴X轴朝右Z轴朝屏幕外。UE则不一样。UE用的是左手坐标系且Z轴向上X轴朝前ForwardY轴朝右Right。也就是说同一个模型在Maya里是“站”在Y轴上的到UE里必须“改站”到Z轴上除此之外因为手性不同从一个坐标系的Z轴正方向映射到另一个坐标系时其中一个轴的方向必须翻转不然会变成镜像。把两者摆在一起看Maya和UE的核心差异就两条项目MayaUE手性右手坐标系左手坐标系向上轴Y轴Z轴前向轴Z轴朝屏幕外X轴默认单位厘米一般厘米默认所以说Maya到UE的坐标系转换本质上不是“点一下旋转”这么简单。它涉及向上轴的重定向、前向轴的重映射以及可能出现的左右手翻转。这三个问题单独处理都不难但放在一个带有骨骼动画、法线、光照和缩放的资产上时就容易连锁翻车。1.3 坐标系差异会导致哪些问题如果是纯网格模型坐标系搞错最多就是模型躺倒转90度就能救回来。但一旦资产包含下面几类信息问题就远不只是一个旋转值的事模型朝向Maya里Z轴正向朝观察者UE里X轴正向朝前进方向。导向不当角色导进去会侧着走、倒着走甚至“面朝地板滑行”。动画数据骨骼的每个关键帧都记录了旋转和位移坐标系不同意味着所有关键帧都需要被重映射。如果只把根节点旋转一下而不管骨骼子节点动画大概率会扭曲。法线方向法线是依赖模型空间的。轴翻转之后如果处理不好光照会出现“半边脸黑”的诡异效果。摄像机路径影视预演项目里尤其明显Maya里的摄像机飞行路径一旦坐标系映射错导进UE就是一台“穿地摄像机”。数据流对接比如从Maya里导出的点云、路径点、追踪数据通过CSV或TXT喂给UE时坐标字段如果不转换点位会直接跑到完全错误的位置排查起来非常痛苦。这也是为什么我建议每个涉及Maya和UE双端的团队都要把坐标系转换做成标准操作流程而不是每次靠手动旋转“碰运气”。2. 转换核心思路轴映射与旋转矩阵2.1 从Y-Up到Z-Up的本质绕X轴旋转90°先说一个很容易被忽略的事实从Maya坐标系到UE坐标系并不是一个“各轴分别缩放映射”的线性变换而是一个绕X轴旋转90°的纯旋转变换。试想一个点在Maya里的坐标是(0, 1, 0)也就是Y轴正半轴上的点。它对应到UE里应该是(0, 0, 1)也就是Z轴正半轴上的点。把(0,1,0)变成(0,0,1)可以绕X轴旋转90°。再看一个点Maya里的(0, 0, 1)Z轴正方向绕X轴旋转90°后变为(0, -1, 0)即UE的Y轴负方向。于是可以验证在绕X轴旋转90°的变换下X轴保持不变Y轴向Z轴方向转Z轴向Y轴负方向转。换算成公式就是在Maya中取任意点(x, y, z)映射到UE坐标系后为(x, -z, y)。这个关系的行列式是1意味着这个变换是纯旋转没有产生镜像不需要额外做左右手翻转。2.2 位置、旋转、缩放的映射关系上面这个公式适用于位置坐标那旋转和缩放呢缩放是最好办的坐标系的轴手性和缩放没有关系如果Maya和UE都用厘米作为单位缩放在转换过程中保持不变。旋转信息就复杂一些。如果你只是把模型整体绕X轴转90°那么模型自身的旋转角度的意义也完全变了。四元数或者欧拉角都需要经过“基向量重映射”才会得到UE语义下的正确朝向。最简单的做法是提取Maya中模型的世界矩阵W用上面那个旋转矩阵M去做相似变换也就是W_UE M × W_Maya × M⁻¹其中M是2.1里那个轴映射矩阵。这个式子做的事本质上就是把Maya世界的旋转矩阵整体搬到UE世界坐标系里同时保留旋转本身的实际物理方向。对于大部分静态模型和带骨骼的网格用这条变换逻辑去处理旋转值是可靠的。2.3 选型三种主流方案对比知道数学原理接下来就是工程落地。我常用三种方案它们分别适用于不同场景方案手段适用场景优势注意点AMaya FBX导出时启用轴转换绝大多数资产导入无损、自动、可批量需要确认FBX Explorer设置BMaya内脚本旋转根组静态场景、临时调整快速、直观绑定角色慎用CUE端调整导入/运行时Transform资产已经导入不打算重导不用回到DCC要在每次导入时手动确认下面三个章节就分别把这三条路线展开每一步都给可以直接抄的参数和脚本。3. 方案A通过Maya FBX导出设置完成转换最推荐3.1 导出前的场景检查在动FBX导出设置之前先把Maya里的场景做一次“体检”能省下后面很多事。首先确认场景单位Maya的默认工作单位如果设成了厘米和UE默认单位一致那你就不需要担心缩放如果你习惯用米或者英寸请先把它切回厘米再检查模型本身的比例。其次是确认Maya场景里的实际轴向。你可以创建一个Locator在世界原点看它的局部坐标轴确认Y轴向上。如果场景里面有些模型是建模阶段用Z轴向上的模板生成的那单独看这个物体的局部坐标轴会和别的物体不一致这种情况要先统一物体轴向否则导出的FBX也会带着混乱的局部空间。最后如果是带骨骼的角色建议先把根骨骼的“设置初始姿态”做个快照防止后面调整坐标时骨骼的初始姿态被污染。3.2 FBX Axis Conversion参数设置在Maya里导出FBX时如果没有额外配置轴转换插件通常会把场景里所有坐标连同“Y-up”的信息一起写进FBX。UE读取FBX时会读取文件里的Up Axis信息所以理论上即使不手动设置UE也能识别。但这个机制并不总是按预期工作尤其是Maya版本比较旧、FBX插件版本不统一时轴信息容易被写错。更稳妥的做法是在Maya的FBX导出设置面板里找到Axis Conversion选项。菜单路径大致是“File Export All / Export Selection”后在选项窗口的Advanced Options一节里。不同Maya版本叫法略有差异但核心就是设置目标Up Axis为ZSource Up AxisY标识Maya场景Y向上Target Up AxisZ告诉FBX导出器最终要转成Z向上如果有Source Front Axis / Target Front Axis的选项继续保持默认不要乱改。设置完成后把模型重新导出一份FBX再导入UE。UE里可以看到模型是直立的而且前向朝向也大致正确。3.3 在UE中导入并检查在UE Content Browser里双击FBX文件会弹出导入设置面板。如果你的Maya导出设置正确这里有几点可以直接看到Import Uniform Scale保持1.0Import Translation为(0,0,0)Import Rotation为(0,0,0)注意“Mesh → Transform → Rotation”里如果没有额外偏移说明FBX文件内的坐标轴信息已经被正确读取。导入后一定要做三件事把StaticMesh放进关卡看朝向如果是骨骼网格播放一遍动画看骨骼是否有异常扭曲再打一个方向光看模型表面的法线是否正常。这三个检查过了这条链路就是通的。4. 方案B使用Maya Python脚本批量转换坐标4.1 场景根组旋转脚本FBX自动转换当然省事但当你的需求是“把Maya场景里的所有对象直接变成UE坐标然后再做其他深度处理”时在Maya里用脚本旋转根组反而更灵活。比如你想导出一个带点云、哑光物件、路径曲线的原始场景到UEFBX轴转换不一定能处理所有自定义属性这时脚本方案就显出优势。下面这段代码可以在Maya中创建根组、把场景所有可选对象挂进去、绕X轴旋转90°然后冻结变换。import pymel.core as pm def rotate_all_to_ue(selection_onlyTrue): # 选择要转换的对象。如果只转换选中项就取当前选中否则取场景所有dagObject if selection_only: items pm.ls(slTrue, dagTrue, longTrue) if not items: pm.warning(请先选中要转换的节点或组) return else: items pm.ls(assemblableTrue, longTrue) # 创建根组 root pm.group(emptyTrue, nameUE_coordinate_root) pm.parent(items, root) # 绕X轴旋转正90度让Y-up变成Z-up pm.xform(root, rotation(90, 0, 0)) # 冻结变换把旋转写回所有子对象的本地空间 pm.makeIdentity(root, applyTrue, translateTrue, rotateTrue, scaleTrue) # 删除成组用的空组因为变换已经被冻结到子对象 pm.delete(root) print(坐标转换完成Maya Y-up - UE Z-up)需要注意这段代码会修改每个对象本地空间的旋转值。纯粹的场景物件没问题但如果是绑定了骨骼且带有动画的角色直接执行会导致动画曲线含义改变整体动画表现可能会异常。这种时候把模型和动画作为两套数据分开处理会更稳妥。4.2 逐点坐标转换脚本适用于CSV/TXT数据有时候你手里拿到的不是场景文件而是一份Maya里导出的坐标列表比如标记点、麦克风阵列点位、角色路径点。这里只需要套用前面推导的位置映射公式UE坐标 (x, -z, y)。def maya_point_to_ue(point): x, y, z point return (x, -z, y) # 示例读取Maya导出的点列表输出UE可用的点列表 import csv def convert_csv_maya_to_ue(input_path, output_path): with open(input_path, r) as fin, open(output_path, w, newline) as fout: reader csv.reader(fin) writer csv.writer(fout) header next(reader) writer.writerow(header [X_UE, Y_UE, Z_UE]) for row in reader: # 假设前三列分别对应Maya的x, y, z x_m, y_m, z_m float(row[0]), float(row[1]), float(row[2]) xu, yu, zu maya_point_to_ue((x_m, y_m, z_m)) writer.writerow(row [xu, yu, zu])这个脚本在两种软件之间搬运数据时特别好用尤其是当数据不是通过FBX传输而是走CSV/TXT/JSON这类中间格式时。我经常在动捕棚里用这个脚本把Maya整理好的标记点位转换成UE里的Actor布局效率比手敲高得多。4.3 动画与骨骼的注意事项对带骨骼动画的角色我的建议是不要轻易用根组旋转脚本更不要对骨骼链整体绕X轴转90°之后直接导出动画。原因很简单动画曲线在骨骼子空间里存储的是相对父骨骼的旋转根节点旋转后父骨骼的旋转值变了但子骨骼的局部旋转本质上已经隐含了旋转偏移两者一叠加动画就会变成“拧毛巾”。正确处理方式分两步走第一步在Maya里把模型和控制器整体转到UE坐标同样用根组旋转随后导出静止模型第二步导出动画时设置FBX导出的轴转换并把根骨骼的旋转归零后作为基础姿态。这样模型是UE坐标动画数据也经过正确重映射两者在UE里才能合得上。这个流程我踩过很多次坑才总结出来。5. 方案C在UE端使用蓝图/C修正5.1 UE导入FBX时的Transform设置有些时候资产已经在项目里了重新回Maya再导一次流程成本太高这时候直接在UE里修正也可以。双击FBX打开导入设置时重点看“Transform”区域。如果导入之后模型躺倒也就是Y轴朝上的模型被当成Z轴朝上了一般做法是调整“Import Rotation”把Pitch设为-90度或者-90的变体。也有一部分依赖Yaw的比如模型虽然直立但朝向偏了那就把Yaw改成90、-90、180逐个试。这里有一个非常重要的经验不要直接在导入设置里把Import Rotation改成一个非零值就完事而是要检查模型导入后的本地朝向是否和你资产库的标准朝向一致。如果你把“Pitch -90”写死在导入设置里后续每个新模型都要保证同样的初始姿态否则一堆模型进场景后各转各的统一管理会变成灾难。5.2 运行时蓝图坐标修正如果是数据驱动的场景比如在Maya标定好一串路径点通过CSV给UE里的物体做运动在UE端用蓝图做运行时修正会更灵活。做法不复杂从CSV读入点位之后把每个点的Maya坐标做一次轴映射再传给你要控制的Actor。蓝图里只需把从文件读到的Vector值经过“MayaToUE”函数转换。转换逻辑就是三个节点X保持不变Y输入取负然后接到新Vector的ZZ输入接到新Vector的Y。用蓝图节点描述从原始向量拿到X、Y、Z然后MakeVector(XNew X原始, YNew -Z原始, ZNew Y原始)。这个函数小组件可以做成工具蓝图或者放进通用函数库项目里所有需要从Maya数据流接入的地方都能复用。5.3 C方式FTransform换算如果你的工具链跑在UE C层需要用代码处理外部传入的Maya坐标核心就是一个FTransform换算。注意UE的FMatrix采用行向量右乘规则轴映射矩阵要写成前面推导的矩阵的转置形式FTransform ConvertMayaToUE(const FTransform InMayaTransform) { // 映射矩阵Maya (x, y, z) - UE (x, -z, y) const FMatrix A2B FMatrix( FPlane(1, 0, 0, 0), FPlane(0, 0, 1, 0), FPlane(0, -1, 0, 0), FPlane(0, 0, 0, 1)); const FMatrix MayaMatrix InMayaTransform.ToMatrixWithScale(); const FMatrix UEMatrix MayaMatrix * A2B; return FTransform(UEMatrix); }这里把Maya的矩阵先取出来再乘上映射矩阵就得到了UE坐标语义下的Transform。从C层面做的好处是可以直接接到数据管线中间件里比如通过UDP/TCP接收Maya实时动捕数据在UE端即时转换成引擎坐标再驱动虚拟角色整体延迟可控表现也稳定。6. 进阶用CSV/TXT批量转换坐标数据并在UE中导入6.1 常见需求Maya标定的点云/路径数据导入UE做虚拟制片或者建筑可视化项目时经常遇到一个很实际的需求Maya里有一张标定好的点位表可能包含几十甚至上百个标记点对应的是真实演播厅里的机位或者道具摆放位置需要用CSV/TXT格式传给UE在UE里瞬间生成对应的Actor阵列。如果你直接在UE里导入Maya导出的原始CSV点位的位置通常会有一半在地底下、一半在天上因为坐标轴定义不同。这种情况需要把逐点转换做成导入流程自动完成而不是每次拖入Excel手动加工。6.2 写一个Python小工具批量转换我一般会在项目里放一个独立的Python小工具负责读Maya导出文件、做坐标变换、输出UE能用的列表文件。这个工具不依赖Maya环境所以美术和TA都能独立使用。import csv def maya_to_ue_row(row): x_m, y_m, z_m float(row[0]), float(row[1]), float(row[2]) return (x_m, -z_m, y_m) def convert_file(input_path, output_path): with open(input_path, r) as fin: reader csv.reader(fin) header next(reader) rows list(reader) with open(output_path, w, newline) as fout: writer csv.writer(fout) writer.writerow(header) # 输出同样表头 for row in rows: writer.writerow(maya_to_ue_row(row))这个工具唯一的输出差异是坐标字段已经被替换成UE坐标不额外增加列所以下游程序不需要改动字段索引比较省心。6.3 在UE编辑器用Python读CSV并生成ActorUE从4.26开始编辑器本身支持Python脚本扩展。你可以启用“Python Editor Script Plugin”和“Editor Scripting Utilities”然后在编辑器的Python控制台里执行脚本直接读取CSV并在关卡中生成Actor。示例代码如下import csv import unreal def spawn_points_from_csv(csv_path): with open(csv_path, r) as f: reader csv.reader(f) next(reader) # 跳过表头 for idx, row in enumerate(reader): x, y, z maya_to_ue_point((float(row[0]), float(row[1]), float(row[2]))) loc unreal.Vector(x, y, z) actor unreal.EditorLevelLibrary.spawn_actor_from_class( unreal.PointLight, loc, unreal.Rotator(0, 0, 0) ) actor.set_actor_label(fPoint_{idx:03d})用PointLight只是为了方便肉眼验证点位你也可以换成EmptyActor或者继承自自定义蓝图类的Actor。这个方法非常适合在关卡里快速铺开标定点阵列。7. 常见问题与排查技巧实录7.1 模型躺倒/翻转问题表现模型在Maya里直立导入UE后平躺、横躺或者以任意角度侧躺。排查思路先看FBX文件本身的Axis信息。用Maya重新导出一份FBX并在导出设置里查看Axis Conversion部分确认Source Up Axis是Y、Target Up Axis是Z。如果设置没问题但依然躺倒那么大概率是场景里根节点的局部坐标轴本身就不是标准Y-up比如模型是从老项目里拷贝过来、根骨骼被人为旋转过。这种情况在Maya里选中根节点打开Channel Box检查Rotate属性是否非零若非零则先清零再导出。7.2 模型前后镜像/左右颠倒问题表现模型导入UE后不是“歪倒”而是左右翻转或者前后反转比如把右手模型导成了左手模型。这种通常是FBX文件的轴向定义与UE的导入设置不匹配导致的。处理方法检查Maya导出的FBX是否包含Handedness信息。如果你的DCC工具是Blender或者自研导出器很容易在这里出问题。对这种情况我通常在UE导入设置里通过旋转变换解决而不是回Maya重做。如果是前后反转设置Yaw180如果是左右镜像需要额外在网格层级做镜像处理这个就涉及网格几何翻转比旋转复杂要特别小心。7.3 动画、法线、灯光异常问题表现模型看着方向正确但播放动画时角色肢体“拧麻花”或者模型表面光照明暗交替看起来像被“粗糙刷子”刷过。动画异常基本都是旋转映射没做完整。不要只转根节点要确保骨架层级里的每个骨骼关节都经过同一套轴映射。如果你的动画是通过FBX导出建议先导出一个T-Pose检查骨骼朝向再导正常动画。法线异常则多半是模型在转换时遭遇过非等比缩放或者导入设置里Normal Import Method选错了建议把“Import Normals”设为“Calculate Normals”重新计算一次。7.4 缩放和单位问题问题表现模型大小差10倍、100倍或者平时用Maya做1米高的杯子导入UE却变成一颗摩天楼。先核对两边单位。Maya如果设成米UE默认厘米不换算的话一个1米见方的物件进UE就是1厘米大小。同理Maya里的角色如果是190cm导出FBX时确认“FBX Units”设置为Automatic让单位信息写进文件。导入UE时保持Import Uniform Scale1.0。如果已经导成了1厘米的小玩意直接改缩放是下策最好是回Maya把单位改对再重导避免隐藏的缩放污染后续动画和物理计算。7.5 特殊其他DCC导出的FBX也要统一处理这条往往被忽视。团队里除了Maya还有人用Blender、Houdini或者C4D。Blender默认也是Y-up右手系与Maya相近但有些设置会覆盖Houdini默认也是Y-up但可配置。因此所有资产入库前都应该有一道“轴向检查”关卡哪怕只是写一个小工具在导入GIT仓库前自动检查FBX头部的Up Axis和Front Axis字段。提前拦截问题比出镜到UE里再返工要划算得多。我个人在实际生产里最大的体会是坐标系转换不是一道“加个旋转节点”就结束的工序它是一条必须写进资产规范的约定。最好在项目启动第一天就确认好“DCC标准坐标是Y-up右手系引擎标准坐标是Z-up左手系中间转换责任方是谁、工具是什么”并沉淀成团队工具链的一部分。只有把轴映射公式、FBX导出选项、脚本转换、UE导入设置、CSV数据流这几层都打通后续做动画、虚拟制片、多工具联调时才不会再被“哪个轴朝上”这种基础问题拖慢进度。希望这篇内容能帮你少踩几个坑直接把坐标转换这堵墙拆平。
返回列表