从零开始游戏开发——3.6 材质与Mesh

  需要显示一个模型,虽然可以通过在代码指定顶点数据、显示的纹理、使用的Shader等内容,但游戏开中这些数据通常都是由美术人员提供的,这就涉及到了材质和Mesh的概念。材质提供了模型的外在显示效果,如果使用的Shader、纹理、光照属性等,Mesh提供了模型的几何数据。在游戏资源管理中,有CPU资源和GPU资源,使用到图形硬件API时,纹理、Shader程序、顶点缓冲区、帧缓冲区等都是由GPU进行管理的,但材质和Mesh则一般都在CPU端进行管理。 这里我们将材质通过json格式进行存储,下面是wood材质的简单例子,字段值代表了顶点着色器和片断着色器的名字为VertexShader和fragmentShader,材质名字是Wood,使用的0号纹理为crate.tga,Transparent表示是否为透明材质。

{"Frag":"FragmentShader","Name":"Wood","Textures":[[0,"crate.tga"]],"Transparent":false,"Vert":"VertexShader"}

Mesh则是通过二进制形式进行存储,其格式为:

顶点数  |  顶点属性  |  顶点数据  |  索引数  |  索引数据

材质和Mesh通过资源管理器加载到内存,并将数据上传到渲染器用于最终的渲染,通过Mesh和材质创建渲染数据的代码如下:

 1 _Mesh = (IMesh *)CResourceMgr::Instance()->LoadResource("cube.mesh");
 2 auto vertexCount = _Mesh->GetVertexCount();
 3 
 4 IVertexBuffer *vertexBuffer = Renderer->CreateVertexBuffer(vertexCount * (sizeof(Vector3f) + sizeof(Vector2f)), vertexCount);
 5 vertexBuffer->BufferData(_Mesh->GetVertices(), vertexCount * sizeof(Vector3f));
 6 vertexBuffer->BufferData(_Mesh->GetUVs(), vertexCount * sizeof(Vector2f), vertexCount * sizeof(Vector3f));
 7 vertexBuffer->GetAttribute()->SetPositionAttr(0, sizeof(Vector3f));
 8 vertexBuffer->GetAttribute()->SetUVAttr(sizeof(Vector3f) * vertexCount, sizeof(Vector2f));
 9 
10 auto indexCount = _Mesh->GetIndexCount();
11 IIndexBuffer *indexBuffer = Renderer->CreateIndexBuffer(indexCount * sizeof(int), indexCount);
12 indexBuffer->BufferData(_Mesh->GetIndices());
13 
14 _Geometry = NEW CGeometry(vertexBuffer, indexBuffer);
15 
16 _Material = (IMaterial *)CResourceMgr::Instance()->LoadResource("wood.mat");
17 
18 _ShaderProgram = Renderer->CreateShaderProgram(_Material->GetVertShader(), _Material->GetFragShader());
19 int texQuantiity = _Material->GetTextureQuantity();
20 for (int i = 0; i < texQuantiity; ++i)
21 {
22     auto texture = Renderer->CreateTexture(_Material->GetTexture(i));
23     _Textures.push_back(texture);
24     _RenderInput.SetTexture(0, texture);
25 }
26 
27 _RenderInput.SetGeometry(_Geometry); 
28 _RenderInput.SetShaderProgram(_ShaderProgram);
29 
30 
31 Renderer->SetClearColor(0, 0, 0); 
32 _ViewMat.BuildCameraLookAtMatrix(Vector3f(0, 0, 5.f), Vector3f(0, 0, -1), Vector3f(0, 1, 0));
33 _ProjMat.BuildProjectionMatrixPerspectiveFovRH(PI / 3.f,   1.0f * GetWindowWidth() / GetWindowHeight(), 1.0f, 100.f);
34 Renderer->SetGlobalUniform("mvpMat", (_ProjMat * _ViewMat).m, sizeof(Matrix4x4f));
35 Renderer->SetCullMode(CullMode::CULL_NONE);

上面代码中第1行和第16行通过资源管理器LoadResource操作将Material和Mesh加载后,就拥有用于渲染的数据了。

  材质和Mesh类继承于IResource类:

 1     class IResource
 2     {
 3     public:
 4         enum Type
 5         {
 6             MATERIAL,
 7             MESH,
 8             IMAGE,
 9             SHADER,
10         };
11         virtual ~IResource(){}
12         virtual Type GetType() const = 0;
13         virtual bool Load(const char *fullPath) = 0;
14         virtual bool Save(const char *fullPath) = 0;
15     };

