• Android OpenGL ES 3.0 3D模型介绍以及加载和渲染


    1.OpenGLES 3D 模型

    OpenGLES 3D 模型本质上是由一系列三角形在 3D 空间(OpenGL 坐标系)中构建而成,另外还包含了用于描述三角形表面的纹理、光照、材质等信息。

    利用 3D 建模软件,设计师可以构建一些复杂的形状,并将贴图应用到形状上去,不需要去关注图像技术细节。最后在导出模型文件时,建模工具会自己生成所有的顶点坐标、顶点法线和纹理坐标

    常用的模型文件格式有 .obj、.max、.fbx .3ds 等,其中.obj是 Wavefront 科技开发的一种几何体图形文件格式,包含每个顶点的位置、纹理坐标、法线,以及组成面(多边形)的顶点列表等数据,应用较为广泛。

    2.OBJ 文件的结构
    # 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    模型的数据量是非常大的,这里做了部分截取

    OBJ 文件数据结构的简单说明:

    • # 开头的行表示注释行
    • mtllib 表示指定该 OBJ 文件所使用的 mtl 文件(材质文件)
    • v 开头的行表示存放的是顶点坐标,后面三个数分别表示一个顶点的(x,y,z)坐标值
    • vn 开头的行表示存放的是顶点法向量,后面三个数分别表示一个顶点法向量的三维(x,y,z)分量值
    • vt 开头的行表示存放的是纹理坐标,后面三个数分别表示一个纹理坐标的(s,t,p)分量值,其中 p 分量一般用于 3D 纹理
    • f 开头的行表示存放的是一个三角面的信息,后面有三组数据分别表示组成三角面的三个顶点的信息,每个顶点信息的格式为:顶点位置索引/纹理坐标索引/法向量索引。
    3.模型加载

    模型加载可以使用模型加载库 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:"< coords;     //顶点数据
    std::vector texturCoords;   //uv贴图顶点
    std::vector normals;     //法线数据
    
    std::vector vertexes;
    std::vector indexes;
    
    MSMesh *mesh = nullptr;
    
    std::string mtlName;
    std::string lineString;
    
    while (std::getline(inputStream,lineString))
    {
    
        std::vector 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 vert = ccStringSplit(list[i],"/");
                vertexes.emplace_back(VertexData(
                        coords[static_cast(atol(vert[0].c_str())) - 1],
                        texturCoords[static_cast(atol(vert[1].c_str())) - 1],
                        normals[static_cast(atol(vert[2].c_str())) -1 ])
                        );
                indexes.emplace_back(static_cast(indexes.size()));
            }
            continue;
        } else if (list[0] == "usemtl") {
            mtlName = list[1];
            std::cout<< "This is used naterial:" << mtlName;
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    通过不同的字段就能解析3d模型文件,将解析好的数据保存到vertexes 和 indexes中

    4.进行TBN空间计算
    void MSModelLoader::calculateTBN(std::vector &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;
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    由于模型中只保存了,顶点数据、材质顶点数据、法线数据以及顶点的index,由于要进行法线纹理贴图需要切线和二维切线数据,

    所以通过TBN空间计算得到切线以及二维切线来加载法线纹理贴图。

    5.加载顶点以及顶点序列数据
    void MSMesh::InitRenderResources(AAssetManager *pManager, const std::vector &vertData, const std::vector &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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    由于3D模型顶点数量非常多,复杂的模型可能达到百万顶点或者更多,所以需要用到VBO、EBO以及VAO等,否则频繁的从CPU传递数据到GPU会卡死。

    这一步就是将解析到的模型的顶点数据以及顶点序列数据加载到shader中。

    6.进行渲染操作
    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();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    渲染的操作大部分都是在通过uniform进行给sahder传数据

    由于使用VBO、EBO、VAO,渲染操作只需要执行glDrawElements就可以了。

    7.顶点着色器
    #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空间矩阵  传给片元着色器
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • MVP矩阵*顶点坐标得到的是屏幕坐标
    • transpose 求转置矩阵
    • inverse 逆矩阵
    • normalize 归一化
    8.片元着色器
    #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) ;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • dot:点乘得到的是两个向量的夹角
    • reflect:进行反射,镜面光用到
    • 片元着色器的本质就是给GPU传递一个颜色数据
  • 相关阅读:
    仿照Everything实现的文件搜索工具--SearchEverything
    【前端指南】session和token
    Excel - 使用VBA通过ADO数据库连接来操作一个Excel数据源
    2`,7`-二氯二氢荧光素,CAS号: 106070-31-9
    最新win11配置cuda以及cudnn补丁教程
    DP20 计算字符串的编辑距离
    arguments对象
    函数的实参和形参
    【SQLite】二、SQLite 和 HeidiSQL 的安装
    jvm之java类加载机制和类加载器(ClassLoader)的详解
  • 原文地址:https://blog.csdn.net/u014078003/article/details/128010355