赞
踩
OpenGLES 3D 模型本质上是由一系列三角形在 3D 空间(OpenGL 坐标系)中构建而成,另外还包含了用于描述三角形表面的纹理、光照、材质等信息。
利用 3D 建模软件,设计师可以构建一些复杂的形状,并将贴图应用到形状上去,不需要去关注图像技术细节。最后在导出模型文件时,建模工具会自己生成所有的顶点坐标、顶点法线和纹理坐标。
常用的模型文件格式有 .obj、.max、.fbx .3ds 等,其中.obj是 Wavefront 科技开发的一种几何体图形文件格式,包含每个顶点的位置、纹理坐标、法线,以及组成面(多边形)的顶点列表等数据,应用较为广泛。
# Blender v2.83.15 OBJ File: 'monkey.blend' # www.blender.org mtllib monkey.mtl o face_Plane v 0.156846 -0.166171 1.299656 v 0.040169 -0.151694 1.361770 v 0.167955 -0.148006 1.292944 ... vt 0.211637 0.867755 vt 0.220409 0.881854 vt 0.205920 0.875706 vt 0.225888 0.873355 vt 0.235953 0.888705 vt 0.197610 0.862036 ... vn 0.7356 -0.5550 0.3885 vn 0.8016 -0.5069 0.3170 vn 0.7547 -0.5032 0.4209 vn 0.7271 -0.6192 0.2964 vn 0.7657 -0.5795 0.2791 vn 0.6553 -0.4363 0.6166 ... f 313/720/711 1850/727/718 316/722/713 f 1623/337/337 317/728/719 1873/729/720 f 311/719/710 1851/730/721 313/720/711 f 317/728/719 1839/717/708 1871/704/695 f 1847/721/712 1874/726/717 1873/729/720 f 1875/725/716 1625/327/327 1874/726/717 f 1850/727/718 1877/731/722 1876/732/723
模型的数据量是非常大的,这里做了部分截取
OBJ 文件数据结构的简单说明:
#
开头的行表示注释行mtllib
表示指定该 OBJ 文件所使用的 mtl 文件(材质文件)v
开头的行表示存放的是顶点坐标,后面三个数分别表示一个顶点的(x,y,z)坐标值vn
开头的行表示存放的是顶点法向量,后面三个数分别表示一个顶点法向量的三维(x,y,z)分量值vt
开头的行表示存放的是纹理坐标,后面三个数分别表示一个纹理坐标的(s,t,p)分量值,其中 p 分量一般用于 3D 纹理f
开头的行表示存放的是一个三角面的信息,后面有三组数据分别表示组成三角面的三个顶点的信息,每个顶点信息的格式为:顶点位置索引/纹理坐标索引/法向量索引。模型加载可以使用模型加载库 Assimp,Assimp 全称为 Open Asset Import Library,可以支持几十种不同格式的模型文件的解析(同样也可以导出部分模型格式),Assimp 本身是 C++ 库,可以跨平台使用。
Assimp 可以将几十种模型文件都转换为一个统一的数据结构,所有无论我们导入何种格式的模型文件,都可以用同一个方式去访问我们需要的模型数据。
由于知道了模型的数据格式,通过C++代码的方式也可以方便的解析,本文就通过C++的方式进行解析,暂时先不使用Assimp这个库。
std::string fileName = dirPath + "/monkey.obj"; std::ifstream inputStream(fileName, std::ifstream::in | std::ifstream::binary); if (!inputStream.is_open()){ std::cerr << "Error opening file:"<<fileName<<std::endl; } std::vector<glm::vec3> coords; //顶点数据 std::vector<glm::vec2> texturCoords; //uv贴图顶点 std::vector<glm::vec3> normals; //法线数据 std::vector<MSVertexData> vertexes; std::vector<GLuint> indexes; MSMesh *mesh = nullptr; std::string mtlName; std::string lineString; while (std::getline(inputStream,lineString)) { std::vector<std::string> list = ccStringSplit(lineString," "); if (list[0] == "#") { std::cout<< "This is comment:" << lineString; continue; } else if (list[0] == "mtllib") { std::cout<< "File with materials:" << list[1]; continue; } else if (list[0] == "v") { //顶点 coords.emplace_back(glm::vec3(atof(list[1].c_str()), atof(list[2].c_str()), atof(list[3].c_str()))); continue; } else if (list[0] == "vt") { //纹理坐标 texturCoords.emplace_back(glm::vec2(atof(list[1].c_str()), atof(list[2].c_str()))); continue; } else if (list[0] == "vn") { //顶点法向量 Normal vector normals.emplace_back(glm::vec3(atof(list[1].c_str()), atof(list[2].c_str()), atof(list[3].c_str()))); continue; } else if (list[0] == "f") { // 顶点位置索引/纹理坐标索引/法向量索引 for (int i = 1; i <= 3; ++i){ std::vector<std::string> vert = ccStringSplit(list[i],"/"); vertexes.emplace_back(VertexData( coords[static_cast<int>(atol(vert[0].c_str())) - 1], texturCoords[static_cast<int>(atol(vert[1].c_str())) - 1], normals[static_cast<int>(atol(vert[2].c_str())) -1 ]) ); indexes.emplace_back(static_cast<unsigned>(indexes.size())); } continue; } else if (list[0] == "usemtl") { mtlName = list[1]; std::cout<< "This is used naterial:" << mtlName; } }
通过不同的字段就能解析3d模型文件,将解析好的数据保存到vertexes 和 indexes中
void MSModelLoader::calculateTBN(std::vector<MSVertexData> &vertData) { for (int i = 0; i < (int)vertData.size(); i += 3) { glm::vec3 &v1 = vertData[i].position; glm::vec3 &v2 = vertData[i + 1].position; glm::vec3 &v3 = vertData[i + 2].position; glm::vec2 &uv1 = vertData[i].textCoord; glm::vec2 &uv2 = vertData[i + 1].textCoord; glm::vec2 &uv3 = vertData[i + 2].textCoord; // https://youtu.be/ef3XR0ZttDU?t=1097 // deltaPos1 = deltaUV1.x * T + deltaUV1.y * B; // deltaPos2 = deltaUV2.x * T + deltaUV2.y * B; glm::vec3 deltaPos1 = v2 - v1; glm::vec3 deltaPos2 = v3 - v1; glm::vec2 deltaUV1 = uv2 - uv1; glm::vec2 deltaUV2 = uv3 - uv1; float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); glm::vec3 tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r; glm::vec3 bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r; vertData[i].tangent = tangent; vertData[i + 1].tangent = tangent; vertData[i + 2].tangent = tangent; vertData[i].bitangent = bitangent; vertData[i + 1].bitangent = bitangent; vertData[i + 2].bitangent = bitangent; } }
由于模型中只保存了,顶点数据、材质顶点数据、法线数据以及顶点的index,由于要进行法线纹理贴图需要切线和二维切线数据,
所以通过TBN空间计算得到切线以及二维切线来加载法线纹理贴图。
void MSMesh::InitRenderResources(AAssetManager *pManager, const std::vector<MSVertexData> &vertData, const std::vector<GLuint> &indexes) { if(pManager == NULL){ return; } loadTextureResources(pManager); loadShaderResources(pManager); m_indexBuffSize = indexes.size() ; m_pVAO->Create(); m_pVAO->Bind(); /*给顶点数据赋值*/ m_pVBO->Create(); m_pVBO->Bind(); m_pVBO->SetBufferData(vertData.data(),vertData.size() * sizeof (MSVertexData)); /*给Index 数据赋值 */ m_pEBO->Create(); m_pEBO->Bind(); m_pEBO->SetBufferData(indexes.data(), indexes.size() * sizeof (GLuint)); int offset = 0; /*给a_position传值 */ m_pOpenGLShader->SetAttributeBuffer(0, GL_FLOAT, (void *)offset, 3, sizeof(MSVertexData)); m_pOpenGLShader->EnableAttributeArray(0); offset += sizeof (glm::vec3); /*给shader材质顶点 a_texturCoord 赋值*/ m_pOpenGLShader->SetAttributeBuffer(1, GL_FLOAT, (void *)offset, 2, sizeof(MSVertexData)); m_pOpenGLShader->EnableAttributeArray(1); offset += sizeof (glm::vec2); /*给shader a_normal赋值 法向量*/ m_pOpenGLShader->SetAttributeBuffer(2, GL_FLOAT, (void *)offset, 3, sizeof (MSVertexData)); m_pOpenGLShader->EnableAttributeArray(2); offset += sizeof (glm::vec3); /*给切线赋值 a_tangent */ m_pOpenGLShader->SetAttributeBuffer(3, GL_FLOAT, (void *)offset, 3, sizeof (MSVertexData)); m_pOpenGLShader->EnableAttributeArray(3); offset += sizeof (glm::vec3); /*给双切线赋值*/ m_pOpenGLShader->SetAttributeBuffer(4, GL_FLOAT, (void *)offset, 3, sizeof (MSVertexData)); m_pOpenGLShader->EnableAttributeArray(4); m_pVAO->Release(); m_pVBO->Release(); m_pEBO->Release(); }
由于3D模型顶点数量非常多,复杂的模型可能达到百万顶点或者更多,所以需要用到VBO、EBO以及VAO等,否则频繁的从CPU传递数据到GPU会卡死。
这一步就是将解析到的模型的顶点数据以及顶点序列数据加载到shader中。
void MSMesh::Render(MSGLCamera* pCamera) { glm::mat4x4 modelMatrix = glm::mat4x4(1.0); glm::mat4x4 objectTransMat = glm::translate(glm::mat4(1.0f), glm::vec3(m_Objx, m_Objy, m_Objz)); glm::mat4x4 objectScaleMat = glm::scale(glm::mat4(1.0f),glm::vec3(0.25f*m_ObjScale, 0.25f*0.6*m_ObjScale, 0.25f*m_ObjScale) ); modelMatrix = objectTransMat * objectScaleMat ; //模型矩阵 m_pOpenGLShader->Bind(); //使用程序 //对shader的三个矩阵传值 m_pOpenGLShader->SetUniformValue("u_modelMatrix", modelMatrix); m_pOpenGLShader->SetUniformValue("u_viewMatrix", pCamera->viewMatrix); m_pOpenGLShader->SetUniformValue("u_projectionMatrix", pCamera->projectionMatrix); //给shader的散射光采样器赋值 m_pOpenGLShader->SetUniformValue("texture_diffuse", 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D,m_diffuseId); //给环境光 采样器赋值 m_pOpenGLShader->SetUniformValue("texture_normal", 1); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D,m_normalId); //给镜面光 采样器赋值 m_pOpenGLShader->SetUniformValue("texture_specular", 2); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D,m_specularId); m_pOpenGLShader->SetUniformValue("m_shiness", 32.0f); /*观察者的位置*/ m_pOpenGLShader->SetUniformValue("u_viewPos", pCamera->GetEyePosition()); /*给光线的位置赋值*/ m_pOpenGLShader->SetUniformValue("myLight.m_ambient", glm::vec3(0.5,0.5,0.5)); m_pOpenGLShader->SetUniformValue("myLight.m_diffuse", glm::vec3(0.8,0.8,0.8)); m_pOpenGLShader->SetUniformValue("myLight.m_specular", glm::vec3(0.9,0.9,0.9)); m_pOpenGLShader->SetUniformValue("myLight.m_pos", glm::vec3(5.0,5.0,5.0)); m_pOpenGLShader->SetUniformValue("myLight.m_c", 1.0f); m_pOpenGLShader->SetUniformValue("myLight.m_l", 0.09f); m_pOpenGLShader->SetUniformValue("myLight.m_q", 0.032f); m_pVAO->Bind(); const short* indices =(const short *) 0; glDrawElements(GL_TRIANGLES, m_indexBuffSize, GL_UNSIGNED_INT, indices); glBindTexture(GL_TEXTURE_2D,0); m_pOpenGLShader->Release(); m_pVAO->Release(); }
渲染的操作大部分都是在通过uniform进行给sahder传数据
由于使用VBO、EBO、VAO,渲染操作只需要执行glDrawElements就可以了。
#version 300 es layout(location = 0) in vec3 a_position; //顶点 layout(location = 1) in vec2 a_texturCoord; //材质顶点 layout(location = 2) in vec3 a_normal; //法线 layout(location = 3) in vec3 a_tangent; //切线 layout(location = 4) in vec3 a_bitangent; //双切线 uniform mat4 u_projectionMatrix; //透视矩阵 uniform mat4 u_viewMatrix; //观察者矩阵 uniform mat4 u_modelMatrix; //模型矩阵 out vec4 vary_pos; out vec2 vary_texCoord; out mat3 vary_tbnMatrix; void main(void) { mat4 mv_matrix = u_viewMatrix * u_modelMatrix; //观察者矩阵和模型矩阵相乘 gl_Position = u_projectionMatrix * mv_matrix * vec4(a_position,1.0); vary_texCoord = a_texturCoord; vary_pos = u_modelMatrix * vec4(a_position,1.0); //transpose 求转置矩阵 inverse 逆矩阵 normalize 归一化 vec3 normal = normalize(mat3(transpose(inverse(u_modelMatrix))) * a_normal); vec3 tangent = normalize(mat3(transpose(inverse(u_modelMatrix))) * a_tangent); vec3 bitangent = normalize(mat3(transpose(inverse(u_modelMatrix))) * a_bitangent); vary_tbnMatrix = mat3(tangent, bitangent, normal); //得到的是TBN空间矩阵 传给片元着色器 }
#version 300 es precision highp float; struct Light { vec3 m_pos; vec3 m_ambient; //环境光 vec3 m_diffuse; //散射光 vec3 m_specular; //镜面光 float m_c; float m_l; float m_q; }; uniform Light myLight; uniform sampler2D texture_diffuse; uniform sampler2D texture_normal; //环境光采样器 uniform sampler2D texture_specular; uniform float m_shiness; uniform vec3 u_viewPos; in vec4 vary_pos; in vec2 vary_texCoord; in mat3 vary_tbnMatrix; out vec4 fragColor; //GUP 本质上只要这个4分量的颜色 void main(void) { vec3 normal = texture(texture_normal,vary_texCoord).rgb; //texture(texture_normal,vary_texCoord) //材质顶点 采样器 normal = normalize(normal * 2.0 - 1.0); normal = normalize(vary_tbnMatrix * normal); float dist = length(myLight.m_pos - vary_pos.xyz); float attenuation = 1.0f / (myLight.m_c + myLight.m_l * dist + myLight.m_q *dist * dist); //ambient 环境光 vec3 ambient = myLight.m_ambient * vec3(texture(texture_diffuse , vary_texCoord).rgb); //diffuse 散射光 vec3 lightDir = normalize(myLight.m_pos - vary_pos.xyz); float diff = max(dot(normal , lightDir) , 0.0f); vec3 diffuse = myLight.m_diffuse * diff * vec3(texture(texture_diffuse , vary_texCoord).rgb); //mirror reflect 镜面光 float specular_strength = 0.5; vec3 viewDir = normalize(u_viewPos - vary_pos.xyz); vec3 reflectDir = reflect(-lightDir , normal); float spec = pow(max(dot(viewDir , reflectDir) , 0.0f) , m_shiness); vec3 sepcular = specular_strength* myLight.m_specular * spec; vec3 result = ambient + diffuse + sepcular ; //三个光的强度 加在一起 fragColor = vec4(result,1.0f) ; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。