加载FBX模型文件

article/2025/9/13 11:31:23

深入理解加载FBX模型文件


每个模型文件都有自己的格式,有自研引擎的模型格式,有AutoDesk提供的模型文件格式,比如FBX模型文件,因为Unity与UE4引擎的使用而备受关注,FBX文件是AutoDesk提供的SDK,已经封装好了,我们并不能查看到其内部结构。网上也有很多关于这方面的文章,但是都没有真正解释FBX文件的内部结构,以及自己如何封装程序加载FBX模型文件。本篇博客就教给读者这两方面的知识,这样更有助于读者理解FBX文件,从而可以将FBX的加载代码移植到自己的引擎中或者自己的SDK中,当然也有助于理解Unity和UE4引擎中使用的FBX模型文件,下面我们先介绍FBX文件内部结构。

FBX文件内部结构

我们要加载模型文件,必须要清楚模型文件的内部结构,否则我们无法写程序加载模型文件,FBX模型文件是一种二进制文件,我们打开只能看到一些局部信息,无法知道它真正的内部结构,也有的文件提供了模型文件的内部结构比如OBJ文件,OBJ文件是无法导出骨骼动画的,只能是静态的,但是它文件的内部结构是可以参考的,所有模型格式的文件内部结构有自己的共性,比如模型信息:模型面数,三角形数,顶点坐标,骨骼动画信息,模型版本号信息等等,这些信息每个模型文件都会有的。 
接下来我们分析FBX文件的内部结构,我们先打开FBX模型的二进制文件: 

è¿éåå¾çæè¿°

这个是我们的FBX文件模型,虽然它是二进制的,但是我们从二进制模型文件信息中还是可以看到一点端倪的,比如FBXVersion 版本号,NodeAttributeL结点属性,ObjectTypeS 对象类型,RotationPivotS旋转信息,ScalingOffsetS缩放偏移等等,相信读者看起来还是比较费劲的。 
下面先看看我们已经实现的FBX文件内部结构,如下图所示: 

è¿éåå¾çæè¿°
 
上图显示的是我们带有动作的FBX模型文件内部结构,node attribute, animation curve node,body,skin等等,这些我们需要通过代码实现,我们使用的是C++语言,下面我们把FBX模型文件中的重要内容给读者展示如下: 

è¿éåå¾çæè¿°
 
上图是FBX文件的核心内容的标识,在具体实现时,我们可通过枚举表示,代码定义如下所示:

    enum class Type
    {
        ROOT,
        GEOMETRY,
        MATERIAL,
        MESH,
        TEXTURE,
        LIMB_NODE,
        NULL_NODE,
        NODE_ATTRIBUTE,
        CLUSTER,
        SKIN,
        ANIMATION_STACK,
        ANIMATION_LAYER,
        ANIMATION_CURVE,
        ANIMATION_CURVE_NODE
    };


下面我们告诉读者如何加载FBX模型文件,该模块也可以移植到自己的引擎或者SDK中,作者自己写的引擎使用的也是FBX SDK,用的也是本博客实现的接口模块。

加载FBX模型文件

我们先从加载FBX模型文件开始,加载模型文件的实现内容如下所示:

    FILE* fp = fopen("c.fbx", "rb");
    if (!fp) return false;

    fseek(fp, 0, SEEK_END);
    long file_size = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    auto* content = new ofbx::u8[file_size];
    fread(content, 1, file_size, fp);
    g_scene = ofbx::load((ofbx::u8*)content, file_size);

看着是不是眼熟,上面的代码我们经常用于读取二进制文件,在这段代码中最为关键的函数是load加载FBX模型文件,下面实现load函数,如下所示:

