原文1 - 看我七十二变:fbx格式
原文2 - 快用Python解析fbx格式!便捷!高效!易操作!
原文3 - FBX模型的"影分身术"
出处 - 网新电气IN VETA数字孪生引擎 - 微信公众号
Autodesk FBX是Autodesk公司出品的一款用于跨平台的免费三维创作与交换格式的软件,通过FBX用户能访问大多数三维供应商的三维文件.
FBX文件格式支持所有主要的三维数据元素以及二维、音频和视频媒体元素.
FBX对于三维软件的兼容性非常非常强大,几乎所有的三维软件或者游戏引擎全部都支持导入FBX模型,所以FBX格式已然成为新时代的互导利器.
FBX有两种文件模型,一种是二进制文件,另外一种是ASCII文件. 二进制文件文件大小和加载速度上面具有天然的优势,但是在可读性和易于集成方面不如ASCII文件. 本来主要基于ASCII文件格式进行研究.
1. FBX-ASCII
这里通过一系列示例来研究FBX的数据存储方法.
在3dmax中新建一个正方体,属性如下:
1.系统单位为米
2.长、宽、高为1米
3.场景中位置为(0,1,0)
4.材质为pbr材质,名称为M001
5.Z轴向上
效果图和导出格式如下:
1.1. 全局的配置
全局的配置存放在 GlobalSettings 结构里,通过 UpAxis 表示向上的坐标轴,其中Y轴向上值为1,若Z轴向上值为2. 通过OriginalUnitScaleFactor存放系统单位,值为100表示单位为1米. 示例如下:
GlobalSettings: {
Version: 1000
Properties70: {
P: "UpAxis", "int", "Integer", "",2
P: "OriginalUnitScaleFactor", "double", "Number", "",100.00000066
}
}
1.2. 默认设置
对象的默认属性存储在Object definitions字段. 这样就可以不在下面的具体定义中体现默认值了,可以有效地减少存储空间. 例如网格体的部分默认属性设置示例如下:
ObjectType: "Model" {
Count: 1
PropertyTemplate: "FbxNode" {
Properties70: {
P: "QuaternionInterpolate", "enum", "", "",0
P: "RotationStiffnessX", "double", "Number", "",0
P: "RotationStiffnessY", "double", "Number", "",0
P: "RotationStiffnessZ", "double", "Number", "",0
P: "AxisLen", "double", "Number", "",10
P: "PreRotation", "Vector3D", "Vector", "",0,0,0
P: "PostRotation", "Vector3D", "Vector", "",0,0,0
P: "RotationActive", "bool", "", "",0
}
}
}
1.3. 对象定义
对象的定义是整个FBX格式中最核心的单元,这里定义了几何形状、模型、材质等组成模型的必要元素.
1.3.1. Geometry对象
保存了对象的顶点、多边形、法线、UV、材质.
顶点数据格式如下:
Vertices: *24 {
a: -0.5,-0.5,0,0.5,-0.5,0,-0.5,0.5,0,0.5,0.5,0,-0.5,-0.5,1,0.5,-0.5,1,-0.5,0.5,1,0.5,0.5,1
}
这里实际上只有8个顶点,因为每个顶点坐标需要用(x,y,z)三个值来表示.
多边形格式如下:
PolygonVertexIndex: *24 {
a: 0,2,3,-2,4,5,7,-7,0,1,5,-5,1,3,7,-6,3,2,6,-8,2,0,4,-7
}
这里为了区分模型网格是三角面还是四边面,如果数据每隔三个为负数则为三角面,每隔四个为四边面.
如何将负数的索引值转成有效索引数据呢?
假如n为负数索引值,m = |n|-1 . m为计算后的正确索引值. 可以看到是每隔四个一个负数,这说明是四边形.
另外法线信息存储在LayerElementNormal中,uv信息存储在LayerElementUV中,材质信息存储在LayerElementMaterial中. 这些信息与模型关联通过总的Layer来进行. 示例如下:
Layer: 0 {
Version: 100
LayerElement: {
Type: "LayerElementNormal"
TypedIndex: 0
}
LayerElement: {
Type: "LayerElementMaterial"
TypedIndex: 0
}
LayerElement: {
Type: "LayerElementUV"
TypedIndex: 0
}
}
1.3.2. Model对象
Model对象主要用来存储模型的平移、旋转、缩放信息. 如,模型在场景中坐标为(0,1,0) 通过Lcl Translation字段来存储,示例如下
Model: 2358487033216, "Model::Box001", "Mesh" {
Version: 232
Properties70: {
P: "InheritType", "enum", "", "",1
P: "ScalingMax", "Vector3D", "Vector", "",0,0,0
P: "DefaultAttributeIndex", "int", "Integer", "",0
P: "Lcl Translation", "Lcl Translation", "", "A",0,1,0
P: "MaxHandle", "int", "Integer", "UH",15
}
Shading: T
Culling: "CullingOff"
}
1.4.2. Material对象
材质对象通过Material来标识,这里存储的是pbr此安置,定义示例如下:
Material: 2356630663440, "Material::M001", "" {
Version: 102
ShadingModel: "unknown"
MultiLayer: 0
Properties70: {
P: "SpecularColor", "ColorRGB", "Color", "",1,1,1
P: "SpecularFactor", "double", "Number", "",2
P: "ShininessExponent", "double", "Number", "",1024
P: "TransparencyFactor", "double", "Number", "",0
P: "EmissiveColor", "ColorRGB", "Color", "",0,0,0
P: "EmissiveFactor", "double", "Number", "",0
P: "3dsMax", "Compound", "", ""
P: "3dsMax|ClassIDa", "int", "Integer", "",2121471519
}
}
1.4.3. 对象关联
通过Connections字段来实现Geometry对象、Model对象、Material对象以及父子节点的关联. 示例如下:
Connections: {
;Model::Box001, Model::RootNode
C: "OO",2358487033216,0
;Geometry::, Model::Box001
C: "OO",2358344616656,2358487033216
;Material::M001, Model::Box001
C: "OO",2356630663440,2358487033216
}
对象的关联分为四种类型:
● OO: Object (source) to Object (destination).
● OP: Object (source) to Property (destination).
● PO: Property (source) to Object (destination).
● PP: Property (source) to Property (destination).
子节点往往通过source来表示. 材质和几何体也通过source来表示,这里有3个连接,分别代表:
[1] - Box001的父节点是RootNode
[2] - Box001的几何体是id为2358344616656的几何体
[3] - Box001的材质是M001材质
2. Python 解析 FBX 格式
FBX 官方提供了C++和python两种 FBX 解析库. 参考网址:https://www.autodesk.com/developer-network/platform-technologies/fbx-sdk-2020-3
其中,FBX Python SDK:
Windows FBX SDK 2020.3.1 Python (exe - 5571Kb)
Mac FBX SDK 2020.3.1 Python (tgz - 4066Kb)
Linux FBX SDK 2020.3.1 Python (gz - 5052Kb)
但,官方的SDK只支持 Python2.7 和 3.3.
对此,就需要用到 FBX Python Bindings,根据对应的 Python 版本进行编译.
Windows FBX SDK 2020.3.1 Python Bindings (exe - 989Kb)
Mac FBX SDK 2020.3.1 Python Bindings (tgz - 341Kb)
Linux FBX SDK 2020.3.1 Python Bindings (gz - 567Kb)
Github 已有编译好的库,https://github.com/Shi-Iho/FBX-Python-SDK-for-Python3.x,支持 Python3.7 和 3.8.
2.1. FBX Python库安装
找到 Python 安装目录,然后将 FBX 的3个库(fbx.pyd、FbxCommon.py、fbxsip.pyd)拷贝到lib/site-packages
目录下,如:
Programs/Python/Python37/lib/site-packages
然后还需要在该目录新建一个空fbx文件夹,之后即可在代码中进行调用.
2.2. FBX Python库调用
2.2.1. 加载FBX文件
示例如:
from fbx import *
import FbxCommon as FbxCommon
sdk_manager, scene = FbxCommon.InitializeSdkObjects()
lResult = FbxCommon.LoadScene(sdk_manager, scene, 'tt.fbx')
其中sdk_manager是fbxsdk的内存管理对象,scene就是加载的fbx文件的场景.
2.2.2. 获取网格体对象
每个场景会有一个根节点,通过如下方法来获取:
root_node = scene.GetRootNode()
fbx是用一颗树来管理对象,每个树的节点称为node. 由于主要关注网格体对象,所以就以这个为示例. 网格体对象不是node节点,而且其属性值,通过如下方法获取:
lAttributeType = (pNode.GetNodeAttribute().GetAttributeType())
if lAttributeType == FbxNodeAttribute.eMesh:
lMesh = pNode.GetNodeAttribute ()
2.2.3. 获取网格对象基本属性
[1] - 获取顶点数据
lControlPointsCount = lMesh.GetControlPointsCount()
lControlPoints = lMesh.GetControlPoints()
[2] - 获取面数据
lPolygonCount = pMesh.GetPolygonCount()
for i in range(lPolygonCount):
lPolygonSize = pMesh.GetPolygonSize(i)
for j in range(lPolygonSize):
lControlPointIndex = pMesh.GetPolygonVertex(i, j)
说明:
lPolygonCount
:面的总个数lPolygonSize
:面的形状(三角形或者四边形)lControlPointIndex
:面的顶点的索引,这里顶点数据到上面的lControlPoints
中去获取
[3] - uv坐标
uv坐标和顶点的获取方式不太一样,需要遍历Layer获取. 而Layer可能会有多个,这样网格体就具备了多套uv的能力.
for l in range(pMesh.GetLayerCount()):
leUV = pMesh.GetLayer(l).GetUVs()
[4] - 获取法线
法线的获取与uv坐标的获取方式类似,都需要遍历Layer来获取.
for j in range(pMesh.GetLayerCount()):
leNormals = pMesh.GetLayer(j).GetNormals()
[5] - 获取材质
材质并没有存储在mesh中,而是需要到node节点中去获取. 获取方法如下:
lNode = pMesh.GetNode()
lMaterialCount = lNode.GetMaterialCount()
for lCount in range(lMaterialCount):
lMaterial = lNode.GetMaterial(lCount)
材质对象和网格体对象是两个互相独立的对象,之后都与node节点进行关联. 不过网格体对象的Layer中也会记录引用材质对象的索引.