zoukankan      html  css  js  c++  java
  • 扒几个 3D 模型备用

    前言

    在上一篇中,我展示了 OpenGL 开发的基本过程,算是向 3D 世界迈出的一小步吧。对于简单的 3D 物体,比如立方体、球体、圆环等等,我们只需要简单的计算就可以得到他们的顶点的坐标。但是仅仅这样,还不是太过瘾,我们需要找一些复杂一点的 3D 模型,以便于我们体会 3D 世界的魅力。

    在我学习 OpenGL 的过程中,我收集了不少的 3D 模型,主要是从 Free3D 下载的,都是 Obj 格式的文件,有的带纹理贴图,有的不带纹理贴图。比如,有一个小木屋的模型,带纹理贴图和法线贴图,是我学习贴图和光照的好素材。还有一个地球的模型,还有几辆汽车的模型。还有我从著名的 OpenGL 网络教程 LearnOpenGL 中下载得有一套 nanosuit 的模型。对于这些有着规范格式的 3D 模型,我觉得使用 Assimp 库加载是比较好的选择,至于 Assimp 库,以后再介绍。

    另外,茶壶也是一个经典的模型,不过是以贝塞尔曲面的方式定义的。贝塞尔曲面其实不难,使用 16 个控制点可以描述一个曲面,并且可以根据我们需要的光滑程度选择不同的细分级别,关于贝塞尔曲面的内容留待以后再专讲,而且我觉得和曲面细分着色器一起学习效果更佳。那么这个茶壶模型的数据在哪里可以找到呢?FreeGlut 中有,可以在 github 中找到。除此之外,红宝书的源代码中也有一个茶壶的数据。这里不赘述。

    我这里要扒的几个模型来自红宝书的源代码,它们分别是 armadillo.vbm、 bunny.vbm 和 ninja.vbm。这里,作者使用了他自创的 vbm 模型格式。作者还写了从 obj 格式到 vbm 格式转换的工具以及从 Maya 导出 vbm 格式的工具。但毕竟 vbm 格式不是标准的通用格式,我并不是很喜欢。但是为了把这三个模型显示出来看看,我还是认真研究了作者的源代码。

    VBM 模型文件的具体细节

    我是通过阅读红宝书源代码中的 vbm.h 和 vbm.cpp 文件来了解 vbm 模型文件的细节的。这是一个二进制的模型文件,一开始是个 VBM_HEADER 结构,在作者的设计中,该文件分为新版和旧版,旧版的头部结构为 VBM_HEADER_OLD,但是从我扒出的数据来看,根本就不需要考虑旧版。

    在 VBM_HEADER 之后,是若干个 VBM_ATTRIB_HEADER 结构,该结构用来说明每个顶点包含哪些属性,每个属性又包含哪些分量。从我扒出的数据来看,以上三个模型,都是包含三个属性的,分别是顶点坐标,包含 4 个 GLfloat 分量,顶点法向量,包含 3 个 GLfloat 分量,纹理贴图坐标,包含两个 GLfloat 分量。这和我上一篇中对顶点格式的设计简直一模一样。

    在 VBM_ATTRIB_HEADER 之后,是若干个 VBM_FRAME_HEADER,看来该作者设计该格式是可以支持动画的。不过以我扒出的数据来看,以上三个模型文件都只包含一帧。

    在 VBM_FRAME_HEADER 之后就是顶点数据。从头文件中可以得到顶点的个数,以及每个顶点包含哪些属性,以及每个属性包含几个分量,就很容易算出顶点数据的长度。

    顶点数据之后,就是索引数据。我读源代码,同时还发现顶点数据之后是材质信息。这两组数据是有点混淆的。好在,以我扒出的数据来看,以上三个模型文件既没有使用索引,也没有包含任何材质,那倒是让我省事了不少。

    编写我自己的 VbmObject 类

    参考我之前写的 Mesh 类,就很容易写一个能在我的 App 框架中非常容易使用的 VbmObject 类。在 VbmObject 类中,写一个 loadFromVBM() 方法,以从文件中加载顶点数据,同时获取顶点个数的信息。然后写一个 setup() 方法,用来创建相应的 VAO 和 VBO,并向缓存中存入数据,并启用顶点属性。这里需要特别注意的是,该模型文件中的数据,是每一个属性集中存放的,所以调用 glVertexAttribPointer() 方法时要特别注意。最后,写一个 render() 方法进行渲染,render() 方法很简单,就是调用 glDrawArrays(),当然,调用该方法之前需要绑定 VAO。

    vbm.hpp 的完整代码如下:

    #ifndef __VBM_H__
    #define __VBM_H__
    
    #include <glm/glm.hpp>
    #include <glm/gtc/matrix_transform.hpp>
    #include <vector>
    #include <string>
    #include <string.h>
    #include <GL/glew.h>
    #include <iostream>
    
    typedef struct VBM_HEADER_t
    {
        unsigned int magic;
        unsigned int size;
        char name[64];
        unsigned int num_attribs;
        unsigned int num_frames;
        unsigned int num_vertices;
        unsigned int num_indices;
        unsigned int index_type;
        unsigned int num_materials;
        unsigned int flags;
    } VBM_HEADER;
    
    typedef struct VBM_ATTRIB_HEADER_t
    {
        char name[64];
        unsigned int type;
        unsigned int components;
        unsigned int flags;
    } VBM_ATTRIB_HEADER;
    
    typedef struct VBM_FRAME_HEADER_t
    {
        unsigned int first;
        unsigned int count;
        unsigned int flags;
    } VBM_FRAME_HEADER;
    
    class VbmObject{
        protected:
            unsigned char* file_data;
            unsigned char* vertex_data;
            unsigned int vertex_num;
            GLuint VAO, VBO;
                   
        public:
            bool loadFromVBM(const char * filename){
                std::cout << "File name: " << filename << std::endl;
                FILE * f = NULL;
                f = fopen(filename, "rb");
                if(f == NULL)
                    return false;
    
                fseek(f, 0, SEEK_END);
                size_t filesize = ftell(f);
                fseek(f, 0, SEEK_SET);
    
                file_data = new unsigned char [filesize];
                fread(file_data, filesize, 1, f);
                fclose(f);
    
                VBM_HEADER * header = (VBM_HEADER *)file_data;
                vertex_data = file_data + header->size + header->num_attribs * sizeof(VBM_ATTRIB_HEADER) + header->num_frames * sizeof(VBM_FRAME_HEADER);
                vertex_num = header->num_vertices;
                std::cout << "Num of Vertices: " << vertex_num << std::endl;
    
                return true;
            }
    
            void setup(){
                glCreateVertexArrays(1, &VAO);
                glBindVertexArray(VAO);
                glCreateBuffers(1, &VBO);
                glBindBuffer(GL_ARRAY_BUFFER, VBO);
                glNamedBufferStorage(VBO, 9*sizeof(GLfloat)*vertex_num, vertex_data, 0);
                glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);
                glEnableVertexAttribArray(0);
                glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(GLfloat)*vertex_num*4));
                glEnableVertexAttribArray(1);
                glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(GLfloat)*vertex_num*3));
                glEnableVertexAttribArray(2);
            }
    
            void render(){
                glBindVertexArray(VAO);
                glDrawArrays(GL_TRIANGLES, 0, vertex_num);
            }
    
            ~VbmObject(){
                if(file_data != NULL){
                    delete file_data;
                }
            }
    };
    
    #endif
    

    主程序文件是 DumpVbm.cpp,其框架结构还是和前面的差不多,先是继承 App 类,在 init() 方法中初始化数据,比如调用 VbmObject 对象的 loadFromVBM() 方法,调用 setup() 方法,同时创建 shader。然后在 display() 中准备模型、视图、投影矩阵,向 shader 中传递这些矩阵数据,然后调用 VbmObject 对象的 render() 方法。

    DumpVbm.cpp 的完整内容如下:

    #include "../include/app.hpp"
    #include "../include/shader.hpp"
    #include "../include/vbm.hpp"
    #include <glm/glm.hpp>
    #include <glm/gtc/matrix_transform.hpp>
    
    class MyApp : public App {
        private:
            const GLfloat clearColor[4] = {0.2f, 0.3f, 0.3f, 1.0f};
            VbmObject armadillo;
            VbmObject bunny;
            VbmObject ninja;
            Shader* shaderDumpVbm;
    
        public:
            void init(){
                
                ShaderInfo shaders[] = {
                    {GL_VERTEX_SHADER, "dumpvbm.vert"},
                    {GL_FRAGMENT_SHADER, "dumpvbm.frag"},
                    {GL_NONE, ""}
                };
                shaderDumpVbm = new Shader(shaders);
                armadillo.loadFromVBM("armadillo.vbm");
                armadillo.setup();
    
                bunny.loadFromVBM("bunny.vbm");
                bunny.setup();
    
                ninja.loadFromVBM("ninja.vbm");
                ninja.setup();
                
                glEnable(GL_DEPTH_TEST);
                glDepthFunc(GL_LEQUAL);
    
                glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
            }
    
            void display(){
                glClearBufferfv(GL_COLOR, 0, clearColor);
                glClear(GL_DEPTH_BUFFER_BIT);
    
                glm::mat4 I(1.0f);
                glm::vec3 X(1.0f, 0.0f, 0.0f);
                glm::vec3 Y(0.0f, 1.0f, 0.0f);
                glm::vec3 Z(0.0f, 0.0f, 1.0f);
                float t = (float)glfwGetTime();
    
                glm::mat4 view_matrix = glm::translate(I, glm::vec3(0.0f, 0.0f, -5.0f))
                                            * glm::rotate(I, t, Y);
    
                glm::mat4 projection_matrix = glm::perspective(glm::radians(45.0f), aspect, 1.0f, 100.0f);
    
                glm::mat4 armadillo_model_matrix = glm::translate(I, glm::vec3(-2.0f, 0.0f, 0.0f)) * glm::scale(I, glm::vec3(0.015f, 0.015f, 0.015f)) * glm::rotate(I, glm::radians(180.0f), Y);
                
                shaderDumpVbm->setModelMatrix(armadillo_model_matrix);
                shaderDumpVbm->setViewMatrix(view_matrix);
                shaderDumpVbm->setProjectionMatrix(projection_matrix);
                shaderDumpVbm->setCurrent();
                armadillo.render();
    
                glm::mat4 bunny_model_matrix =  glm::scale(I, glm::vec3(10.0f, 10.0f, 10.0f));
                shaderDumpVbm->setModelMatrix(bunny_model_matrix);
                bunny.render();
    
                glm::mat4 ninja_model_matrix = glm::translate(I, glm::vec3(2.0f, -1.0f, 0.0f)) * glm::scale(I, glm::vec3(0.015f, 0.015f, 0.015f));
                shaderDumpVbm->setModelMatrix(ninja_model_matrix);
                ninja.render();
            }
    
            ~MyApp(){
                if(shaderDumpVbm != NULL){
                    delete shaderDumpVbm;
                }
            }
    
    };
    
    
    DECLARE_MAIN(MyApp)
    

    shader 文件和之前没有区别。编译运行,命令如下:

    g++ DumpVbm.cpp -o DumpVbm -lGL -lglfw -lGLEW
    ./DumpVbm
    

    就可以看到效果了。如下:

    版权申明

    该随笔由京山游侠在2021年02月23日发布于博客园,引用请注明出处,转载或出版请联系博主。QQ邮箱:1841079@qq.com

  • 相关阅读:
    Path Sum II
    Convert Sorted Array to Binary Search Tree
    Construct Binary Tree from Inorder and Postorder Traversal
    Construct Binary Tree from Preorder and Inorder Traversal
    Maximum Depth of Binary Tree
    Binary Tree Zigzag Level Order Traversal
    Binary Tree Level Order Traversal
    Same Tree
    Validate Binary Search Tree
    Binary Tree Inorder Traversal
  • 原文地址:https://www.cnblogs.com/youxia/p/cg004.html
Copyright © 2011-2022 走看看