IScene* load(const u8* data, int size)
{
    std::unique_ptr<Scene> scene = std::make_unique<Scene>();
    scene->m_data.resize(size);
    memcpy(&scene->m_data[0], data, size);
    OptionalError<Element*> root = tokenize(&scene->m_data[0], size);
    if (root.isError())
    {
        Error::s_message = "";
        root = tokenizeText(&scene->m_data[0], size);
        if (root.isError()) return nullptr;
    }

    scene->m_root_element = root.getValue();
    assert(scene->m_root_element);

    if(!parseConnections(*root.getValue(), scene.get())) return nullptr;
    if(!parseTakes(scene.get())) return nullptr;
    if(!parseObjects(*root.getValue(), scene.get())) return nullptr;
    parseGlobalSettings(*root.getValue(), scene.get());

    return scene.release();
}

在load函数中,首先判断加载的文件是不是fbx文件,如果是再执行下面的操作,parseConnections和parseTakes也是针对FBX文件的验证信息,它们验证的信息,我们通过代码已经将其实现出来了,截图如下所示: 

è¿éåå¾çæè¿°
关键函数是parseObjects,这个是解释FBX文件的全部内容,首先实现的是Mesh,Material,Texture的解释,模型必须有材质贴图的,材质信息在FBX模型文件中也带有此信息的,下面的代码就是解决此问题的,接口如下:

if (iter.second.element->id == "Geometry")
        {
            Property* last_prop = iter.second.element->first_property;
            while (last_prop->next) last_prop = last_prop->next;
            if (last_prop && last_prop->value == "Mesh")
            {
                obj = parseGeometry(*scene, *iter.second.element);
            }
        }
        else if (iter.second.element->id == "Material")
        {
            obj = parseMaterial(*scene, *iter.second.element);
        }

parseGeometry函数解决的是模型的几何信息,具体实现如下所示:

