FBX 文件结构

由于我们自己拥有二进制文件格式,因此该文档尝试收集有关FBX结构的知识(文件的典型内容,以及属性和模板的使用等)。

警告:重要的WIP,因为我们必须猜测这里有什么(没有关于工作原理的官方文档)!

我们尝试着重于最新版本(7.3,甚至7.4)!

注意:一旦我们在这里或多或少地完成了某些事情,我们也可以将其移至博客页面…………

泛泛而谈

FBX是一种Node节点格式,具有根元素(从未明确写入)和子树。

每个元素都有一个id(字节字符串),并且可以包含data和children元素。

数据是一组(值POC,类型)元组Tuple,可用类型有:bool,short,int,long,float,double,bytes,string和这些类型的数组。

这是元素的(JSON)表示:

["Element_ID",                         # ID
     ["data_string", 13],              # Data
     "SI",                             # Data types, as single-char codes (S for String, I for Integer, etc.)
     [__other_children_elements__]
]

一切都基于这个简单的架构。

属性Property

属性是一种添加大量类型数据的方法。

属性由“Properties70”元素的子元素表示,该元素不包含任何数据。

每个属性都是一个元素。它的ID似乎并不重要(通常,它们都是“P”或“PS”)。他们的数据布局遵循该架构:

["PropName", "PropType", "Label(?)", "Flags", __values__, …]

换句话说,属性的四个第一个数据总是字符串(它的名称,类型,可能是它的标签(通常为空)和一些标志(可选)),它们是属性的“元数据”。其他数据是属性的值(通常只有一个,但是例如对于颜色或3D向量它们是三个 - 并且一些属性类型没有值:/),它们的类型取决于数据类型!

请注意,定义属性类型的三个元数据看起来相当模糊,它们可能会在不同版本之间发生变化。

Flags意义非常模糊,这是目前已知的:

  • ’U’表示属性是用户定义的(默认情况下在FBX中不存在)User-defined。
  • ’A’表示该属性是可动画的 Animation。
  • ’+’也在某些文件中找到,假设当前它意味着’动画’(始终跟随’A’标志)。
  • ‘H’ 高度推测为隐藏 Hide?
  • ‘L7’ 高度推测可能是四个’锁定’标志(数字是四位比特标志)Lock?

另请参阅SDK文档,遗憾的是,通常没有关于这些标志的实际FBX表示的信息…………

进一步关于动画属性:看起来前两个元数据依赖于属性是否可动画,最后。例如,不可动画的float64将是(“double”,“Number”,“”),而可动画的float64将是(“Number”,“”,“”) - 至少,这是因为阅读“官方” FBX文件。

下列是属性的一些基本例子:

Integer: ["P", ["some_name", "int", "Integer", "", 1], "SSSSI", []]
Double:  ["P", ["some_name", "double", "Number", "", 1.0], "SSSSD", []]
Color:   ["P", ["some_name", "ColorRGB", "Color", "", 0.0, 0.0, 0.0], "SSSSDDD", []]

似乎属性也可以使用“复合Compound”类型(没有值)进行组合,例如:

["P", ["Original", "Compound", "", ""], "SSSS", []],
["P", ["Original|ApplicationVendor", "KString", "", "", ""], "SSSSS", []],
["P", ["Original|ApplicationName", "KString", "", "", ""], "SSSSS", []],
["P", ["Original|ApplicationVersion", "KString", "", "", ""], "SSSSS", []],
["P", ["Original|DateTime_GMT", "DateTime", "", "", ""], "SSSSS", []],
["P", ["Original|FileName", "KString", "", "", ""], "SSSSS", []],

组Group定义:“父”属性开头,使用“|”作为分隔符。

一些特定的Property类型
  • KTime:FBX时间戳,signed int64,单位是1/46186158000秒…………

模板Template

每个模板由“ObjectType”元素定义,该元素采用单个属性,模板的名称,也是它们“定义”的对象的名称(例如“模型Model”,“几何Geometry”,“材料Material”等) )。

每个模板都有两个子节点:“Count”是使用此模板的对象2数,“PropertyTemplate”,它只包含一些属性。

总而言之,这是模板定义的通用结构:

["ObjectType", ["Geometry"], "S", [            # Start of template definition
    ["Count", [1], "I", []],                   # Number of Objects using this template
    ["PropertyTemplate", ["KFbxMesh"], "S", [  # Start of template's properties
        ["Properties70", [], "", [
            ["P", ["Color", "ColorRGB", "Color", "", 0.8, 0.8, 0.8], "SSSSDDD", []],
            ["P", ["BBoxMin", "Vector3D", "Vector", "", 0.0, 0.0, 0.0], "SSSSDDD", []],
            ["P", ["BBoxMax", "Vector3D", "Vector", "", 0.0, 0.0, 0.0], "SSSSDDD", []],
            ...
        ]]
    ]]
]],

注意GlobalSettings似乎也有它的模板定义,虽然它不是一个Object ……

“PropertyTemplate”元素的名称数据是某种子类型。通常,他们直接使用SDK中的类名。

子类型名称在FBX 7.3中,子类型名称似乎使用前缀为’K’的SDK类名(如’KFbxFileTexture’),而在7.4中,’K’似乎已消失……

模板定义某种“默认值”,如果需要,可以由每个对象覆盖。

模板还有另一个问题:例如灯光,相机,零点等共享相同的“全局”元素类型,而它们具有分离的子类型,这些子类型是它们自己的属性集。不幸的是,模板是为“全局”类型定义的,并且不希望处理几组属性定义,因此在这种情况下你必须选择一个子类型,而对于其他子类型则根本没有模板(尽管要反对模板必须保持“全局”类型的用户总数……)。

顶级结构

有效的FBX文件必须包含一组标准元素:

  • FBXHeaderExtension:强制性?包含文件的元数据。
  • FileId:强制性。某种(当前)模糊的哈希,显然基于CreationTime数据。
  • CreationTime:强制性。创建该文件的日期/时间,作为单个字符串(例如“2013-08-05 10:27:44:000”)。
  • Creator:强制性?标识用于创建FBX的工具的字符串。
  • GlobalSettings:强制性?定义一般数据的一组属性,例如场景的方向…………
  • Documents:可选。不知道它实际上是用什么的,到目前为止只看到了带有单个doc定义的FBX文件…………也许它允许在一个FBX中有几个场景?
  • Reference:???
  • Definitions:可选。这里定义了模板。
  • Objects:可选。这里存储了真实有用的静态(?)数据(对象,几何,纹理,材料等)。
  • Connections:可选。这里是在对象中定义的数据之间定义的链接(例如,哪个对象使用哪种材料等)。
  • Takes:可选。这里定义了动画?

FBXHeaderExtension

……

FileId和CreationTime

这两个字段被用作文件的某种模糊的“签名”。它似乎使用CreationTime来生成FileId,但是我们仍然不知道如何,所以目前我们使用虚拟时间戳(自纪元以来的0)和固定的fileid …………这里需要调查!

全局设置GlobalSettings

……

文件Documents

不知道它目前使用的是什么 - 看起来像FBX可以支持多个文档(比如in,多个场景?),但到目前为止只找到了单文档的例子…………要进行调查。

包含“Count”元素(文档数)和一组“Document”元素。

每个Document包含三个数据,一个UID(如int64),一个始终为空的字符串和一个名称。它的子节点是一组属性,“RootNode”具有唯一的int64值(总是0,很可能是某种偏移量,这里没有更多提示)。

["Documents", [], "", [
    ["Count", [1], "I", []],
    ["Document", [151925416, "", "Scene"], "LSS", [
        ["Properties70", [], "", [
            ["P", ["SourceObject", "object", "", ""], "SSSS", []],
            ["P", ["ActiveAnimStackName", "KString", "", "", ""], "SSSSS", []]
        ]],
        ["RootNode", [0], "L", []]
    ]]
]]

References

目前绝对不知道这是什么。在我找到的例子中通常是空的。

Definitions

包含Version元素,Count 1(使用这些模板定义提供数据元素的总数)和一组模板定义

["Definitions", [], "", [
    ["Version", [100], "I", []],
    ["Count", [12], "I", []],
    ["ObjectType", ["GlobalSettings"], "S", [
        ...
    ]],
    ["ObjectType", ["Model"], "S", [
        ...
    ]],
    ...
]]

对象Objects

