用FBX_SDK读取网格数据

1.前言

前一篇讲了如何配置FBX_SDK,这一篇来看如何读取模型的网格数据,效果如下:

2.导出

首先随便创建一个场景,然后导出:

在导出的时候勾选上三角算法,将网格全部以三角形面的形式存储,这样方便DirectX的API使用:

有些模型在导出三角面时会失败,我在3ds max里转化为可编辑多边形后就导出成功了。

当然也可以在代码中执行转换的操作,不过编辑器里都会转换失败,代码就也很可能转换失败,所以我还是在编辑器里导出时就转换好。

3.具体代码

DX需要的是顶点缓存和索引缓存,所以先定义一个类来缓存数据,如果以后有需要也可以方便实现自己的3D模型中转文件,这样不仅加快读取速度,并且可以避免版权问题。

所以如下,先用两个vector保存顶点和索引:

class MeshObject
{
public:UINT64 GetVertexDataSize(){return _vecVertex.size() * sizeof(DirectX::Vertex);}UINT8* GetVertexData(){return (UINT8*)(_vecVertex.data());}UINT64 GetIndexDataSize(){return _vecIndex.size() * sizeof(UINT32);}UINT8* GetIndexData(){return (UINT8*)(_vecIndex.data());}vector<Vertex> _vecVertex;vector<UINT32> _vecIndex;
};

先初始化FBX_SDK,设置iosetting,创建一个importer

// Initialize the SDK manager. This object handles all our memory management.
FbxManager* _lSdkManager = FbxManager::Create();// Create the IO settings object.
FbxIOSettings* ios = FbxIOSettings::Create(_lSdkManager, IOSROOT);
_lSdkManager->SetIOSettings(ios);// Create an importer using the SDK manager.
FbxImporter* lImporter = FbxImporter::Create(_lSdkManager, "");

然后用importer加载文件:

if (!lImporter->Initialize(lFilename, -1, _lSdkManager->GetIOSettings()))
{//error
}

接着用importer创建一个scene,然后释放importer,遍历scene的根节点,完成后再释放内存:

// Create a new scene so that it can be populated by the imported file.
FbxScene* lScene = FbxScene::Create(_lSdkManager, "myScene");// Import the contents of the file into the scene.
lImporter->Import(lScene);// The file is imported; so get rid of the importer.
lImporter->Destroy();// Print the nodes of the scene and their attributes recursively.
// Note that we are not printing the root node because it should
// not contain any attributes.
MeshObject* ret = nullptr;
FbxNode* lRootNode = lScene->GetRootNode();
if (lRootNode) {ret = _init_mesh_object(lRootNode);/*for (int i = 0; i < lRootNode->GetChildCount(); i++)PrintNode(lRootNode->GetChild(i));*/
}
// Destroy the SDK manager and all the other objects it was handling.
_lSdkManager->Destroy();

_init_mesh_object函数,我们传入根节点,然后读取了网格数据。从root_node遍历有可能得到多个Mesh,下面的代码处理了一下,将多个Mesh放到了一起,当然也可以分开放:

DND::MeshObject* DNDFBX::_init_mesh_object(FbxNode* root_node)
{if (!root_node)return nullptr;MeshObject* ret = new MeshObject;int last_index = 0;//上一个模型的最大索引for (int i = 0; i < root_node->GetChildCount(); i++){FbxNode* p_node = root_node->GetChild(i);for (int j = 0; j < p_node->GetNodeAttributeCount(); j++){FbxNodeAttribute* p_attribute = p_node->GetNodeAttributeByIndex(j);if (p_attribute->GetAttributeType() == FbxNodeAttribute::eMesh){FbxMesh* mesh = p_attribute->GetNode()->GetMesh();if (mesh == NULL){return nullptr;}//数据是以形状存储的,会有重复的顶点,所以需要计算索引缓存int count_polygon = mesh->GetPolygonCount();//g_debug.Line(to_wstring(count_polygon));ret->_vecVertex.resize(last_index + count_polygon * 3);//已初始化标记vector<bool> vec_inited;vec_inited.resize(count_polygon * 3, false);int max_index = 0;for (int k = 0; k != count_polygon; ++k){if (mesh->GetPolygonSize(k) != 3){g_debug.Line(L"模型数据未三角化!");continue;}FbxVector4* ctrl_point = mesh->GetControlPoints();for (int l = 0; l != 3; ++l){int index = mesh->GetPolygonVertex(k, l);if (index == -1){g_debug.Line(L"获取顶点失败!");continue;}max_index = max(index, max_index);//依次记录顶点的索引,而顶点数据只存放一次//g_debug.Line(to_wstring(index));//g_debug.Line(String::Format(L"(%.0lf)(%.0lf)(%.0lf)", ctrl_point[index][0], ctrl_point[index][1], ctrl_point[index][2]));ret->_vecIndex.push_back(last_index + index);if (!vec_inited[index]){vec_inited[index] = true;ret->_vecVertex[last_index + index].position.x = (float)(ctrl_point[index][0]) * 0.01f;ret->_vecVertex[last_index + index].position.y = (float)(ctrl_point[index][1]) * 0.01f;ret->_vecVertex[last_index + index].position.z = -(float)(ctrl_point[index][2]) * 0.01f;ret->_vecVertex[last_index + index].color = { 1.0f, 1.0f, 1.0f, 1.0f };}}}last_index += max_index + 1;}}}ret->_vecVertex.resize(last_index + 1);return ret;
}

4.数据传递到DX

顶点缓存和索引缓存都是需要通过上载堆传递数据,使用com_ptr的局部变量需要在完成GPU的数据上传后才能被释放。这一部分比较麻烦,后面再详细捋一捋。