static OptionalError<Object*> parseGeometry(const Scene& scene, const Element& element)
{
    assert(element.first_property);

    const Element* vertices_element = findChild(element, "Vertices");
    if (!vertices_element || !vertices_element->first_property) return Error("Vertices missing");

    const Element* polys_element = findChild(element, "PolygonVertexIndex");
    if (!polys_element || !polys_element->first_property) return Error("Indices missing");

    std::unique_ptr<GeometryImpl> geom = std::make_unique<GeometryImpl>(scene, element);

    std::vector<Vec3> vertices;
    if (!parseDoubleVecData(*vertices_element->first_property, &vertices)) return Error("Failed to parse vertices");
    std::vector<int> original_indices;
    if (!parseBinaryArray(*polys_element->first_property, &original_indices)) return Error("Failed to parse indices");

    std::vector<int> to_old_indices;
    geom->triangulate(original_indices, &geom->to_old_vertices, &to_old_indices);
    geom->vertices.resize(geom->to_old_vertices.size());

    for (int i = 0, c = (int)geom->to_old_vertices.size(); i < c; ++i)
    {
        geom->vertices[i] = vertices[geom->to_old_vertices[i]];
    }

    geom->to_new_vertices.resize(vertices.size()); // some vertices can be unused, so this isn't necessarily the same size as to_old_vertices.
    const int* to_old_vertices = geom->to_old_vertices.empty() ? nullptr : &geom->to_old_vertices[0];
    for (int i = 0, c = (int)geom->to_old_vertices.size(); i < c; ++i)
    {
        int old = to_old_vertices[i];
        add(geom->to_new_vertices[old], i);
    }

    const Element* layer_material_element = findChild(element, "LayerElementMaterial");
    if (layer_material_element)
    {
        const Element* mapping_element = findChild(*layer_material_element, "MappingInformationType");
        const Element* reference_element = findChild(*layer_material_element, "ReferenceInformationType");

        std::vector<int> tmp;

        if (!mapping_element || !reference_element) return Error("Invalid LayerElementMaterial");

        if (mapping_element->first_property->value == "ByPolygon" &&
            reference_element->first_property->value == "IndexToDirect")
        {
            geom->materials.reserve(geom->vertices.size() / 3);
            for (int& i : geom->materials) i = -1;

            const Element* indices_element = findChild(*layer_material_element, "Materials");
            if (!indices_element || !indices_element->first_property) return Error("Invalid LayerElementMaterial");

            if (!parseBinaryArray(*indices_element->first_property, &tmp)) return Error("Failed to parse material indices");

            int tmp_i = 0;
            for (int poly = 0, c = (int)tmp.size(); poly < c; ++poly)
            {
                int tri_count = getTriCountFromPoly(original_indices, &tmp_i);
                for (int i = 0; i < tri_count; ++i)
                {
                    geom->materials.push_back(tmp[poly]);
                }
            }
        }
        else
        {
            if (mapping_element->first_property->value != "AllSame") return Error("Mapping not supported");
        }
    }

    const Element* layer_uv_element = findChild(element, "LayerElementUV");
    while (layer_uv_element)
    {
        const int uv_index = layer_uv_element->first_property ? layer_uv_element->first_property->getValue().toInt() : 0;
        if (uv_index >= 0 && uv_index < Geometry::s_uvs_max)
        {
            std::vector<Vec2>& uvs = geom->uvs[uv_index];

            std::vector<Vec2> tmp;
            std::vector<int> tmp_indices;
            GeometryImpl::VertexDataMapping mapping;
            if (!parseVertexData(*layer_uv_element, "UV", "UVIndex", &tmp, &tmp_indices, &mapping)) return Error("Invalid UVs");
            if (!tmp.empty())
            {
                uvs.resize(tmp_indices.empty() ? tmp.size() : tmp_indices.size());
                splat(&uvs, mapping, tmp, tmp_indices, original_indices);
                remap(&uvs, to_old_indices);
            }
        }

        do
        {
            layer_uv_element = layer_uv_element->sibling;
        } while (layer_uv_element && layer_uv_element->id != "LayerElementUV");
    }

    const Element* layer_tangent_element = findChild(element, "LayerElementTangents");
    if (layer_tangent_element)
    {
        std::vector<Vec3> tmp;
        std::vector<int> tmp_indices;
        GeometryImpl::VertexDataMapping mapping;
        if (findChild(*layer_tangent_element, "Tangents"))
        {
            if (!parseVertexData(*layer_tangent_element, "Tangents", "TangentsIndex", &tmp, &tmp_indices, &mapping)) return Error("Invalid tangets");
        }
        else
        {
            if (!parseVertexData(*layer_tangent_element, "Tangent", "TangentIndex", &tmp, &tmp_indices, &mapping))  return Error("Invalid tangets");
        }
        if (!tmp.empty())
        {
            splat(&geom->tangents, mapping, tmp, tmp_indices, original_indices);
            remap(&geom->tangents, to_old_indices);
        }
    }

    const Element* layer_color_element = findChild(element, "LayerElementColor");
    if (layer_color_element)
    {
        std::vector<Vec4> tmp;
        std::vector<int> tmp_indices;
        GeometryImpl::VertexDataMapping mapping;
        if (!parseVertexData(*layer_color_element, "Colors", "ColorIndex", &tmp, &tmp_indices, &mapping)) return Error("Invalid colors");
        if (!tmp.empty())
        {
            splat(&geom->colors, mapping, tmp, tmp_indices, original_indices);
            remap(&geom->colors, to_old_indices);
        }
    }

    const Element* layer_normal_element = findChild(element, "LayerElementNormal");
    if (layer_normal_element)
    {
        std::vector<Vec3> tmp;
        std::vector<int> tmp_indices;
        GeometryImpl::VertexDataMapping mapping;
        if (!parseVertexData(*layer_normal_element, "Normals", "NormalsIndex", &tmp, &tmp_indices, &mapping)) return Error("Invalid normals");
        if (!tmp.empty())
        {
            splat(&geom->normals, mapping, tmp, tmp_indices, original_indices);
            remap(&geom->normals, to_old_indices);
        }
    }

    return geom.release();
}
它主要解决的问题是读取模型的顶点,多边形索引,法线,切线,UV等模型信息。再看看parseMaterial函数实现的内容如下所示:

static OptionalError<Object*> parseMaterial(const Scene& scene, const Element& element)
{
    MaterialImpl* material = new MaterialImpl(scene, element);
    const Element* prop = findChild(element, "Properties70");
    material->diffuse_color = { 1, 1, 1 };
    if (prop) prop = prop->child;
    while (prop)
    {
        if (prop->id == "P" && prop->first_property)
        {
            if (prop->first_property->value == "DiffuseColor")
            {
                material->diffuse_color.r = (float)prop->getProperty(4)->getValue().toDouble();
                material->diffuse_color.g = (float)prop->getProperty(5)->getValue().toDouble();
                material->diffuse_color.b = (float)prop->getProperty(6)->getValue().toDouble();
            }
        }
        prop = prop->sibling;
    }
    return material;
}

以上实现的内容效果如下所示: 
 è¿éåå¾çæè¿°
这样我们把FBX模型文件的关键信息就一一解释出来了,在这里还有一个很重要的就是骨骼动画信息,并且FBX也有变形的实现,在这里我们都做了一一解释,代码如下所示:

else if (iter.second.element->id == "AnimationStack")
        {
            obj = parse<AnimationStackImpl>(*scene, *iter.second.element);
            if (!obj.isError())
            {
                AnimationStackImpl* stack = (AnimationStackImpl*)obj.getValue();
                scene->m_animation_stacks.push_back(stack);
            }
        }
        else if (iter.second.element->id == "AnimationLayer")
        {
            obj = parse<AnimationLayerImpl>(*scene, *iter.second.element);
        }
        else if (iter.second.element->id == "AnimationCurve")
        {
            obj = parseAnimationCurve(*scene, *iter.second.element);
        }
        else if (iter.second.element->id == "AnimationCurveNode")
        {
            obj = parse<AnimationCurveNodeImpl>(*scene, *iter.second.element);
        }
        else if (iter.second.element->id == "Deformer")
        {
            IElementProperty* class_prop = iter.second.element->getProperty(2);

            if (class_prop)
            {
                if (class_prop->getValue() == "Cluster")
                    obj = parseCluster(*scene, *iter.second.element);
                else if (class_prop->getValue() == "Skin")
                    obj = parse<SkinImpl>(*scene, *iter.second.element);
            }
        }

对应的实现效果如下所示: 

è¿éåå¾çæè¿°
 
上面显示的是Take001动画文件信息,当然并不只限于以上信息,还有动作文件名字,动作播放动画时间信息,实现的效果如下所示: 

è¿éåå¾çæè¿°
 
以上关于FBX文件关键信息解释,另外还有很多细节函数实现,在这里就不一一给读者展示了,文章最后会把代码给读者,我们封装的模块可以直接用于DX或者OpenGL的加载的,我们上面用于演示的FBX动作文件在Unity中的显示如下所示: 

è¿éåå¾çæè¿°
 
我们的代码是用VS2017实现的,代码下载地址: 
链接:https://pan.baidu.com/s/1q5vvdrkr5VBKdtWbw2UUBg 密码:7mvi

https://github.com/Larry955/FbxParser


http://chatgpt.dhexx.cn/article/ElD1pIeh.shtml

相关文章

FBX格式转换为GLTF/GLB格式

有小伙伴说通过blende将fbx转glb/gltb格式的模型无法在web端加载&#xff0c;或glb模型无法打开&#xff0c;比如腾讯地图加载gltf。 这里个大家分享一个插件 可以将fbx格式转换为glb格式 window版本 链接&#xff1a;https://pan.baidu.com/s/17wwI-hmezg9-sOnHZNn_uw?pwd1…