对象是一组各种元素(光Light数据,相机Camera数据,几何Geometry数据,对象Object数据,材质Material,纹理Texture等),后来使用它们的UID 在Connections部分中链接在一起。

所有数据都有一个类似的顶级元素,它似乎根据其内容类型使用标准名称,有三个数据(第一个是其UID,第二个是’name :: class’对,第三个是排序子类)和一组子项,包括属性:

["NodeAttribute", [                 # Main class
         151952504,                  # The UID.
         "::NodeAttribute",          # Name::Class
         "CameraSwitcher"            # Sub-class
     ], "LSS", [
     ["Properties70", [], "", [
         ["P", ["Camera Index", "Integer", "", "A+", 100], "SSSSI", []]
     ]],
     ["Version", [101], "I", []],
     ...
 ]]

name:: class 分隔符(在此处表示为’::’)二进制格式格式实际上是链b"\x00\x01"\x00 - NUL - 空字符\x01 - SOH - 标题开始

请注意,有些子元素有时会重复属性中的内容(例如,对于相机,“Position”,“UpVector”和“InterestPosition”属性分别具有与“Position”,“Up”和“LookAt”子元素相同的内容)。可能一些兼容性很重要。

连接Connections

连接只是定义对象之间的链接(就像给定对象使用的给定材料一样):

["Connections", [], "", [
    ["C", ["OO", 152171968, 0], "SLL", []],
    ["C", ["OO", 152161640, 0], "SLL", []],
    ["C", ["OP", 152102088, 987654321, "Lcl Translation"], "SLLS", []],
    …
]]

每个连接都是名称为“C”的元素,包含以下数据:

  • 定义关系性质的代码字符串(到目前为止,找到链接到对象的对象的“OO”,以及链接到属性的对象的“OP”)。
  • 第一个UID(“Child孩子”)。
  • 第二个UID(“Parent父”,如果为0,则它是FBX doc的(隐式)根节点)。
  • 在对象到属性连接的情况下,一个字符串,它是子链接到的父元素的名称。这用于例如将纹理指定给材料的漫反射,镜面反射,环境等属性。请注意,属性可以是“Properties70”,也可以是直接父元素1。

我还在几个文件中看到了“PO”关系(元素的属性子元素),我想我们也可以想象“PP” - 但是我们不必担心当前的那些,还没有打到任何用例。

Takes

这是旧的(已弃用的甚至?)动画系统,看起来好像让它在7.4文件中为空而没用……

主要数据 - 对象Objects

模型Model

  • 类:FbxObject :: FbxNode
    Connected to [root element(0) 跟元素] or [another model as parent 另一个父模型]

这些元素大致相当于Blender的Objects。它们定义了基本位置,旋转和缩放,以及与相同主题相关的一系列高级设置。

相机Camera……当链接到相机数据时,模型还包含一些相机数据(如Width/Height等)。虽然这不是旧版本格式的残余,但不确定……

变换Transformation 约束 Constrain

任何FBX模型都可以具有 平移/旋转/缩放 限制,由“…… Active” bool 属性启用,并具有 全局每轴最小/最大值

它也可以有一些“跟踪到 Track”的关系,使用场景的另一个对象作为目标(并且可选地另一个作为向上目标)。但是,这是如何工作的仍然不清楚(相关属性是“对象”类型,它没有任何价值…………)。

空间Spaces & Parenting父子关系

