从零开始游戏开发——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类,就可以通过资源的方式进行模型显示,不用硬编码来绘制物体了。
本文来自博客园,作者:毅安,转载请注明原文链接:https://www.cnblogs.com/primarycode/p/16840831.html,文章内容同步更新微信公众号:“游戏开发实战”或“GamePrimaryCode”