DirectX11 With Windows SDK--19 模型加載:obj格式的讀取及使用二進制文件提高讀取效率

前言

一個模型一般是由三個部分組成:網格、紋理、材質。在一開始的時候,咱們是經過Geometry類來生成簡單幾何體的網格。但如今咱們須要尋找合適的方式去表述一個複雜的網格,並且包含網格的文件類型多種多樣,對應的描述方式也存在着差別。這一章咱們主要研究obj格式文件的讀取。html

由於精力問題沒法對obj作完整支持,若是須要讀取obj格式的模型文件,推薦各位使用ASSIMP庫ios

紋理映射回顧git

DirectX11 With Windows SDK完整目錄github

Github項目源碼數組

歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。緩存

.obj格式

.obj格式是Alias|Wavefront公司推出的一種模型文件格式,一般以文本形式進行描述,所以你能夠按記事原本打開查看裏面的內容。經過市面上的一些常見的建模軟件如3dsMax,Maya等均可以導出.obj文件。一些遊戲引擎如Unity3d也支持導入.obj格式的模型。該文件能夠直接描述多邊形、法向量、紋理座標等等信息。ide

.obj文件結構簡述

.obj文件內部的每一行具體含義取決於開頭以空格、製表符分隔的關鍵字是什麼。這裏只根據當前項目須要的部分來描述關鍵字佈局

關鍵字 含義
# 這一行是一條註釋

頂點數據:spa

關鍵字 含義
v 這是一個3D頂點座標
vt 這是一個紋理座標
vn 這是一個3D法向量

元素:.net

關鍵字 含義
f 這是一個面,這裏咱們只支持三角形構成的面

組合:

關鍵字 含義
g 這是一個組,後面接着的內容是組的名稱
o 這是一個對象,後面接着的內容是對象的名稱

材質:

關鍵字 含義
mtllib 須要加載.mtl材質文件,後面接着的內容是文件名
usemtl 使用加載的.mtl材質文件中的某一材質,後面接着的內容是材質名

.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字節。索引使用WORDDWORD類型,若當前Part不一樣的頂點數超過65535,則必須使用DWORD類型來存儲索引。

環境光/漫射光材質文件名使用的是wchar_t[MAX_PATH]的數組,大小爲2*260字節。

但要注意一開始從.obj導出的頂點數組是沒有通過處理(包含重複頂點),須要經過必定的操做分離出頂點數組和索引數組才能傳遞給.mbo格式。

ObjReader--讀取.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實現更多的功能:

  1. 使用了/將下一行的內容鏈接在一塊兒表示一行
  2. 存在索引爲負數
  3. 使用了相似1//2這樣的頂點(即不包含紋理座標的頂點)
  4. 使用了絕對路徑的文件引用
  5. 相對路徑使用了.和..兩種路徑格式
  6. 若.mtl材質文件不存在,則內部會使用默認材質值
  7. 若.mtl內部沒有指定紋理文件引用,須要另外自行加載紋理
  8. f的頂點數不爲3(網格只能以三角形構造,即一個f的頂點數只能爲3)

Model類

如今使用一個模型類來管理從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類

由於下一章還會講到硬件實例化,因此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;                          // 世界矩陣

};

GameObject::Draw方法

該方法根據已有的模型數據繪製出來:

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文件完整說明

今天在修改的時候查到一份十分詳細的obj和mtl文件說明,有興趣的讀者能夠點擊下面的連接:

Object Files (.obj)

Material Files (.mtl)

DirectX11 With Windows SDK完整目錄

Github項目源碼

歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。

相關文章
相關標籤/搜索