HRESULT hr = S_OK;
//-------------------------------------------------顶点缓存------------------------------------------------------------
// Create the vertex buffer.
//默认堆需要上载堆传递CPU数据CD3DX12_HEAP_PROPERTIES heapProps0(D3D12_HEAP_TYPE_DEFAULT);
auto desc0 = CD3DX12_RESOURCE_DESC::Buffer(mesh_object->GetVertexDataSize());
hr = g_dx._device->CreateCommittedResource(&heapProps0,D3D12_HEAP_FLAG_NONE,&desc0,D3D12_RESOURCE_STATE_COPY_DEST,nullptr,IID_PPV_ARGS(&_vertexBuffer));
if (FAILED(hr))
{g_debug.Line(L"创建顶点缓存失败(DEFAULT)!");return;
}//上载堆
com_ptr<ID3D12Resource> vertexBufferUploadHeap;CD3DX12_HEAP_PROPERTIES heapProps1(D3D12_HEAP_TYPE_UPLOAD);
auto desc1 = CD3DX12_RESOURCE_DESC::Buffer(mesh_object->GetVertexDataSize());hr = g_dx._device->CreateCommittedResource(&heapProps1,D3D12_HEAP_FLAG_NONE,&desc1,D3D12_RESOURCE_STATE_GENERIC_READ,nullptr,IID_PPV_ARGS(&vertexBufferUploadHeap));
if (FAILED(hr))
{g_debug.Line(L"创建顶点缓存失败(UPLOAD)!");return;
}
_vertexBuffer->SetName(L"vb0");// Copy data to the intermediate upload heap and then schedule a copy
// from the upload heap to the vertex buffer.
//从内存复制到上载堆,再复制到GPU
D3D12_SUBRESOURCE_DATA vertexData = {};
vertexData.pData = mesh_object->GetVertexData();
vertexData.RowPitch = mesh_object->GetVertexDataSize();
vertexData.SlicePitch = vertexData.RowPitch;UpdateSubresources<1>(g_dx._commandList.get(), _vertexBuffer.get(), vertexBufferUploadHeap.get(), 0, 0, 1, &vertexData);auto num_barrier = CD3DX12_RESOURCE_BARRIER::Transition(_vertexBuffer.get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
g_dx._commandList->ResourceBarrier(1, &num_barrier);// Initialize the vertex buffer view.
//创建顶点缓存视图
_vertexBufferView.BufferLocation = _vertexBuffer->GetGPUVirtualAddress();
_vertexBufferView.StrideInBytes = sizeof(DirectX::Vertex);
_vertexBufferView.SizeInBytes = mesh_object->GetVertexDataSize();//-------------------------------------------------索引缓存------------------------------------------------------------CD3DX12_HEAP_PROPERTIES heapProps2(D3D12_HEAP_TYPE_DEFAULT);
auto desc2 = CD3DX12_RESOURCE_DESC::Buffer(mesh_object->GetIndexDataSize());hr = g_dx._device->CreateCommittedResource(&heapProps2,D3D12_HEAP_FLAG_NONE,&desc2,D3D12_RESOURCE_STATE_COPY_DEST,nullptr,IID_PPV_ARGS(&_indexBuffer));
if (FAILED(hr))
{g_debug.Line(L"创建索引缓存失败(DEFAULT)!");return;
}com_ptr<ID3D12Resource> indexBufferUploadHeap;CD3DX12_HEAP_PROPERTIES heapProps3(D3D12_HEAP_TYPE_UPLOAD);
auto desc3 = CD3DX12_RESOURCE_DESC::Buffer(mesh_object->GetIndexDataSize());hr = g_dx._device->CreateCommittedResource(&heapProps3,D3D12_HEAP_FLAG_NONE,&desc3,D3D12_RESOURCE_STATE_GENERIC_READ,nullptr,IID_PPV_ARGS(&indexBufferUploadHeap));_indexBuffer->SetName(L"ib0");// Copy data to the intermediate upload heap and then schedule a copy
// from the upload heap to the index buffer.
D3D12_SUBRESOURCE_DATA indexData = {};
indexData.pData = mesh_object->GetIndexData();
indexData.RowPitch = mesh_object->GetIndexDataSize();
indexData.SlicePitch = indexData.RowPitch;UpdateSubresources<1>(g_dx._commandList.get(), _indexBuffer.get(), indexBufferUploadHeap.get(), 0, 0, 1, &indexData);
auto num_barrier1 = CD3DX12_RESOURCE_BARRIER::Transition(_indexBuffer.get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDEX_BUFFER);
g_dx._commandList->ResourceBarrier(1, &num_barrier1);// Describe the index buffer view.
_indexBufferView.BufferLocation = _indexBuffer->GetGPUVirtualAddress();
_indexBufferView.Format = DXGI_FORMAT_R32_UINT;
_indexBufferView.SizeInBytes = mesh_object->GetIndexDataSize();_numIndices = mesh_object->GetIndexDataSize() / 4;    // R32_UINT (SampleAssets::StandardIndexFormat) = 4 bytes each.

5.模型位置

之所以最终绘制出的模型都在原点,是因为FBX的单个模型的顶点都是以自身坐标系的,而自身的位置属性(位移、旋转、缩放)通过以下面方式得到:

FbxDouble3 translation = pNode->LclTranslation.Get();
FbxDouble3 rotation = pNode->LclRotation.Get();
FbxDouble3 scaling = pNode->LclScaling.Get();

其后可以通过构造矩阵,在读取时就变换到整体的坐标系,比如一个大的游戏场景,有很多固定不动的物体,我们就不必分开处理,直接当成一个整体模型来处理。

如果是动态的、反复出现的模型,就需要单独保存,通过上传矩阵到着色器来计算位置。

6.结语

目前我也只是一步一步的尝试,有很多不完善的地方,此文章只是简单说一下,等以后理解得比较透彻后再详细一步一步的说明。

代码上传到了这儿:

https://gitee.com/lveyou/dnd3d

接下来做一下按键检测,来控制镜头,方便详细的观察模型,好弄清楚它们的坐标系到底是怎样的。

【DirectX12】4.用FBX_SDK读取网格数据相关推荐

  1. 性能优化实战|使用eBPF代替iptables优化服务网格数据面性能

    目前以 Istio[1] 为代表的服务网格普遍使用 Sidecar 架构,并使用 iptables 将流量劫持到 Sidecar 代理,优点是对应用程序无侵入,但是 Sidecar 代理会增加请求时延 ...

  2. Open3d(三)——网格数据操作

    亲测代码程序可运行使用,open3d版本0.13.0. open3d数据资源下载:GitHub - Cobotic/Open3D: Open3D: A Modern Library for 3D Da ...

  3. ENVI_IDL:读取OMI数据(HDF5)并输出为Geotiff文件+详细解析

    目录 1. 课堂内容 2. 知识储备 3. 编程 1. 课堂内容 读取OMI数据(HDF5)并输出为Geotiff文件,最重要的是数据的处理以及输出 这里我个人觉得难度不大, 第一,获取OMI文件的N ...

  4. TensorFlow csv读取文件数据(代码实现)

    TensorFlow csv读取文件数据(代码实现) 大多数人了解 Pandas 及其在处理大数据文件方面的实用性.TensorFlow 提供了读取这种文件的方法. 前面章节中,介绍了如何在 Tens ...

  5. SharePoint2010沙盒解决方案基础开发——关于TreeView树形控件读取列表数据(树形导航)的webpart开发及问题...

    转:http://blog.csdn.net/miragesky2049/article/details/7204882 SharePoint2010沙盒解决方案基础开发--关于TreeView树形控 ...

  6. Kinect V1读取图像数据(For Windows)

    Kinect V1读取图像数据(For Windows) 这篇博客 Kinect V1介绍 数据读取的基本流程 运行代码和注释 结尾 这篇博客  刚好有一台现成的Kinect V1相机,所以就拿过来学 ...

  7. C++ 简单读写文本文件、统计文件的行数、读取文件数据到数组

    转自:http://hi.baidu.com/ctralt/blog/item/cde79fec87f841302697911c.html fstream提供了三个类,用来实现c++对文件的操作.(文 ...

  8. 图像数据读取及数据扩增方法

    Datawhale干货 作者:王程伟,Datawhale成员 本文为干货知识+竞赛实践系列分享,旨在理论与实践结合,从学习到项目实践.(零基础入门系列:数据挖掘/cv/nlp/金融风控/推荐系统等,持 ...

  9. 操作系统学习:Linux0.12初始化详细流程-进程1调度与读取硬盘数据

    本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 Linux0.1 ...

最新文章

  1. 新手零基础学习Python第一步,搭建开发环境!
  2. Python学习笔记:循环语句
  3. Notepad++技巧
  4. 全国计算机等级考试题库二级C操作题100套(第01套)
  5. php 查看 实例 的方法,php – 从Laravel 5.1中的通用数据库查询中获取Eloquent模型的实例...
  6. Python 字符串查找子串的方法之 index() 和 find()
  7. 推荐:腾讯开源的词向量精简版本下载|湾区人工智能
  8. 在国内使用DNS服务器的一个对比分析
  9. ffmpeg的安装和使用教程
  10. 队列练习之Example004-设计一个循环队列,用 front 和 rear 分别作为队头和队尾指针,另外用一个标志 tag 表示队列是空还是不空
  11. 【91xcz】五方法助你轻松实现win8系统关机操作
  12. 剑指Offer——滴滴笔试题+知识点总结
  13. SQL语句在Mysql中是如何被执行的?
  14. java回车换行符linux,回车换行符 java
  15. 图观目前各类芯片的交货周期
  16. “纸上得来终觉浅,觉知此事要躬行”——博客起始
  17. 云计算介绍 TCP/IP协议以及配置
  18. 五款最棒的Go语言开发工具?
  19. LeetCode 167.Two Sum II 解题报告
  20. 初学者python总结

热门文章

  1. 总结Python机器学习中的回归算法
  2. 科研实习 | 北京大学智能学院贺笛老师招收NLP/GNN方向科研实习生
  3. 理解神经网络函数高频成分的收敛率界限
  4. Beam Search还能更快?结合优先队列的最佳优先化Beam Search
  5. 社招转行CV算法的心酸之路:越朴素的方法,往往越容易成功!
  6. ACL 2019 开源论文 | 基于知识库和大规模网络文本的问答系统
  7. 半监督学习之数据加载
  8. Java基础:HashMap的用法
  9. 2020新年发红包Java实现
  10. git/码云上关于项目的一些操作:初始化、克隆、上传修改等