一个模型一般是由三个部分组成:网格、纹理、材质。在一开始的时候,咱们是经过Geometry类来生成简单几何体的网格。但如今咱们须要寻找合适的方式去表述一个复杂的网格,并且包含网格的文件类型多种多样,对应的描述方式也存在着差别。这一章咱们主要研究obj格式文件的读取。html
由于精力问题没法对obj作完整支持,若是须要读取obj格式的模型文件,推荐各位使用ASSIMP库ios
纹理映射回顾git
DirectX11 With Windows SDK完整目录github
欢迎加入QQ群: 727623616 能够一块儿探讨DX11,以及有什么问题也能够在这里汇报。缓存
.obj格式是Alias|Wavefront公司推出的一种模型文件格式,一般以文本形式进行描述,所以你能够按记事原本打开查看里面的内容。经过市面上的一些常见的建模软件如3dsMax,Maya等均可以导出.obj文件。一些游戏引擎如Unity3d也支持导入.obj格式的模型。该文件能够直接描述多边形、法向量、纹理坐标等等信息。ide
.obj文件内部的每一行具体含义取决于开头以空格、制表符分隔的关键字是什么。这里只根据当前项目须要的部分来描述关键字布局
关键字 | 含义 |
---|---|
# | 这一行是一条注释 |
顶点数据:spa
关键字 | 含义 |
---|---|
v | 这是一个3D顶点坐标 |
vt | 这是一个纹理坐标 |
vn | 这是一个3D法向量 |
元素:.net
关键字 | 含义 |
---|---|
f | 这是一个面,这里咱们只支持三角形构成的面 |
组合:
关键字 | 含义 |
---|---|
g | 这是一个组,后面接着的内容是组的名称 |
o | 这是一个对象,后面接着的内容是对象的名称 |
材质:
关键字 | 含义 |
---|---|
mtllib | 须要加载.mtl材质文件,后面接着的内容是文件名 |
usemtl | 使用加载的.mtl材质文件中的某一材质,后面接着的内容是材质名 |
.mtl文件内部描述方式和.obj文件同样,但里面使用的关键字有所不一样
关键字 | 含义 |
---|---|
# | 这一行是一条注释 |
newmtl | 这是一个新的材质,后面接着的内容是材质名称 |
材质描述:
关键字 | 含义 |
---|---|
Ka | 环境光反射颜色 |
Kd | 漫射光反射颜色 |
Ks | 镜面反射光反射颜色 |
d | 不透明度,即Alpha值 |
Tr | 透明度,即1.0 - Alpha值 |
map_Ka | 环境光反射指定的纹理文件 |
map_Kd | 漫射光反射指定的纹理文件 |
如今要经过.obj文件来描述一个平面正方形草丛。ground.obj
文件以下:
mtllib ground.mtl v -10.0 -1.0 -10.0 v -10.0 -1.0 10.0 v 10.0 -1.0 10.0 v 10.0 -1.0 -10.0 vn 0.0 0.0 -1.0 vt 0.0 0.0 vt 0.0 5.0 vt 5.0 5.0 vt 5.0 0.0 g Square usemtl TestMat f 1/1/1 2/2/1 3/3/1 f 3/3/1 4/4/1 1/1/1 # 2 faces
其中根据v的前后出现顺序,对应的索引为1到4。若索引值为3,则对应第3行v对应的顶点
注意: 索引的初始值在.obj中为1,而不是0!
而诸如1/1/1
这样的三索引对应的含义为:顶点坐标索引/纹理坐标索引/法向量索引
若写成1//1
,则代表不使用纹理坐标,但如今在咱们的项目中不容许缺乏上面任何一种索引
这样在一个f
里面出现顶点坐标索引/纹理坐标索引/法向量索引
的次数说明了该面的顶点数目,目前咱们也仅考虑支持三角形面
一个模型最少须要包含一个组或一个对象
注意:
(1).obj纹理坐标是基于笛卡尔坐标系的,即(0.3, 0.7)对应的是实际的纹理坐标(0.3, 0.3),即须要作(x, 1.0 - y)的变换
(2).obj的顶点坐标和法向量坐标是基于右手坐标系的,而且在右手坐标系下顶点其实是按逆时针排布的,须要进行从右手坐标系到左手坐标系的变换,就得取z的负值,并将顶点顺序反过来读(理论上z值不变化且顶点顺序正常来读也是能够正常显示的,只不过读出来的模型变成了xOy屏幕的镜面反射效果)
而.mtl文件的描述以下
newmtl TestMat d 1.0000 Ns 10.0000 Ka 0.8000 0.8000 0.8000 Kd 0.3000 0.3000 0.3000 Ks 0.0000 0.0000 0.0000 map_Ka grass.dds map_Kd grass.dds
漫反射和环境光反射都将使用同一种纹理。
使用文本类型的.obj格式文件进行读取的话必然要面临一个比较严重的问题:模型网格的面数较多会致使文本量极大,直接读取.obj的效率会很是低下。一般推荐在第一次读取.obj文件导入到程序后,再将读取好的顶点等信息以二进制文件的形式合理安排内容布局并保存,而后下次运行的时候读取该二进制文件来获取模型信息,能够大幅度加快读取速度,而且节省了必定的内存空间。
如今来讲明下当前项目下自定义二进制格式.mbo的字节布局:
// [Part数目] 4字节 // [AABB盒顶点vMax] 12字节 // [AABB盒顶点vMin] 12字节 // [Part // [漫射光材质文件名]520字节 // [材质]64字节 // [顶点数]4字节 // [索引数]4字节 // [顶点]32*顶点数 字节 // [索引]2(或4)*索引数 字节,取决于顶点数是否不超过65535 // ] // ...
这里将.obj中的一个组或一个对象定义为.mbo格式中的一个模型部分,而后顶点使用的是VertexPosNormalTex
类型,大小为32字节。索引使用WORD
或DWORD
类型,若当前Part不一样的顶点数超过65535,则必须使用DWORD
类型来存储索引。
环境光/漫射光材质文件名使用的是wchar_t[MAX_PATH]
的数组,大小为2*260字节。
但要注意一开始从.obj导出的顶点数组是没有通过处理(包含重复顶点),须要经过必定的操做分离出顶点数组和索引数组才能传递给.mbo格式。
ObjReader.h中包含了ObjReader
类和MtlReader
类:
#ifndef OBJREADER_H #define OBJREADER_H #include <iostream> #include <vector> #include <string> #include <fstream> #include <unordered_map> #include <map> #include <algorithm> #include <locale> #include <filesystem> #include "Vertex.h" #include "LightHelper.h" class MtlReader; class ObjReader { public: struct ObjPart { Material material; // 材质 std::vector<VertexPosNormalTex> vertices; // 顶点集合 std::vector<WORD> indices16; // 顶点数不超过65535时使用 std::vector<DWORD> indices32; // 顶点数超过65535时使用 std::wstring texStrDiffuse; // 漫射光纹理文件名,需为相对路径,在mbo必须占260字节 }; // 指定.mbo文件的状况下,若.mbo文件存在,优先读取该文件 // 不然会读取.obj文件 // 若.obj文件被读取,且提供了.mbo文件的路径,则会根据已经读取的数据建立.mbo文件 bool Read(const wchar_t* mboFileName, const wchar_t* objFileName); bool ReadObj(const wchar_t* objFileName); bool ReadMbo(const wchar_t* mboFileName); bool WriteMbo(const wchar_t* mboFileName); public: std::vector<ObjPart> objParts; DirectX::XMFLOAT3 vMin, vMax; // AABB盒双顶点 private: void AddVertex(const VertexPosNormalTex& vertex, DWORD vpi, DWORD vti, DWORD vni); // 缓存有v/vt/vn字符串信息 std::unordered_map<std::wstring, DWORD> vertexCache; }; class MtlReader { public: bool ReadMtl(const wchar_t* mtlFileName); public: std::map<std::wstring, Material> materials; std::map<std::wstring, std::wstring> mapKaStrs; std::map<std::wstring, std::wstring> mapKdStrs; }; #endif
ObjReader.cpp定义以下:
#include "ObjReader.h" using namespace DirectX; bool ObjReader::Read(const wchar_t * mboFileName, const wchar_t * objFileName) { if (mboFileName && ReadMbo(mboFileName)) { return true; } else if (objFileName) { bool status = ReadObj(objFileName); if (status && mboFileName) return WriteMbo(mboFileName); return status; } return false; } bool ObjReader::ReadObj(const wchar_t * objFileName) { objParts.clear(); vertexCache.clear(); MtlReader mtlReader; std::vector<XMFLOAT3> positions; std::vector<XMFLOAT3> normals; std::vector<XMFLOAT2> texCoords; XMVECTOR vecMin = g_XMInfinity, vecMax = g_XMNegInfinity; std::wifstream wfin(objFileName); if (!wfin.is_open()) return false; // 切换中文 std::locale china("chs"); china = wfin.imbue(china); for (;;) { std::wstring wstr; if (!(wfin >> wstr)) break; if (wstr[0] == '#') { // // 忽略注释所在行 // while (!wfin.eof() && wfin.get() != '\n') continue; } else if (wstr == L"o" || wstr == L"g") { // // 对象名(组名) // objParts.emplace_back(ObjPart()); // 提供默认材质 objParts.back().material.ambient = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f); objParts.back().material.diffuse = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f); objParts.back().material.specular = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); vertexCache.clear(); } else if (wstr == L"v") { // // 顶点位置 // // 注意obj使用的是右手坐标系,而不是左手坐标系 // 须要将z值反转 XMFLOAT3 pos; wfin >> pos.x >> pos.y >> pos.z; pos.z = -pos.z; positions.push_back(pos); XMVECTOR vecPos = XMLoadFloat3(&pos); vecMax = XMVectorMax(vecMax, vecPos); vecMin = XMVectorMin(vecMin, vecPos); } else if (wstr == L"vt") { // // 顶点纹理坐标 // // 注意obj使用的是笛卡尔坐标系,而不是纹理坐标系 float u, v; wfin >> u >> v; v = 1.0f - v; texCoords.emplace_back(XMFLOAT2(u, v)); } else if (wstr == L"vn") { // // 顶点法向量 // // 注意obj使用的是右手坐标系,而不是左手坐标系 // 须要将z值反转 float x, y, z; wfin >> x >> y >> z; z = -z; normals.emplace_back(XMFLOAT3(x, y, z)); } else if (wstr == L"mtllib") { // // 指定某一文件的材质 // std::wstring mtlFile; wfin >> mtlFile; // 去掉先后空格 size_t beg = 0, ed = mtlFile.size(); while (iswspace(mtlFile[beg])) beg++; while (ed > beg && iswspace(mtlFile[ed - 1])) ed--; mtlFile = mtlFile.substr(beg, ed - beg); // 获取路径 std::wstring dir = objFileName; size_t pos; if ((pos = dir.find_last_of('/')) == std::wstring::npos && (pos = dir.find_last_of('\\')) == std::wstring::npos) { pos = 0; } else { pos += 1; } mtlReader.ReadMtl((dir.erase(pos) + mtlFile).c_str()); } else if (wstr == L"usemtl") { // // 使用以前指定文件内部的某一材质 // std::wstring mtlName; std::getline(wfin, mtlName); // 去掉先后空格 size_t beg = 0, ed = mtlName.size(); while (iswspace(mtlName[beg])) beg++; while (ed > beg && iswspace(mtlName[ed - 1])) ed--; mtlName = mtlName.substr(beg, ed - beg); objParts.back().material = mtlReader.materials[mtlName]; objParts.back().texStrDiffuse = mtlReader.mapKdStrs[mtlName]; } else if (wstr == L"f") { // // 几何面 // VertexPosNormalTex vertex; DWORD vpi[3], vni[3], vti[3]; wchar_t ignore; // 顶点位置索引/纹理坐标索引/法向量索引 // 原来右手坐标系下顶点顺序是逆时针排布 // 如今须要转变为左手坐标系就须要将三角形顶点反过来输入 for (int i = 2; i >= 0; --i) { wfin >> vpi[i] >> ignore >> vti[i] >> ignore >> vni[i]; } for (int i = 0; i < 3; ++i) { vertex.pos = positions[vpi[i] - 1]; vertex.normal = normals[vni[i] - 1]; vertex.tex = texCoords[vti[i] - 1]; AddVertex(vertex, vpi[i], vti[i], vni[i]); } while (iswblank(wfin.peek())) wfin.get(); // 几何面顶点数可能超过了3,不支持该格式 if (wfin.peek() != '\n') return false; } } // 顶点数不超过WORD的最大值的话就使用16位WORD存储 for (auto& part : objParts) { if (part.vertices.size() < 65535) { for (auto& i : part.indices32) { part.indices16.push_back((WORD)i); } part.indices32.clear(); } } XMStoreFloat3(&vMax, vecMax); XMStoreFloat3(&vMin, vecMin); return true; } bool ObjReader::ReadMbo(const wchar_t * mboFileName) { // [Part数目] 4字节 // [AABB盒顶点vMax] 12字节 // [AABB盒顶点vMin] 12字节 // [Part // [漫射光材质文件名]520字节 // [材质]64字节 // [顶点数]4字节 // [索引数]4字节 // [顶点]32*顶点数 字节 // [索引]2(或4)*索引数 字节,取决于顶点数是否不超过65535 // ] // ... std::ifstream fin(mboFileName, std::ios::in | std::ios::binary); if (!fin.is_open()) return false; UINT parts = (UINT)objParts.size(); // [Part数目] 4字节 fin.read(reinterpret_cast<char*>(&parts), sizeof(UINT)); objParts.resize(parts); // [AABB盒顶点vMax] 12字节 fin.read(reinterpret_cast<char*>(&vMax), sizeof(XMFLOAT3)); // [AABB盒顶点vMin] 12字节 fin.read(reinterpret_cast<char*>(&vMin), sizeof(XMFLOAT3)); for (UINT i = 0; i < parts; ++i) { wchar_t filePath[MAX_PATH]; // [漫射光材质文件名]520字节 fin.read(reinterpret_cast<char*>(filePath), MAX_PATH * sizeof(wchar_t)); objParts[i].texStrDiffuse = filePath; // [材质]64字节 fin.read(reinterpret_cast<char*>(&objParts[i].material), sizeof(Material)); UINT vertexCount, indexCount; // [顶点数]4字节 fin.read(reinterpret_cast<char*>(&vertexCount), sizeof(UINT)); // [索引数]4字节 fin.read(reinterpret_cast<char*>(&indexCount), sizeof(UINT)); // [顶点]32*顶点数 字节 objParts[i].vertices.resize(vertexCount); fin.read(reinterpret_cast<char*>(objParts[i].vertices.data()), vertexCount * sizeof(VertexPosNormalTex)); if (vertexCount > 65535) { // [索引]4*索引数 字节 objParts[i].indices32.resize(indexCount); fin.read(reinterpret_cast<char*>(objParts[i].indices32.data()), indexCount * sizeof(DWORD)); } else { // [索引]2*索引数 字节 objParts[i].indices16.resize(indexCount); fin.read(reinterpret_cast<char*>(objParts[i].indices16.data()), indexCount * sizeof(WORD)); } } fin.close(); return true; } bool ObjReader::WriteMbo(const wchar_t * mboFileName) { // [Part数目] 4字节 // [AABB盒顶点vMax] 12字节 // [AABB盒顶点vMin] 12字节 // [Part // [环境光材质文件名]520字节 // [漫射光材质文件名]520字节 // [材质]64字节 // [顶点数]4字节 // [索引数]4字节 // [顶点]32*顶点数 字节 // [索引]2(或4)*索引数 字节,取决于顶点数是否不超过65535 // ] // ... std::ofstream fout(mboFileName, std::ios::out | std::ios::binary); UINT parts = (UINT)objParts.size(); // [Part数目] 4字节 fout.write(reinterpret_cast<const char*>(&parts), sizeof(UINT)); // [AABB盒顶点vMax] 12字节 fout.write(reinterpret_cast<const char*>(&vMax), sizeof(XMFLOAT3)); // [AABB盒顶点vMin] 12字节 fout.write(reinterpret_cast<const char*>(&vMin), sizeof(XMFLOAT3)); // [Part for (UINT i = 0; i < parts; ++i) { wchar_t filePath[MAX_PATH]; wcscpy_s(filePath, objParts[i].texStrDiffuse.c_str()); // [漫射光材质文件名]520字节 fout.write(reinterpret_cast<const char*>(filePath), MAX_PATH * sizeof(wchar_t)); // [材质]64字节 fout.write(reinterpret_cast<const char*>(&objParts[i].material), sizeof(Material)); UINT vertexCount = (UINT)objParts[i].vertices.size(); // [顶点数]4字节 fout.write(reinterpret_cast<const char*>(&vertexCount), sizeof(UINT)); UINT indexCount; if (vertexCount > 65535) { indexCount = (UINT)objParts[i].indices32.size(); // [索引数]4字节 fout.write(reinterpret_cast<const char*>(&indexCount), sizeof(UINT)); // [顶点]32*顶点数 字节 fout.write(reinterpret_cast<const char*>(objParts[i].vertices.data()), vertexCount * sizeof(VertexPosNormalTex)); // [索引]4*索引数 字节 fout.write(reinterpret_cast<const char*>(objParts[i].indices32.data()), indexCount * sizeof(DWORD)); } else { indexCount = (UINT)objParts[i].indices16.size(); // [索引数]4字节 fout.write(reinterpret_cast<const char*>(&indexCount), sizeof(UINT)); // [顶点]32*顶点数 字节 fout.write(reinterpret_cast<const char*>(objParts[i].vertices.data()), vertexCount * sizeof(VertexPosNormalTex)); // [索引]2*索引数 字节 fout.write(reinterpret_cast<const char*>(objParts[i].indices16.data()), indexCount * sizeof(WORD)); } } // ] fout.close(); return true; } void ObjReader::AddVertex(const VertexPosNormalTex& vertex, DWORD vpi, DWORD vti, DWORD vni) { std::wstring idxStr = std::to_wstring(vpi) + L"/" + std::to_wstring(vti) + L"/" + std::to_wstring(vni); // 寻找是否有重复顶点 auto it = vertexCache.find(idxStr); if (it != vertexCache.end()) { objParts.back().indices32.push_back(it->second); } else { objParts.back().vertices.push_back(vertex); DWORD pos = (DWORD)objParts.back().vertices.size() - 1; vertexCache[idxStr] = pos; objParts.back().indices32.push_back(pos); } } bool MtlReader::ReadMtl(const wchar_t * mtlFileName) { materials.clear(); mapKdStrs.clear(); std::wifstream wfin(mtlFileName); std::locale china("chs"); china = wfin.imbue(china); if (!wfin.is_open()) return false; std::wstring wstr; std::wstring currMtl; for (;;) { if (!(wfin >> wstr)) break; if (wstr[0] == '#') { // // 忽略注释所在行 // while (wfin.get() != '\n') continue; } else if (wstr == L"newmtl") { // // 新材质 // std::getline(wfin, currMtl); // 去掉先后空格 size_t beg = 0, ed = currMtl.size(); while (iswspace(currMtl[beg])) beg++; while (ed > beg && iswspace(currMtl[ed - 1])) ed--; currMtl = currMtl.substr(beg, ed - beg); } else if (wstr == L"Ka") { // // 环境光反射颜色 // XMFLOAT4& ambient = materials[currMtl].ambient; wfin >> ambient.x >> ambient.y >> ambient.z; if (ambient.w == 0.0f) ambient.w = 1.0f; } else if (wstr == L"Kd") { // // 漫射光反射颜色 // XMFLOAT4& diffuse = materials[currMtl].diffuse; wfin >> diffuse.x >> diffuse.y >> diffuse.z; if (diffuse.w == 0.0f) diffuse.w = 1.0f; } else if (wstr == L"Ks") { // // 镜面光反射颜色 // XMFLOAT4& specular = materials[currMtl].specular; wfin >> specular.x >> specular.y >> specular.z; } else if (wstr == L"Ns") { // // 镜面系数 // wfin >> materials[currMtl].specular.w; } else if (wstr == L"d" || wstr == L"Tr") { // // d为不透明度 Tr为透明度 // float alpha; wfin >> alpha; if (wstr == L"Tr") alpha = 1.0f - alpha; materials[currMtl].ambient.w = alpha; materials[currMtl].diffuse.w = alpha; } else if (wstr == L"map_Kd") { // // map_Kd为漫反射使用的纹理 // std::wstring fileName; std::getline(wfin, fileName); // 去掉先后空格 size_t beg = 0, ed = fileName.size(); while (iswspace(fileName[beg])) beg++; while (ed > beg && iswspace(fileName[ed - 1])) ed--; fileName = fileName.substr(beg, ed - beg); // 追加路径 std::wstring dir = mtlFileName; size_t pos; if ((pos = dir.find_last_of('/')) == std::wstring::npos && (pos = dir.find_last_of('\\')) == std::wstring::npos) pos = 0; else pos += 1; mapKdStrs[currMtl] = dir.erase(pos) + fileName; } } return true; }
其中AddVertex
方法用于去除重复的顶点,并构建索引数组。
在改成读取.mbo文件后,本来读取.obj须要耗时3s,如今能够降到2ms之内,大幅提高了读取效率。其关键点就在于要构造连续性的二进制数据以减小读取次数,并剔除掉本来读取.obj时的各类词法分析部分(在该部分也浪费了大量的时间)。
因为ObjReader
类对.obj格式的文件要求比较严格,若是出现不能正确加载的现象,请检查是否出现下面这些状况,不然须要自行修改.obj/.mtl文件,或者给ObjReader
实现更多的功能:
如今使用一个模型类来管理从ObjReader
读取到的信息,并建立出各类GPU资源:
struct ModelPart { // 使用模板别名(C++11)简化类型名 template <class T> using ComPtr = Microsoft::WRL::ComPtr<T>; ModelPart() : material(), texDiffuse(), vertexBuffer(), indexBuffer(), vertexCount(), indexCount(), indexFormat() {} ModelPart(const ModelPart&) = default; ModelPart& operator=(const ModelPart&) = default; ModelPart(ModelPart&&) = default; ModelPart& operator=(ModelPart&&) = default; Material material; ComPtr<ID3D11ShaderResourceView> texDiffuse; ComPtr<ID3D11Buffer> vertexBuffer; ComPtr<ID3D11Buffer> indexBuffer; UINT vertexCount; UINT indexCount; DXGI_FORMAT indexFormat; }; struct Model { // 使用模板别名(C++11)简化类型名 template <class T> using ComPtr = Microsoft::WRL::ComPtr<T>; Model(); Model(ID3D11Device * device, const ObjReader& model); // 设置缓冲区 template<class VertexType, class IndexType> Model(ID3D11Device * device, const Geometry::MeshData<VertexType, IndexType>& meshData); template<class VertexType, class IndexType> Model(ID3D11Device * device, const std::vector<VertexType> & vertices, const std::vector<IndexType>& indices); Model(ID3D11Device * device, const void* vertices, UINT vertexSize, UINT vertexCount, const void * indices, UINT indexCount, DXGI_FORMAT indexFormat); // // 设置模型 // void SetModel(ID3D11Device * device, const ObjReader& model); // // 设置网格 // template<class VertexType, class IndexType> void SetMesh(ID3D11Device * device, const Geometry::MeshData<VertexType, IndexType>& meshData); template<class VertexType, class IndexType> void SetMesh(ID3D11Device * device, const std::vector<VertexType> & vertices, const std::vector<IndexType>& indices); void SetMesh(ID3D11Device * device, const void* vertices, UINT vertexSize, UINT vertexCount, const void * indices, UINT indexCount, DXGI_FORMAT indexFormat); // // 调试 // // 设置调试对象名 // 若模型被从新设置,调试对象名也须要被从新设置 void SetDebugObjectName(const std::string& name); std::vector<ModelPart> modelParts; DirectX::BoundingBox boundingBox; UINT vertexStride; };
由于下一章还会讲到硬件实例化,因此GameObject
类在后期还会有所改动:
class GameObject { public: // 使用模板别名(C++11)简化类型名 template <class T> using ComPtr = Microsoft::WRL::ComPtr<T>; GameObject(); // 获取位置 DirectX::XMFLOAT3 GetPosition() const; // // 获取包围盒 // DirectX::BoundingBox GetLocalBoundingBox() const; DirectX::BoundingBox GetBoundingBox() const; DirectX::BoundingOrientedBox GetBoundingOrientedBox() const; // // 设置模型 // void SetModel(Model&& model); void SetModel(const Model& model); // // 设置矩阵 // void SetWorldMatrix(const DirectX::XMFLOAT4X4& world); void XM_CALLCONV SetWorldMatrix(DirectX::FXMMATRIX world); // // 绘制 // // 绘制对象 void Draw(ID3D11DeviceContext * deviceContext, BasicEffect& effect); // // 调试 // // 设置调试对象名 // 若模型被从新设置,调试对象名也须要被从新设置 void SetDebugObjectName(const std::string& name); private: Model m_Model; // 模型 DirectX::XMFLOAT4X4 m_WorldMatrix; // 世界矩阵 };
该方法根据已有的模型数据绘制出来:
void GameObject::Draw(ID3D11DeviceContext * deviceContext, BasicEffect & effect) { UINT strides = m_Model.vertexStride; UINT offsets = 0; for (auto& part : m_Model.modelParts) { // 设置顶点/索引缓冲区 deviceContext->IASetVertexBuffers(0, 1, part.vertexBuffer.GetAddressOf(), &strides, &offsets); deviceContext->IASetIndexBuffer(part.indexBuffer.Get(), part.indexFormat, 0); // 更新数据并应用 effect.SetWorldMatrix(XMLoadFloat4x4(&m_WorldMatrix)); effect.SetTextureDiffuse(part.texDiffuse.Get()); effect.SetMaterial(part.material); effect.Apply(deviceContext); deviceContext->DrawIndexed(part.indexCount, 0, 0); } }
剩余一些不是很重大的变更就不放出来了,好比BasicEffect
类和对应的hlsl的变更,能够查看源码(文首文末都有)。
这里我选用了以前合做项目时设计师完成的房屋模型,通过ObjReader
加载后实装到GameObject
以进行绘制。效果以下:
今天在修改的时候查到一份十分详细的obj和mtl文件说明,有兴趣的读者能够点击下面的连接:
DirectX11 With Windows SDK完整目录
欢迎加入QQ群: 727623616 能够一块儿探讨DX11,以及有什么问题也能够在这里汇报。