模型的变换(主要是“Lcl Translation”,“Lcl Rotation”[Euler,in degrees]和“Lcl Scaling”)总是在父空间中,或者没有父节点的世界空间(“Lcl”代表“Local” 模型空间) )。这对于骨骼也是正确的,因为在FBX中它们大致表示为父级模型的链(参见下面的#Armature_and_Bones Armature和Bones)。

但是,有三种方法可以在父空间中应用子转换,由“InheritType”枚举属性(FbxTransform :: EInheritType)控制:

  • eInheritRrSs (0).
  • eInheritRSrs (1).
  • eInheritRrs (2).

大写用于父级(旋转R / 缩放S),小写用于子级(rs)。

使用Blender父母和骨链,我们应该使用RSrs,它匹配通常的3×3矩阵乘法(据我所知!)。我们也可以使用Rrs来匹配我们禁用的“继承比例”骨骼选项?

光照数据 Light

  • 类:FbxObject :: FbxNodeAttribute :: FbxLight
    Connected to a [Model]

大致相当于Blender’s Lamp(光源类型,能量,方向等)。

相机数据 Camera

  • 类:FbxObject :: FbxNodeAttribute :: FbxCamera
    Connected to a [Model] (外加,“virtual” Model 空模型 => CameraSwitcher? 相机切换器)
  • 类:FbxObject :: FbxNodeAttribute :: FbxCameraSwitcher

相机数据分为两个实体,相机本身和“相机切换器”

Camera嵌入对象级数据(位置和方向)。 Camera期望传感器尺寸的值以英寸为单位! Camera期望其角度的值以度为单位。

⭐️网格数据 Mesh

  • 类:FbxObject :: FbxNodeAttribute :: FbxLayerContainer :: FbxGeometryBase :: FbxGeometry :: FbxMesh
  • Connected to a [Model]

大致相当于Blender的Mesh。

这是在“层 layer”元素族中,即它可以包含几个相同类型的数据(每层一个),例如对于UV maps 或 VCol (vertext column) layer。一般结构是:

["Geometry", [152167664, "Name::Geometry", "Mesh"], "LSS", [
    ["Vertices", [[<array_of_floats>]], "d", []],
    ["PolygonVertexIndex", [[<array_of_integers>]], "i", []],
    ["Edges", [[<array_of_integers>]], "i", []],
    ["GeometryVersion", [124], "I", []],
    ["LayerElementNormal", [0], "I", [
        ["Version", [101], "I", []],
        ["Name", [""], "S", []],
        ["MappingInformationType", ["ByVertice"], "S", []],
        ["ReferenceInformationType", ["Direct"], "S", []],
        ["Normals", [[<array_of_floats>]], "d", []]
    ]],
    ["LayerElementSmoothing", [0], "I", [
        ["Version", [102], "I", []],
        ["Name", [""], "S", []],
        ["MappingInformationType", ["ByPolygon"], "S", []],
        ["ReferenceInformationType", ["Direct"], "S", []],
        ["Smoothing", [[<array_of_integers>]], "i", []]  # Yep, int32 for bool values...
    ]],
    ["LayerElementUV", [0], "I", [
        ["Version", [101], "I", []],
        ["Name", ["UVMap"], "S", []],
        ["MappingInformationType", ["ByPolygonVertex"], "S", []],
        ["ReferenceInformationType", ["IndexToDirect"], "S", []],
        ["UV", [[<array_of_floats>]], "d", []],
        ["UVIndex", [[<array_of_integers>]], "i", []],
    ]],
    ["LayerElementUV", [1], "I", [
        ["Version", [101], "I", []],
        ["Name", ["UVMap.001"], "S", []],
        ["MappingInformationType", ["ByPolygonVertex"], "S", []],
        ["ReferenceInformationType", ["IndexToDirect"], "S", []],
        ["UV", [[<array_of_floats>]], "d", []],
        ["UVIndex", [[<array_of_integers>]], "i", []],
    ]],
    ["LayerElementMaterial", [0], "I", [
        ["Version", [101], "I", []],
        ["Name", ["gold"], "S", []],
        ["MappingInformationType", ["AllSame"], "S", []],
        ["ReferenceInformationType", ["IndexToDirect"], "S", []],
        ["Materials", [[0]], "i", []]
    ]],
    ["Layer", [0], "I", [
        ["Version", [100], "I", []],
        ["LayerElement", [], "", [
            ["Type", ["LayerElementNormal"], "S", []],
            ["TypedIndex", [0], "I", []]
        ]],
        ["LayerElement", [], "", [
            ["Type", ["LayerElementMaterial"], "S", []],
            ["TypedIndex", [0], "I", []]
        ]],
        ["LayerElement", [], "", [
            ["Type", ["LayerElementSmoothing"], "S", []],
            ["TypedIndex", [0], "I", []]
        ]]
    ]]
    ["Layer", [1], "I", [
        ["Version", [100], "I", []],
        ["LayerElement", [], "", [
            ["Type", ["LayerElementUV"], "S", []],
            ["TypedIndex", [1], "I", []]
        ]],
    ]]
]]

在这里,’::’只是一种惯例,二进制格式实际上是链b"\x00\x01"(NUL空字符 SOH标题开始)。

所以,我们首先得到基本数据:

  • 顶点Vertices:一个展平的coords数组(所以如果verts [0] ==(1.0,2.0,0.5),数组以[1.0,2.0,0.5,……]开头)。
  • 多边形Polygons(由顶点索引定义,多边形的最后一个索引被无效的)。
  • 边Edges(可选)。边缘以非常奇怪的方式定义:此处的每个索引都指向多边形 的数组索引,因此边缘由此多边形的顶点和同一多边形中的下一个顶点定义。

然后,你有layer元素(uvs,material,vcol (vertex column),smooth等)。这些图层元素可以影响顶点,边,面,面的顶点(在Blender代码中我们称之为“循环loop”)或所有内容。除最新情况外,您可以通过两种不同的方式将图层数据映射到受影响的几何元素:

  • 直接:layer数据数组中的 item n 影响**(vertice | edge | face | loop)*n警告虽然不允许(参见FBX SDK doc,似乎官方 FBX导出器在此模式下接受截断的数组(例如,在包含数10个顶点的网格中只有2个vcols项……)。
  • 索引直接IndexToDirect:layer数据仅包含奇异值,另外一个整数数组将每个几何元素映射到图层数据数组中的值(或者不显示任何内容 - 映射索引为**-1**)。因此,例如,如果 loop 5和 loop 8的UV相同,则层索引数组的第5项和第8项将具有相同的值,其指向layer数据中的正确UV 三元组 triplet 。

一旦定义了所有 layer 元素,就必须自己定义包含它们的 layer。每个类型中只能有一个layer 元素。

空数据 Null

  • 类:FbxObject :: FbxNodeAttribute :: FbxNull
    Connected to a [Model]

大致相当于Blender’s Empty。

Armature & Bones 骨骼

在FBX中,没有真正的骨架概念,你宁可拥有骨骼链,它几乎只由“模型”元素(FbxNode)定义,即来自父骨骼的loc / rot / scale。根骨骼是空对象(模型)的子对象,它扮演骨架的角色。

换句话说,Armature 更像是一组父系链。

每个骨骼/钩子都由一个“LimbNode”表示,它还包含一个“Size”参数(它的长度),但不确定所有进口商都使用/理解它。

骨骼和网格首先由BindPose元素“链接”,该元素在绑定时存储网格的(变换)矩阵和全局(世界)空间中的所有骨骼。

第二个“link 链接”是Deformer(Skin)元素,它类似于Blender的Armature 顶点组系统。此元素存储SubDeformers(aka Clusters),每个骨骼一个,具有每个顶点/控制点的“绑定”权重,以及一些变换矩阵。

那些变换矩阵对我来说还不是很清楚,据我所知,它们指的是绑定时网格/骨架/骨骼的变换:

  • 变换是骨骼空间中网格的变换**!**。
  • TransformLink是全球空间中骨骼的转换。
  • TransformAssociateModel将是Armature 对象的转换??? 不太清楚为什么需要…………

总结连接:

  • LimbNode(骨骼数据) - >模型(骨骼转换,仅仅是空对象链接父骨骼的一个,与任何其他模型父关系一样)。
  • LimbNode(骨骼数据) - > Cluster(种类’vgroup’) - > Skin - > Geometry(通常是网格数据)。

BindPose有自己的’Link 链接’系统,它不使用连接(是的,FBX …………)。所有子PoseNode元素都有一个Node属性,该属性包含它们所代表的元素(mesh网格 或 bone骨骼)的数字ID。我认为这是一个旧的,有点弃用的系统,支持 cluster集群 / skin皮肤……但我们仍然可以找到许多只有部分使用变形器的FBX文件,所以现在在导入器中我们使用变形器(如果可用),并且回退到另外绑定。

Shape Keys 几何键

几何键处理与FBX 7.x中的Armature 非常相似。

首先,您将形状定义为特殊的几何元素(’Shape’)。它们只存储顶点索引列表,匹配坐标和法线(法线通常存储为NULL矢量,不确定它的含义)。请注意,成形的网格元素为其每个形状键获得一个可动画的数量道具,看起来像形状影响的两个不同的动画系统在这里共存…………:/

在这种情况下它也会生成一个BindPose,但是对于Model元素,而不是Geometry中的一个网格…………我真的没有看到这里使用bindnode矩阵的东西 - 现在将完全忽略。

将几何链接到其网格的主要(可能是面向未来的?)方法是通过Deformer系统完成的 - 这里主要元素是“BlendShape”元素,子变形器是“BlendShapeChannel”。那些具有全局动画的DeformPercent属性和FullWeights数组因子,我认为它们是某种每顶点加权,我们现在可以在Blender中忽略它。

  • Shape - > BlendShapeChannel - > BlendShape - > Geometry(通常是mesh网格数据)。

Material 材料数据

  • 类:FbxObject :: FbxSurfaceMaterial
    Connected to a Model

FBX支持两种材质,基本的Lambert / Phong着色和自定义着色器。不幸的是,后者只支持关闭着色器(HLSL和CGSL),没有GLSL。

因此,基本着色将使您使用FbxSurfaceMaterialLambert(surfacematerial的子类,仅用于diffuse)或FbxSurfaceMaterialPhong(lambert one的子类,用于漫反射 diffuse + 镜面着色 Specular)。

请注意,数据节点本身始终简称为“材质”,并在“ShadingModel”元素中指定“phong”或“lambert”:

["Material", [1055568016, "default::Material", ""], "LSS", [
    ["Version", [102], "I", []],
    ["ShadingModel", ["phong"], "S", []],
    ["MultiLayer", [0], "I", []],
    ["Properties70", [], "", [
        ["P", ["ShadingModel", "KString", "", "", "phong"], "SSSSS", []],
        ["P", ["EmissiveFactor", "Number", "", "A", 0.0], "SSSSD", []],
        ["P", ["AmbientColor", "Color", "", "A", 0.5879999995231628, 0.5879999995231628, 0.5879999995231628], "SSSSDDD", []],
        ["P", ["DiffuseColor", "Color", "", "A", 0.5879999995231628, 0.5879999995231628, 0.5879999995231628], "SSSSDDD", []],
        ...
    ]]
]]

多层实际上是一个bool,我想,还不确定它的含义/控制到底是什么。

根据应用程序的不同,理论上大多数材料的属性都可以通过纹理来控制(例如,将纹理链接到“DiffuseColor”属性是如何将纹理应用于材质的颜色!),请参阅下面的详细信息。

请注意,网格可以具有LayerElementMaterial图层,该图层将给定材质指定给给定面。这里的材料仅由一个索引(而不是现代FBX中的其他地方的UID)引用,这是它们链接到对象(FBX节点)的顺序!

Texture 纹理数据

  • 类:FbxObject :: FbxTexture :: FbxFileTexture
    Connected to a [Material]
  • 类:FbxObject :: FbxVideo
    Connected to a FileTexture

纹理在FBX中分为两个元素:

  • 纹理本身,包含映射数据,并链接到材质的属性(“OP”连接)。
  • “视频”(或“剪辑”),它引用图片,也用于静态图像(喜欢纹理元素)。

纹理(TextureVideoClip类型)包含所有映射数据(当然可以是UV,也可以是简单的立方体/球体/管投影等)。它还包含文件路径,即使Video也有这些数据。注意似乎无法为给定纹理选择特定的UVmap

视频(剪辑类型)包含实际的图像/视频路径,以及一些类似其名称的信息。它还可以在“内容”字节数组元素中嵌入图像/视频的二进制数据。

理论上注意,还有程序纹理的支持,但这些仅仅是不透明的数据块,对我们来说无法使用。

我们还可以使用分层纹理来匹配多个纹理影响相同材质的相同属性的情况。但是,首先要检查它是否在应用程序之间得到了足够的支持!

动画

  • 类:FbxObject :: FbxCollection :: FbxAnimStack
    Connected to [Nothing]!
  • 类:FbxObject :: FbxCollection :: FbxAnimLayer
    Connected to an [AnimStack]
  • 类:FbxObject :: FbxAnimCurveNode
    Connected to an [AnimLayer] and a [Node’s property]
  • 类:FbxObject :: FbxAnimCurveBase :: FbxAnimCurve
    Connected to an [AnimCurveNode’s property]

FBX中的(新)动画实际上相当简单:你有一个Stack(通常每个FBX文件只有一个,否则你可能有兼容性问题),一组图层链接到这个。

AnimLayers类似于Blender’s Actions。您可能有多个图层影响同一对象的属性,具有混合效果,但我们不会在导出器中寻求这种复杂性!

一组CurveNodes链接到每个AnimLayer,它们定义了哪些属性是动画的:

["AnimationCurveNode", [2045776, "T::AnimCurveNode", ""], "LSS", [
    ["Properties70", [], "", [
        ["P", ["d", "Compound", "", ""], "SSSS", []]]]]]]],
        ["P", ["d|X", "Number", "", "A", 2.227995], "SSSSD", []],
        ["P", ["d|Y", "Number", "", "A", 3.0238389999999997], "SSSSD", []],
        ["P", ["d|Z", "Number", "", "A", -1.49012e-08], "SSSSD", []]]]]]

