在之前的DX11入门系列文章中,有篇有关 Directx11教程四十之加载OBJ模型 读取obj模型数据的博客。不过在obj读取的那篇博客我有些坑并没有说,就是我写的那个obj解析器只能解析特定的obj文件格式,因为后面我用我写的obj模型解析器发现根本无法解析大多数的obj文件格式,真是让人崩溃,因为那时的我是分析特定的一两个obj文件写出来的解析器,实际上obj文件数据的多样性超越我的想象。所以我不推荐大家在使用obj了,请远离obj。 obj能直接查看文本数据,但并不好用。我们紧跟商业引擎的步伐,用FBX格式。

惯例,放出本篇博客有关程序对应的结构:

关于商业引擎与FBX SDK

我们知道UE4,U3D导入模型是FBX文件,也就是说FBX文件是UE4,U3D的中间模型文件,为什么特意加个“中间”呢?那是因为FBX模型文件仅仅是UE4,U3D的导入模型文件,并不是UE4,U3D游戏运行时加载的模型文件。像UE4,U3D这样的引擎导入FBX文件会生成自己的自定义模型文件。这是为什么呢?用过FBX SDK读取模型数据你就会发现使用 FBX SDK 读取FBX模型文件的直接得到的几何数据(例如顶点数据)存在一定(有时候甚至可以说是严重的)的冗余,并且读取的速度也不够快,因此FBX文件只是商业引擎的中间模型文件。当然我们是DX11教程做案例,没必要搞那么复杂,直接用FBX SDK读取FBX文件的数据作为渲染数据来使用。

FBX SDK的环境配置。

我用的是VS2017和 FBX  SDK2017.1,

FBX SDK2017.1下载地址:http://usa.autodesk.com/adsk/servlet/pc/item?siteID=123112&id=26012646

相应的开发文档:http://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_6E7B1A2E_584C_49C6_999E_CD8367841B7C_htm 可以跟着文档来配置环境

因为用的是VS2017,相应的FBX SDK2017也只给出了VS2015,VS2013的编译版本,环境配置方案有三种,我三种都试过了,发现VS2017可以使用DLL配置的那种。

FBX 文件的读取

关于FBX数据的读取 这里我就不献丑了,直接引用前人写的博客就行了。下面三篇有关FBX读取数据的博客强烈推荐:

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

2.基于FBX SDK的FBX模型解析与加载 -(二)

3.Working with FBX SDK (2)

4.解析 FBX 模型文件作为 Direct3D 的渲染模型

跟着这三篇博客基本上能理解基本的FBX SDK读取数据的概念。

这里唯一要纠结的是,材质的读取接口已经发生改变了,我们因爲需要在DX11读取文件,就得获取纹理文件的相对路径(不推荐用绝对路径),采用的接口是FBXFileTexture而并非FBXTexture,如下所示:

void ImportFBX::ReadReletiveTextureFileName(FbxProperty* mproperty,int materialIndex, map<int, Material>& materialMap)
{if (!mproperty || !mproperty->IsValid()){return;}string name = mproperty->GetName();bool isNeedTexture = false;isNeedTexture = (name == FbxSurfaceMaterial::sDiffuse) || (name == FbxSurfaceMaterial::sSpecular)|| (name == FbxSurfaceMaterial::sTransparentColor) || (name == FbxSurfaceMaterial::sBump);if (!isNeedTexture){return;}int textureNum = mproperty->GetSrcObjectCount<FbxFileTexture>();//现在每种纹理仅仅读取一个if (textureNum > 0){FbxFileTexture* fbxFileTexture = mproperty->GetSrcObject<FbxFileTexture>(0);string  relativeFileName = fbxFileTexture->GetRelativeFileName();size_t tgaTagPos = relativeFileName.find(".tga");if (tgaTagPos != string::npos){relativeFileName = relativeFileName.substr(0, tgaTagPos);relativeFileName += string(".jpg");}string fileName = fbxFileNamePre + relativeFileName;if (name == FbxSurfaceMaterial::sDiffuse){materialMap[materialIndex].diffuseMapFileName = fileName;}else if (name == FbxSurfaceMaterial::sSpecularFactor){materialMap[materialIndex].specularMapFileName = fileName;}else if (name == FbxSurfaceMaterial::sTransparentColor){materialMap[materialIndex].alphaMapFileName = fileName;}else if (name == FbxSurfaceMaterial::sBump){materialMap[materialIndex].bumpMapFileName = fileName;}}}

好吧,这里我只读取diffuseTexture,NormalTexture,SpecularTexture,AlphaTexture,并且DXUT由于无法加载.tga格式的纹理,我转为.tga文件后缀名为.jpg,并且将相应的FBX文件的所有.tga图片在PS里转为了.jpg文件。

读取FBX的数据结构:

我们知道FBX的节点是以树的形状来组织的,因此用树来组织是再好不过的,当然我为了教程的简易,只是用了数组来组织。

1.顶点结构如下,没多少好说的

struct VertexPCNTT
{XMFLOAT3 pos;XMFLOAT3 color;XMFLOAT3 normal;XMFLOAT3 tangent;XMFLOAT2 uv;
};

2. 三角形结构,因为在fbx的fbxNode节点中,读取的每个三角形都有一个材质id,索引到相应的材质,相应的材质蕴含相应的相应各种属性(Diffuse,Specular,以及各种纹理)  。 这里得注意一点,材质id是绑定于相应的节点的,比如说fbxNode1和fbxNode2的同一个材质id(例如都为0或者1之类的)是完全不存在关系的,材质id仅仅针对于绑定的节点。

struct Triangle
{VertexPCNTT vertexs[3];int MaterialId;
};

3. 材质结构,这里我们的材质直接用纹理文件名来表示,当读取到相应的纹理文件名为空时,就代表不存在相应纹理,反之则存在相应纹理。

struct Material
{string diffuseMapFileName;string specularMapFileName;string alphaMapFileName;string bumpMapFileName;
};

4. mesh结构,我们从前面的四篇博客可以知道,一个mesh的节点读取的所有三角形存在可能不只一个材质id,而每个材质id是绑定于相应的fbxNode的,因为一个mesh类型的fbxNode或者说fbxMesh存在多少个材质id,则就存在多少个mesh.(这里不可能一个三角形就是一个mesh,drawCall得吓死人,所以得根据在读取fbxMesh数据的时候,把材质id相同的三角形归为同一个mesh)。总体来说 一个fbxMesh存在多少个材质id,就生成多少个mesh,也就是一个mesh存在一种材质id

struct Mesh
{vector<VertexPCNTT> mVertexData;vector<WORD> mIndexData;int materialId;ID3D11Buffer* mVertexBuffer;ID3D11Buffer* mIndexBuffer;
};

5.model结构, 刚才我们说过一个mesh的fbxNode或者说fbxMesh可以解析出多个mesh,则一个fbxNode的所有mesh解析成了model.不过得注意我在model结构添加了材质哈希表,mesh可以通过自身的材质id,在model结构找到相应的材质(相应的纹理相对路径。

struct Model
{vector<Mesh> mMeshList;map<int, Material> mMaterialMap;
};

6.FBXModel结构,我们上面说model为一个节点解析出来的结构,而一个fbx文件如果含有n多个分fbxNode(fbxMesh),也就是生成了n多个Model结构体,我解析其为FBXModel.注意我用了一个哈希表来查询相应的 ID3D11ShaderResourceView* 资源,这个mSRVMap的键为纹理的相对路径,可能看到这你可豁然开朗。

//保证了一个FBX加载的所有纹理文件都仅仅加载一次
struct FBXModel
{vector<Model> mModelList;map<string, ID3D11ShaderResourceView*> mSRVMap;
};

这里思路很明确,在渲染的时候,我们是以mesh为个体,一个一个进行渲染的,思路过程:

(1) 设置mesh的顶点缓存和索引缓存

(2) 根据 mesh 的材质MaterialId在相应的 model 材质名哈希表 找到相应的Material,最后用Material对应的四种纹理相应路径名在FBXModel 材质哈希表找到相应的ID3D11ShaderResourceView* 资源,然后用相应的Shader进行渲染,好吧,一切已然豁然开朗。

FBX数据读取的坐标系空间纠正:

因为我们是在3DS MAX建模的,而我们3DS MAX的坐标系空间如下:

3DS MAX的坐标系空间是Z轴向上的右手坐标系,而D3D11的是Y轴向上的左手坐标系。因此如果你直接拿FBX读取的数据渲染,会发现模型 往往是躺着的。我采用了绕X轴渲染 -90度 的办法,对读取的顶点数据做如下变换:

1.位置(XMMatrixRotationX(-XM_PI/2.0)),

//由于3DS MAX里坐标轴为Z轴向上的,Y轴向里的右手坐标系, D3D11为左手坐标系
//参考https://www.cnblogs.com/wantnon/p/4372764.html
void ImportFBX::ReadVertexPos(FbxMesh* mesh, int ctrlPointIndex, XMFLOAT3* pos)
{FbxNode* meshNode = mesh->GetNode();FbxAnimEvaluator* lEvaluator = mScene->GetAnimationEvaluator();FbxMatrix lGlobal;lGlobal.SetIdentity();lGlobal = lEvaluator->GetNodeGlobalTransform(meshNode);FbxDouble3 scaling = meshNode->LclScaling.Get();FbxVector4 * ctrPoints = mesh->GetControlPoints();pos->x = ctrPoints[ctrlPointIndex][0] * scaling[0];pos->y = ctrPoints[ctrlPointIndex][2] * scaling[2];pos->z = -ctrPoints[ctrlPointIndex][1] * scaling[1];}

2.顶点法线(顶点位置变换的逆反矩阵变换)

void ImportFBX::ReadVertexNormal(FbxMesh* mesh, int ctrlPointIndex, int vertexCount, XMFLOAT3* normal)
{if (mesh->GetElementNormalCount() < 1){return;}FbxGeometryElementNormal* vertexNormal = mesh->GetElementNormal(0);switch (vertexNormal->GetMappingMode()){case FbxGeometryElement::eByControlPoint:{switch (vertexNormal->GetReferenceMode()){case FbxGeometryElement::eDirect:{normal->x = vertexNormal->GetDirectArray().GetAt(ctrlPointIndex).mData[0];normal->y = vertexNormal->GetDirectArray().GetAt(ctrlPointIndex).mData[2];normal->z = -vertexNormal->GetDirectArray().GetAt(ctrlPointIndex).mData[1];}break;case FbxGeometryElement::eIndexToDirect:{int id = vertexNormal->GetIndexArray().GetAt(ctrlPointIndex);normal->x = vertexNormal->GetDirectArray().GetAt(id).mData[0];normal->y = vertexNormal->GetDirectArray().GetAt(id).mData[2];normal->z = -vertexNormal->GetDirectArray().GetAt(id).mData[1];}break;default:break;}}break;case FbxGeometryElement::eByPolygonVertex:{switch (vertexNormal->GetReferenceMode()){case FbxGeometryElement::eDirect:{normal->x = vertexNormal->GetDirectArray().GetAt(vertexCount).mData[0];normal->y = vertexNormal->GetDirectArray().GetAt(vertexCount).mData[2];normal->z = -vertexNormal->GetDirectArray().GetAt(vertexCount).mData[1];}break;case FbxGeometryElement::eIndexToDirect:{int id = vertexNormal->GetIndexArray().GetAt(vertexCount);normal->x = vertexNormal->GetDirectArray().GetAt(id).mData[0];normal->y = vertexNormal->GetDirectArray().GetAt(id).mData[2];normal->z = -vertexNormal->GetDirectArray().GetAt(id).mData[1];}break;default:break;}break;}}
}

3.顶点切线(跟顶点位置变换一致)

void ImportFBX::ReadVertexTangent(FbxMesh* mesh, int ctrlPointIndex, int vertexCount, XMFLOAT3* tangent)
{if (mesh->GetElementTangentCount() < 1){return;}FbxGeometryElementTangent* vertexTangent = mesh->GetElementTangent(0);switch (vertexTangent->GetMappingMode()){case FbxGeometryElement::eByControlPoint:{switch (vertexTangent->GetReferenceMode()){case FbxGeometryElement::eDirect:{tangent->x = vertexTangent->GetDirectArray().GetAt(ctrlPointIndex).mData[0];tangent->y = vertexTangent->GetDirectArray().GetAt(ctrlPointIndex).mData[2];tangent->z = -vertexTangent->GetDirectArray().GetAt(ctrlPointIndex).mData[1];}break;case FbxGeometryElement::eIndexToDirect:{int id = vertexTangent->GetIndexArray().GetAt(ctrlPointIndex);tangent->x = vertexTangent->GetDirectArray().GetAt(id).mData[0];tangent->y = vertexTangent->GetDirectArray().GetAt(id).mData[2];tangent->z = -vertexTangent->GetDirectArray().GetAt(id).mData[1];}break;default:break;}}break;case FbxGeometryElement::eByPolygonVertex:{switch (vertexTangent->GetReferenceMode()){case FbxGeometryElement::eDirect:{tangent->x = vertexTangent->GetDirectArray().GetAt(vertexCount).mData[0];tangent->y = vertexTangent->GetDirectArray().GetAt(vertexCount).mData[2];tangent->z = -vertexTangent->GetDirectArray().GetAt(vertexCount).mData[1];}break;case FbxGeometryElement::eIndexToDirect:{int id = vertexTangent->GetIndexArray().GetAt(vertexCount);tangent->x = vertexTangent->GetDirectArray().GetAt(id).mData[0];tangent->y = vertexTangent->GetDirectArray().GetAt(id).mData[2];tangent->z = -vertexTangent->GetDirectArray().GetAt(id).mData[1];}break;default:break;}break;}}
}

4.顶点UV,u2 = u1, v2 = 1.0 - v1,则就是U不变,而v反过来(DX11与OPenGL相反)。

void ImportFBX::ReadVertexUV(FbxMesh* mesh, int ctrlPointIndex, int uvIndex, XMFLOAT2* uv)
{if (mesh->GetElementUVCount() < 1){return;}FbxGeometryElementUV* vertexUV = mesh->GetElementUV(0);switch (vertexUV->GetMappingMode()){case FbxGeometryElement::eByControlPoint:{switch (vertexUV->GetReferenceMode()){case FbxGeometryElement::eDirect:{//因为这些这些数据个Opengl坐标一样,而我们是需要在D3D11里渲染的数据,所以部分数据得改变//v反转uv->x = vertexUV->GetDirectArray().GetAt(ctrlPointIndex).mData[0];uv->y = 1.0f - vertexUV->GetDirectArray().GetAt(ctrlPointIndex).mData[1];}break;case FbxGeometryElement::eIndexToDirect:{int id = vertexUV->GetIndexArray().GetAt(ctrlPointIndex);uv->x = vertexUV->GetDirectArray().GetAt(id).mData[0];uv->y = 1.0f - vertexUV->GetDirectArray().GetAt(id).mData[1];}break;default:break;}}break;case FbxGeometryElement::eByPolygonVertex:{switch (vertexUV->GetReferenceMode()){case FbxGeometryElement::eDirect:case FbxGeometryElement::eIndexToDirect:{uv->x = vertexUV->GetDirectArray().GetAt(uvIndex).mData[0];uv->y = 1.0f - vertexUV->GetDirectArray().GetAt(uvIndex).mData[1];}break;default:break;}break;}}
}

最终渲染结果:

参考资料:

【1】Working with FBX SDK (2)

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

【3】基于FBX SDK的FBX模型解析与加载 -(二)

【4】FBX 2017.1开发者文档

源码链接:

集成了FBX SDK的3D渲染引擎源码:

https://github.com/2047241149/SDEngine

未来待提高

【1】命名有问题,不过懒得改了,Mesh应该改为SubMesh, Model改为Mesh,FBXModel改为Model

【2】FBXModel管理Model更应该用树结构,而非vector数组。

【3】纹理哈希表不应该与FBXModel绑定在一起,因为加载的多个FBX模型很有可能存在相同的加载纹理或者说是相同的纹理相对路径名。更应该建立一个全局的纹理管理类,避免纹理加载资源的重复性。

【4】顶点数据存在大量的冗余,可以自定义文件来存储模型数据,FBX还是作为导入模型比较合适,不适合作为游戏运行时的加载模型文件。

Directx11教程四十六之FBX SDK相关推荐

  1. Android入门教程四十六之ViewFlipper(翻转视图)的基本使用

    本节给大家带了的是ViewFlipper,它是Android自带的一个多页面管理控件,且可以自动播放! 和ViewPager不同,ViewPager是一页页的,而ViewFlipper则是一层层的,和 ...

  2. SpringBoot 系列教程(四十六):SpringBoot集成i18n国际化配置

    一.概述 软件的国际化:软件开发时,要使它能同时应对世界不同地区和国家的访问,并针对不同地区和国家的访问,提供相应的.符合来访者阅读习惯的页面或数据. 国际化(internationalization ...

  3. Spring Boot入门教程(四十六): @Async

    一:简介 ThreadPoolTaskExecutor 用于定义线程池,是对java.util.concurrent.ThreadPoolExecutor类的包装.可以通过@EnableAsync来开 ...

  4. Senparc.Weixin.MP SDK 微信公众平台开发教程(十六):AccessToken自动管理机制

    在<Senparc.Weixin.MP SDK 微信公众平台开发教程(八):通用接口说明>中,我介绍了获取AccessToken(通用接口)的方法. 在实际的开发过程中,所有的高级接口都需 ...

  5. 【Visual C++】游戏开发笔记四十六 浅墨DirectX教程十四 模板测试与镜面特效专场

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处.   文章链接: http://blog.csdn.net/zhmxy555/article/details/8632184 作者:毛星云( ...

  6. 四十六、深入Java的网络编程(下篇)

    @Author:Runsen @Date:2020/6/9 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏艰 ...

  7. [系统安全] 四十六.Powershell恶意代码检测系列 (1)Powershell基础入门及管道和变量的用法

    您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列.因此,我重新开设了这个专栏,准备系统整理和深入学习系统安全.逆向分 ...

  8. SAP UI5 应用开发教程之六十六 - 基于 OData V4 的 SAP UI5 表格控件如何实现删除功能试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

  9. SAP UI5 应用开发教程之五十六 - SAP UI5 树控件(tree)的开发试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

最新文章

  1. 系统文件打开检测脚本
  2. Python中super()和__init__()方法
  3. 神策数据出席 TIC 2018 大会,共同赋能大数据时代
  4. 技术干货|如何在企业内部实现云信私有化?
  5. C++继承时名字的遮蔽
  6. Python开发中有可能遇到的套接字重复使用错误
  7. 还没使用过Web Worker? 推荐一款开源工具Workerize-Loader,让你在webpack项目中轻松使用Web Worker
  8. java查找网站在百度排名_百度网站快排系统 - 网站排名如何优化?
  9. 【英语学习】【English L06】U08 News L5 They are expecting a baby!
  10. word2vec C源码解析
  11. select模型使用例子
  12. Android开发之实现多次点击事件
  13. Air Keyboard
  14. 不二越机器人编程手册_NACHI机器人说明书
  15. 球半篮球比分,西篮甲:沙萨基 VS 华伦西亚 5月31日
  16. 计算机的运行英文表示,电脑一些英文表示什么格式
  17. iOS读懂崩溃日志,解析崩溃日志
  18. 基于Java毕业设计在线答题系统源码+系统+mysql+lw文档+部署软件
  19. Python爬虫-利用xpath解析爬取58二手房详细信息
  20. js 计算当前时间和和一段时候后的工作日天数,排除周末和法定假日

热门文章

  1. java gette_Java setter,getter(滚压模具)
  2. 【PS】海报设计,滤镜
  3. SC7A20获取三轴加速度值
  4. 《Java程序设计与数据结构教程(第二版)》学习指导
  5. 1bit quantization
  6. ios关联启动_部落冲突电脑版与IOS设备关联教程
  7. 拼接模型坐标系的理解(五)
  8. 计算机硬件介绍之CPU与多线程
  9. Qt中如何做出CSS那样华丽质感以及3D立体感的按钮?
  10. 如何在微信小程序里面退出小程序