每个派生类实现自己的Load和Save函数,为了扩展多种格式的资源加载,这里将文件解析部分提出来为IResourceLoader:

1     class IResourceLoader
2     {
3     public:
4         virtual ~IResourceLoader(){}
5         virtual bool Load(const char *filePath, IResource *resource) = 0;
6         virtual bool Save(const char *filePath, IResource *resource) = 0;
7     };

Resource类包含IResourceLoader并在Load和Save时进行调用,材质资源由于通过Json储存,这里使用扩展库Nlohmann进行Json的解析,因此定义Material的Loader非常简单,下面为CNlohmannLoader.hpp的代码:

 1 #include "IResourceLoader.h"
 2 #include "Dependency/nlohmann/json.hpp"
 3 #include "../CMaterial.h"
 4 
 5 #include <fstream>
 6 #include <iostream>
 7 
 8 using namespace std;
 9 using json = nlohmann::json;
10 
11 namespace Magic
12 {
13     class CNlohmannloader : public IResourceLoader
14     {
15     public:
16         bool Load(const char *filePath, IResource *resource)
17         {
18             if (resource->GetType() == IResource::Type::MATERIAL)
19             {
20                 CMaterial *res = (CMaterial *)resource;
21                 try
22                 {
23                     ifstream i(filePath);
24                     json j;
25                     i >> j;
26                     j.at("Name").get_to(res->_Name);
27                     j.at("Vert").get_to(res->_VertShader);
28                     j.at("Frag").get_to(res->_FragShader);
29                     j.at("Transparent").get_to(res->_isTransparent);
30                     j.at("Textures").get_to(res->_Textures);
31                     i.close();
32                 }
33                 catch (const exception &e)
34                 {
35                     cerr << e.what() << endl;
36                     return false;
37                 }
38                 return true;
39             }
40             return false;
41         }
42 
43         bool Save(const char *filePath, IResource *resource)
44         {
45 
46             if (resource->GetType() == IResource::Type::MATERIAL)
47             {
48                 CMaterial *res = (CMaterial *)resource;
49                 try
50                 {
51                     ofstream o(filePath);
52                     json j;
53                     j["Name"] = res->_Name;
54                     j["Vert"] = res->_VertShader;
55                     j["Frag"] = res->_FragShader;
56                     j["Transparent"] = res->_isTransparent;
57                     j["Textures"] = res->_Textures;
58 
59                     o << j;
60 
61                     o.close();
62                 }
63                 catch (const exception &e)
64                 {
65                     cerr << e.what() << endl;
66                     return false;
67                 }
68                 return true;
69             }
70             return false;
71         }
72     };
73 }