这个CurveNode一旦链接到Model的Lcl Translation属性,就会控制它的位置。请注意,理论上,相同的CurveNode可以控制多个对象的几个属性(但我们不会在Blender中弄乱它!)。

请注意它如何使用唯一的Compound属性,每个通道的目标属性有一个项目。因此对于translation&co,你将有三个,对于“factor”属性,你只有一个,等等。那些复合属性必须与目标属性相同,它们的值被假定为“默认”的…………这些道具的命名方案目前仍然有点模糊。

最后,AnimCurve链接到CurveNode的每个prop:

["AnimationCurve", [924545958, "::AnimCurve", ""], "LSS", [
    ["Default", [-120.30426094607161], "D", []],
    ["KeyVer", [4008], "I", []],
    ["KeyTime", [[1847446320, 3694892640, 5542338960, 7389785280, 9237231600, 11084677920, 12932124240, 14779570560, 16627016880, 18474463200, 20321909520, 22169355840, 24016802160, 25864248480, 27711694800, 29559141120, 31406587440, 33254033760, 35101480080, 36948926400, 38796372720, 90524869680]], "l", []],
    ["KeyValueFloat", [[-90.00000762939453, -90.16700744628906, -90.67182159423828, -91.51591491699219, -92.69371032714844, -94.19062042236328, -95.98118591308594, -98.02799224853516, -100.28105926513672, -102.67914581298828, -105.1521224975586, -107.62510681152344, -110.02317810058594, -112.27626037597656, -114.32304382324219, -116.11365509033203, -117.61054229736328, -118.78833770751953, -119.63243103027344, -120.13725280761719, -120.30426025390625, -120.30426025390625]], "f", []],
    ["KeyAttrFlags", [[24840]], "i", []],
    ["KeyAttrDataFloat", [[0.0, 0.0, 9.419963346924634e-30, 0.0]], "f", []],
    ["KeyAttrRefCount", [[22]], "i", []]]]

