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

前言

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

紋理映射回顧

DirectX11 With Windows SDK完整目錄

Github項目源碼

.obj格式

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

.obj文件結構簡述

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

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

頂點數據:

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

元素:

關鍵字 含義
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裏面出現頂點座標索引/紋理座標索引/法向量索引的次數說明了該面的頂點數目,目前我們也僅考慮支持三角形面

一個模型最少需要包含一個組或一個對象

注意:.obj紋理座標是基於笛卡爾座標系的,即(0.3, 0.7)對應的是實際的紋理座標(0.3, 0.3),即需要做(x, 1.0 - y)的變換

而.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字節
//   [漫射光材質文件名]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 texStrA;                       // 環境光紋理文件名,需爲相對路徑,且在mbo必須佔260字節
        std::wstring texStrD;                       // 漫射光紋理文件名,需爲相對路徑,在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;
using namespace std::experimental;
bool ObjReader::Read(const wchar_t * mboFileName, const wchar_t * objFileName)
{
    if (mboFileName && filesystem::exists(mboFileName))
    {
        return ReadMbo(mboFileName);
    }
    else if (objFileName && filesystem::exists(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);
    // 切換中文
    std::locale china("chs");
    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")
        {
            //
            // 頂點位置
            //
            XMFLOAT3 pos;
            wfin >> pos.x >> pos.y >> 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")
        {
            //
            // 頂點法向量
            //
            float x, y, z;
            wfin >> x >> y >> 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().texStrA = mtlReader.mapKaStrs[mtlName];
            objParts.back().texStrD = mtlReader.mapKdStrs[mtlName];
        }
        else if (wstr == L"f")
        {
            //
            // 幾何面
            //
            VertexPosNormalTex vertex;
            DWORD vpi, vni, vti;
            wchar_t ignore;

            // 確定
            // 頂點位置索引/紋理座標索引/法向量索引
            wfin >> vpi >> ignore >> vti >> ignore >> vni;
            vertex.pos = positions[vpi - 1];
            vertex.normal = normals[vni - 1];
            vertex.tex = texCoords[vti - 1];
            AddVertex(vertex, vpi, vti, vni);

            wfin >> vpi >> ignore >> vti >> ignore >> vni;
            vertex.pos = positions[vpi - 1];
            vertex.normal = normals[vni - 1];
            vertex.tex = texCoords[vti - 1];
            AddVertex(vertex, vpi, vti, vni);

            wfin >> vpi >> ignore >> vti >> ignore >> vni;
            vertex.pos = positions[vpi - 1];
            vertex.normal = normals[vni - 1];
            vertex.tex = texCoords[vti - 1];
            AddVertex(vertex, vpi, vti, vni);

            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字節
    //   [漫射光材質文件名]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].texStrA = filePath;
        // [漫射光材質文件名]520字節
        fin.read(reinterpret_cast<char*>(filePath), MAX_PATH * sizeof(wchar_t));
        objParts[i].texStrD = 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].texStrA.c_str());
        // [環境光材質文件名]520字節
        fout.write(reinterpret_cast<const char*>(filePath), MAX_PATH * sizeof(wchar_t));
        wcscpy_s(filePath, objParts[i].texStrD.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 = objParts.back().vertices.size() - 1;
        vertexCache[idxStr] = pos;
        objParts.back().indices32.push_back(pos);
    }
}



bool MtlReader::ReadMtl(const wchar_t * mtlFileName)
{
    materials.clear();
    mapKaStrs.clear();
    mapKdStrs.clear();


    std::wifstream wfin(mtlFileName);
    std::locale china("chs");
    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_Ka" || wstr == L"map_Kd")
        {
            //
            // map_Ka爲環境光反射使用的紋理,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;

            if (wstr == L"map_Ka")
                mapKaStrs[currMtl] = dir.erase(pos) + fileName;
            else
                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)

GameObject類的改進

因爲下一章還會講到硬件實例化,所以GameObject類在後期還會有所改動,現在只放出聲明部分:

class GameObject
{
public:
    // 使用模板別名(C++11)簡化類型名
    template <class T>
    using ComPtr = Microsoft::WRL::ComPtr<T>;

    struct GameObjectPart
    {
        Material material;
        ComPtr<ID3D11ShaderResourceView> texA;
        ComPtr<ID3D11ShaderResourceView> texD;
        ComPtr<ID3D11Buffer> vertexBuffer;
        ComPtr<ID3D11Buffer> indexBuffer;
        UINT vertexCount;
        UINT indexCount;
        DXGI_FORMAT indexFormat;
    };

    GameObject();

    // 獲取位置
    DirectX::XMFLOAT3 GetPosition() const;
    // 獲取子模型
    const GameObjectPart& GetPart(size_t pos) const;
    // 獲取包圍盒
    void GetBoundingBox(DirectX::BoundingBox& box) const;
    void GetBoundingBox(DirectX::BoundingBox& box, DirectX::FXMMATRIX worldMatrix) const;
    //
    // 設置模型
    //
    
    void SetModel(ComPtr<ID3D11Device> device, const ObjReader& model);
    
    //
    // 設置網格
    //

    void SetMesh(ComPtr<ID3D11Device> device, const Geometry::MeshData& meshData);
    void SetMesh(ComPtr<ID3D11Device> device, const std::vector<VertexPosNormalTex>& vertices, const std::vector<WORD> indices);
    void SetMesh(ComPtr<ID3D11Device> device, const std::vector<VertexPosNormalTex>& vertices, const std::vector<DWORD> indices);
    
    //
    // 設置紋理
    //

    void SetTexture(ComPtr<ID3D11ShaderResourceView> texture);
    void SetTexture(ComPtr<ID3D11ShaderResourceView> texA, ComPtr<ID3D11ShaderResourceView> texD);
    void SetTexture(size_t partIndex, ComPtr<ID3D11ShaderResourceView> texture);
    void SetTexture(size_t partIndex, ComPtr<ID3D11ShaderResourceView> texA, ComPtr<ID3D11ShaderResourceView> texD);
    
    //
    // 設置材質
    //

    void SetMaterial(const Material& material);
    void SetMaterial(size_t partIndex, const Material& material);
    
    //
    // 設置矩陣
    //

    void SetWorldMatrix(const DirectX::XMFLOAT4X4& world);
    void SetWorldMatrix(DirectX::FXMMATRIX world);
    void SetTexTransformMatrix(const DirectX::XMFLOAT4X4& texTransform);
    void SetTexTransformMatrix(DirectX::FXMMATRIX texTransform);


    // 繪製對象
    void Draw(ComPtr<ID3D11DeviceContext> deviceContext);

private:
    void SetMesh(ComPtr<ID3D11Device> device, const VertexPosNormalTex* vertices, UINT vertexCount,
        const void * indices, UINT indexCount, DXGI_FORMAT indexFormat);

private:
    DirectX::XMFLOAT4X4 mWorldMatrix;                           // 世界矩陣
    DirectX::XMFLOAT4X4 mTexTransform;                          // 紋理變換矩陣
    std::vector<GameObjectPart> mParts;                         // 模型的各個部分
    DirectX::BoundingBox mBoundingBox;                          // 模型的AABB盒
};

剩餘一些不是很重大的變動就不放出來了,比如BasicObjectFX類和對應的hlsl的變動,可以查看源碼(文首文末都有)。

模型加載演示

這裏我選用了之前合作項目時設計師完成的房屋模型,經過ObjReader加載後實裝到GameObject以進行繪製。效果如下:

DirectX11 With Windows SDK完整目錄

Github項目源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章