FBX文件导入Unity导致贴图丢失问题解决,以3ds max,Blender导入导出为例

一 . 首先把资产文件导入三维软件中 1 . 3ds Max 1.1 导入FBX——【免费或者质量差的资源可能会出现平滑组错误的情况&#xff0c;可以直接镜像一个模型更改法线或者删除面重新封口能解决问题】 1.2 导出FBX至Unity引擎 1.2.1 导出文件预设 注意【导出之后会产生一个FBX文件和流…

基于FBX SDK的FBX模型解析与加载 -(一)

1. 简介 FBX是Autodesk的一个用于跨平台的免费三维数据交换的格式&#xff08;最早不是由Autodesk开发&#xff0c;但后来被其收购&#xff09;&#xff0c;目前被 众多的标准建模软件所支持&#xff0c;在游戏开发领域也常用来作为各种建模工具的标准导出格式。Autodesk提供…

Unity场景素材导出为 FBX文件的方法

系列文章目录 一、Unity场景素材导出为 FBX文件的方法&#xff1a;http://t.csdn.cn/Xyjxe 二、Unity场景素材导出为 OBJ文件的方法&#xff1a;http://t.csdn.cn/08RY3 三、Unity地形导出为 OBJ文件的方法 &#xff08;大家可以打开我的博客主页进行查看此系列其它文章&…

Unity导入FBX动画文件

给刚入门的同学稍微记一下fbx格式动画导入unity的步骤&#xff1a; 首先直接把fbx文件拖入unity内&#xff0c;如图操作 将avatar骨骼创建之后&#xff0c;我们接下来就根据自己对动画的一些需求设置一下动画的参数&#xff0c;比如截取一下动画的开始帧和结束帧来达到剪切的目…

深入理解加载FBX模型文件

每个模型文件都有自己的格式&#xff0c;有自研引擎的模型格式&#xff0c;有AutoDesk提供的模型文件格式&#xff0c;比如FBX模型文件&#xff0c;因为Unity与UE4引擎的使用而备受关注&#xff0c;FBX文件是AutoDesk提供的SDK&#xff0c;已经封装好了&#xff0c;我们并不能查…

FBX模型

概览 fbx文件&#xff0c;一般是导出给unity使用的模型文件。 如下图所示&#xff0c;建立一个models目录&#xff0c;然后右击&#xff0c;选择 imoprt new asserts 即可导入这些文件。 展示如下&#xff0c;Mesh定义了形状。 铅笔也是同理&#xff0c;只不过铅笔有自己的贴图…

FBX文件简述

1&#xff0e; 关于FBX Autodesk FBX是Autodesk公司出品的一款用于跨平台的免费三维创作与交换格式的软件&#xff0c;通过FBX用户能访问大多数三维供应商的三维文件。FBX 文件格式支持所有主要的三维数据元素以及二维、音频和视频媒体元素。 FBX由Kaydara开发并于2006年被AUT…

fbx格式研究

Part1fbx简介 Autodesk FBX是Autodesk公司出品的一款用于跨平台的免费三维创作与交换格式的软件&#xff0c;通过FBX用户能访问大多数三维供应商的三维文件。FBX 文件格式支持所有主要的三维数据元素以及二维、音频和视频媒体元素。FBX对于三维软件的兼容性非常非常强大&#x…

matlab 画图的颜色

plot函数代表不同颜色的标示符一共有八种&#xff1a; y&#xff1a;黄色&#xff1b;k&#xff1a;黑色&#xff1b;w&#xff1a;白色&#xff1b;b&#xff1a;蓝色&#xff1b;g&#xff1a;绿色&#xff1b;r&#xff1a;红色&#xff1b;c&#xff1a;亮青色&#xff1b…

Matlab画图线型、符号及颜色汇总

