原文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中也会记录引用材质对象的索引.

Last modification:June 16th, 2022 at 03:47 pm