Mesh文件则是通过二进制格式存储,CMeshLoader.hpp代码如下:

  1 #include "IResourceLoader.h"
  2 #include "../CMesh.h"
  3 
  4 #include <fstream>
  5 #include <vector>
  6 
  7 using namespace std;
  8 
  9 namespace Magic
 10 {
 11     /**
 12      * 数据存储格式: 顶点数|顶点属性|顶点数据|索引数|索引数据
 13      */
 14     class CMeshLoader : public IResourceLoader
 15     {
 16     public:
 17         bool Load(const char *filePath, IResource *resource)
 18         {
 19             CMesh *mesh = (CMesh *)resource;
 20             if (mesh)
 21             {
 22                 ifstream in(filePath, ifstream::binary);
 23                 int vertexCount;
 24                 int attrBit;
 25                 in.read(reinterpret_cast<char *>(&vertexCount), sizeof(vertexCount));
 26                 in.read(reinterpret_cast<char *>(&attrBit), sizeof(attrBit));
 27 
 28                 std::vector<Vector3f> vertices;
 29                 std::vector<Vector3f> normals;
 30                 std::vector<Vector3f> colors;
 31                 std::vector<Vector2f> uvs;
 32                 for (int i = 0; i < vertexCount; ++i)
 33                 {
 34                     if (attrBit & CMesh::AttributeBit::Vertex)
 35                     {
 36                         Vector3f vertex;
 37                         in.read(reinterpret_cast<char *>(&vertex), sizeof(vertex));
 38                         vertices.push_back(vertex);
 39                     }
 40                     if (attrBit & CMesh::AttributeBit::Normal)
 41                     {
 42                         Vector3f normal;
 43                         in.read(reinterpret_cast<char *>(&normal), sizeof(normal));
 44                         normals.push_back(normal);
 45                     }
 46                     if (attrBit & CMesh::AttributeBit::Color)
 47                     {
 48                         Vector3f color;
 49                         in.read(reinterpret_cast<char *>(&color), sizeof(color));
 50                         colors.push_back(color);
 51                     }
 52                     if (attrBit & CMesh::AttributeBit::UV)
 53                     {
 54                         Vector2f uv;
 55                         in.read(reinterpret_cast<char *>(&uv), sizeof(uv));
 56                         uvs.push_back(uv);
 57                     }
 58                 }
 59 
 60 
 61                 if (vertices.size() > 0)
 62                     mesh->SetVertices(vertices.data(), vertices.size() * sizeof(Vector3f));
 63                 if (normals.size() > 0)
 64                     mesh->SetNormals(normals.data(), normals.size() * sizeof(Vector3f));
 65                 if (colors.size() > 0)
 66                     mesh->SetColors(colors.data(), colors.size() * sizeof(Vector3f));
 67                 if (uvs.size() > 0)
 68                     mesh->SetUVs(uvs.data(), uvs.size() * sizeof(Vector2f));
 69 
 70                 int indexCount;
 71                 vector<int>indices;
 72                 in.read(reinterpret_cast<char *>(&indexCount), sizeof(indexCount));
 73                 for (int i = 0; i < indexCount; ++i)
 74                 {
 75                     int index;
 76                     in.read(reinterpret_cast<char *>(&index), sizeof(index));
 77                     indices.push_back(index);
 78                 }
 79 
 80                 if (indices.size() > 0)
 81                     mesh->SetIndices(indices.data(), indices.size() * sizeof(float));
 82                 in.close();
 83                 return true;
 84             }
 85             return false;
 86         }
 87 
 88         bool Save(const char *filePath, IResource *resource)
 89         {
 90             CMesh *mesh = (CMesh *)resource;
 91             if (mesh)
 92             {
 93                 auto vertexCount = mesh->GetVertexCount();
 94                 auto indexCount = mesh->GetIndexCount();
 95                 auto vertices = mesh->GetVertices();
 96                 auto normals = mesh->GetNormals();
 97                 auto colors = mesh->GetColors();
 98                 auto uvs = mesh->GetUVs();
 99                 auto indices = mesh->GetIndices();
100                 auto attrBit = mesh->GetAttributeBit();
101 
102                 ofstream o(filePath, ios::binary);
103 
104                 o.write(reinterpret_cast<char*>(&vertexCount), sizeof(vertexCount));
105                 o.write(reinterpret_cast<char*>(&attrBit), sizeof(attrBit));
106 
107                 for (int i = 0; i < vertexCount; ++i)
108                 {
109                     if (vertices)
110                     {
111                         auto vertex = *(vertices + i);
112                         o.write(reinterpret_cast<char*>(vertex.v), sizeof(vertex.v));
113                     }
114                     if (normals)
115                     {
116                         auto normal = *(normals + i);
117                         o.write(reinterpret_cast<char*>(normal.v), sizeof(normal.v));
118                     }
119                     if (colors)
120                     {
121                         auto color = *(colors + i);
122                         o.write(reinterpret_cast<char*>(color.v), sizeof(color.v));
123                     }
124 
125                     if (uvs)
126                     {
127                         auto uv = *(uvs + i);
128                         o.write(reinterpret_cast<char*>(uv.v), sizeof(uv.v));
129                     }
130                 }
131 
132                 o.write(reinterpret_cast<char*>(&indexCount), sizeof(indexCount));
133 
134                 for (int i = 0; i < indexCount; ++i)
135                 {
136                     auto index = *(indices + i);
137                     o.write(reinterpret_cast<char*>(&index), sizeof(index));
138                 }
139                 o.close();
140                 return true;
141             }
142             return false;
143         }
144     };
145 }

在实际使用时,可以根据实际需要更换不同的资源加载器,有了Material和Mesh类,就可以通过资源的方式进行模型显示,不用硬编码来绘制物体了。

 

posted @ 2022-11-13 16:01  毅安  阅读(391)  评论(0编辑  收藏  举报