【1】线型、标记符、颜色的说明 【2】对于坐标轴的注释内容xlabel,ylabel的属性说明 figure,plot(Seg1,SS1_QJ1,k);hold onplot(Seg1,SS1_QJ1,ks)plot(Seg1,Q1*ones(length(Seg1)),r)xlabel(\bf{安装角}(),FontSize,10.508) %\bf是加粗的意思%xlabel(\fontsize{10.508}\fontn…

matlab绘图颜色RGB

一.1.MATLAB中颜色数值 二.常用颜色

matlab中的颜色,Matlab里面除了常用的还有哪些颜色?已知的颜色不够怎么办?

我这里有个自己写的函数&#xff0c;支持64个色 %% H:取色函数 % INTRODUCTORY TEXT %标题综述************************ %调用默认色图可实现64色的选取 %输入**************************** %①所选颜色个数 %输出**************************** %①待用颜色的RGB矩阵 %调用流程…

Matlab中绘制颜色渐变曲线

大家在文章中是否看见过各类颜色渐变的曲线呢&#xff1f;今天小编就教大家在matlab中如何绘制这类颜色渐变的曲线。 一、scatter3函数 这个函数是大家最常想到的函数&#xff0c;具体用法就看其帮助文档&#xff0c;小编就给出一段示例代码&#xff1a; % Matlab% data z …

matlab画图配色RGB+线性

转载自https://blog.csdn.net/a_cherry_blossoms/article/details/115185831 画图的线型和符号该博主链接中可以找到&#xff1a;https://blog.csdn.net/weixin_45634606/article/details/104422695 1.使用matlab画图时&#xff0c;怎样才能调出顺眼的颜色呢&#xff1f;反正我…

matlab中surf怎么改变颜色_MATLAB作图

未完,之后将逐渐更新 %%2D作图 1、极坐标转直角坐标 1.1直接用公式 利用极坐标转直接坐标公式 thet=0:2*pi/100:2*pi; rho=1; x=rho.*cos(thet); y=rho.*sin(thet); plot(x,y); 1.2 用pol2cart [x,y]=pol2cart(thet,rho);相当于1.1中第三第四行代码 同理直角坐标转极坐标用car…

Matlab 绘图 曲线颜色自定义

Matlab 绘图 曲线颜色自定义 同时组合图内间距小 确定绘制曲线的RGB颜色根据RGB颜色将其除以255归一化到0-1&#xff0c;plot中选择属性’color’为RGB值即可。示例如下 rn 2; % The number of rows cn 2; % The number of columns% color1 [34 139 34]; % color2 [255…

matlab 中曲线颜色,matlab曲线颜色样式设置

满意答案 南渡江ndj 2013.09.03 采纳率:43% 等级:12 已帮助:19801人 你好 这是我总结的画图资料 比如画一条蓝色的x号线 plot(x,y,bg) 画图: 线形:-实线 -. 点划线 --长虚线 :短虚线 符号 颜色 符号 线形 b 蓝 . 点 c 青 。 圈 g 绿 标记 k 黑 - 实线 m 紫红 * 星号 r 红…

MATLAB画图颜色渐变

原文&#xff08;https://wenku.baidu.com/view/bf87a9d5804d2b160a4ec03f.html&#xff09; 1 渐变 1.1 t[linspace(0,2*pi) nan]; xsin(t);ycos(2*t);zsqrt(t);%所要绘制的曲线方程 patch(x,y,z,z,edgecolor,flat,facecolor,none) view(3);grid on;colorbar 1.2 x-200…

[Matlab]绘图颜色

[Matlab]绘图颜色 修改或规定Matlab中几何图形的颜色&#xff0c;对颜色可以有四种描述方法&#xff0c;分别是&#xff1a;颜色名称、短名称、RGB三元组、十六进制颜色代码。 Matlab中较美观的颜色(RGB三元组) %% 适用于Matlab的RGB颜色 [0.00,0.45,0.74] //蓝 [0.85,0.33,…