动画曲线有一个默认值(大概总是一个double?),一个版本,就像FBX中的大多数其他“数据”元素一样,是一个时间索引数组(在KTime signed int64 格式),一组值(符号浮点数 sign float),两者都定义了关键帧的基础。然后你有三个KeyAttr ……元素。

  • 第一个是标志数组KeyAttrFlags,收集关键帧的一大堆行为(插值类型等)。
  • 第二个是浮点值数组KeyAttrDataFloat,每个定义的标志四个,可能控制TBC和其他高级关键帧功能。
  • 最后KeyAttrRefCount,对于每个标志(以及匹配的浮点数据),RefCount数组包含使用它的键的数量。

因此,如果对所有键使用相同的标志,则只能将其写入一个,并在此处设置曲线中的键数。我使用(flag / floatdata)A前十个键,然后(flag / floatdata)B五个键,然后再(flag / floatdata)A十个键,你会有类似的东西(二十五个键) :

["KeyAttrFlags", [[A, B, A]], "i", []],
 ["KeyAttrDataFloat", [[A1, A2, A3, A4, B1, B2, B3, B4, A1, A2, A3, A4]], "f", []],
 ["KeyAttrRefCount", [[10, 5, 10]], "i", []]]]

现在有了一个有趣的小技巧:对于二进制格式,AnimationStack和AnimationLayer元素需要13-NULL标记,即使它们确实具有属性但没有元素(与bin FBX中的任何其他类型的元素不同,据我们所知)。