[Python] 自定义数据导出为FBX

FBX是Autodesk公司开发的一种通用3D文件格式,可以在不同的3D软件之间进行交换和共享。FBX文件包含了3D模型的几何形状、材质、动画、灯光、相机和其他相关信息

如果想在当前流行的 3D 编辑器(如 Maya 和 3DS Max)与 3D 体验引擎(如 Unity、Unreal Engine)之间传输数据,则应使用 FBX 文件格式。这是这个文件格式主要的设计目的。

关于FBX模型的更多详细信息,可以看我的另一篇博客:FBX文件格式:3D建模和游戏开发的通用语言----FBX模型详解

本篇博客是基于上一篇博客:从Max中读取模型数据,介绍如何将获取到的数据转换为fbx模型导出。

1. 初始化FBX设置

首先要做的就是是创建FBX SDK管理器,它几乎是所有FBX SDK中的类的对象分配器。

接着,创建FBX IO设置,并为FBX SDK管理器设置IO设置。

然后,创建一个空白的场景,用于写入数据。

最后需要注意的是,由于FBX SDK默认的是Y轴向上,而3ds Max默认的是Z轴向上,因此需要进行利用FbxAxisSystemConvertScene进行轴转换。

lSdkManager = FbxManager.Create()
if not lSdkManager:
    sys.exit(0)

ios = FbxIOSettings.Create(lSdkManager, IOSROOT)
lSdkManager.SetIOSettings(ios)

lScene = FbxScene.Create(lSdkManager, "")

if lScene .GetGlobalSettings().GetSystemUnit() != fbx.FbxAxisSystem.eMayaZUp:
    fbx.FbxAxisSystem.MayaZUp.ConvertScene(lScene)

2. 写入模型数据

我之前获取到的数据主要就是模型的mesh数据UV数据以及变换属性。下面依次介绍如何将这些数据写入。

2.1 写入mesh数据

首先,创建一个新的FbxMesh用于写入。接着,调用InitControlPoints初始化控制点,然后将顶点数据写入控制点。

下一步,先构造模型的材料属性并设置材料映射。接着,将面数据写入多边形中。BeginPolygon表示开始多边形构造, AddPolygon向多边形中添加顶点的ID, EndPolygon表示多边形构造结束。

最后是写入法线数据。先构造模型的法线属性并设置法线的映射类型。接着,
通过GetDirectArray方法获取法线数组,接着调用Add写入法线数据。

lMesh = fbx.FbxMesh.Create(fbxScene, "")

lMesh.InitControlPoints(len(vertices))

for i, vertex in enumerate(vertices):
    lMesh.SetControlPointAt(vertex, i)

materialElement = lMesh.CreateElementMaterial()
materialElement.SetMappingMode(fbx.FbxLayerElement.eByPolygon)
materialElement.SetReferenceMode(fbx.FbxLayerElement.eIndexToDirect)

for i in range(len(faces)):
    face = faces[i]
    lMesh.BeginPolygon(material_ids[i])
    for index in face:
        Mesh.AddPolygon(index)
    Mesh.EndPolygon()

normalElement = lMesh.CreateElementNormal()
normalElement.SetMappingMode(fbx.FbxLayerElement.eByPolygonVertex) 
normalElement.SetReferenceMode(fbx.FbxLayerElement.eDirect) 

for normal in normals:
    normalElement.GetDirectArray().Add(normal)

2.2 写入UV

先构造模型的UV属性并设置UV映射,接着通过GetDirectArray方法获取UV顶点数组,然后调用Add写入UV的顶点数据。

接下来,调用GetIndexArray获取面索引数组,然后调用Add写入UV的面的顶点索引数据。

UVElement = lMesh.CreateElementUV("")
UVElement.SetMappingMode(fbx.FbxLayerElement.eByPolygonVertex)
UVElement.SetReferenceMode(fbx.FbxLayerElement.eIndexToDirect)

for i in range(numVtx):
    UVValue = fbx.FbxVector2(verts[i][0], verts[i][1])
    UVElement.GetDirectArray().Add(UVValue)

for i in range(numIndex):
    UVIndex = faces[i]
    for j in range(len(UVIndex)):
        UVElement.GetIndexArray().Add(UVIndex[j] - 1)

2.3 写入变换属性

在写入变换属性之前,需要先在场景中构造一个节点FbxNode代表模型,并将刚刚写入的lMesh通过SetNodeAttribute方法赋予给这个节点。

lNode = fbx.FbxNode.Create(lScene, "")
lNode.SetNodeAttribute(lMesh)

接着,就可以向这个节点设置变换属性了。直接使用节点的对应属性的set方法就可以完成。

lNode.LclTranslation.Set(LclTranslation)
lNode.LclRotation.Set(LclRotation)
lNode.LclScaling.Set(LclScaling)

lNode.GeometricTranslation.Set(GeometricTranslation)
lNode.GeometricRotation.Set(GeometricRotation)
lNode.GeometricScaling.Set(GeometricScaling)

3. 导出模型

导出模型时,需要将刚刚的那个节点放入场景中。先通过GetRootNode获取场景的根节点,然后使用AddChild将其加入根节点的孩子。

最后,创建FbxExporter并初始化,Initialize 方法的第一个参数是导出文件保存路径,第二个参数表示导出的FBX文件类型,这里1代表为ASCii码格式。然后,调用该对象的Export方法将当前的场景导出为fbx模型。

lRootNode = fbxScene.GetRootNode()
lRootNode.AddChild(lNode)

exporter = FbxExporter.Create(fbxManager, "")
exporter.Initialize(output_path, 1, fbxIOSettings)

exporter.Export(fbxOutputScene)
exporter.Destroy()