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

  • 相关阅读:
    [JavaEE] Hibernate ORM
    [PHP] htaccess 探秘
    [JavaEE] SSH框架搭建所需要的包
    博客园使用技巧
    vs快捷键
    算法:递归、循环、迭代、哈希表、查找、内排序、外排序
    【译】.NET中六个重要的概念:栈、堆、值类型、引用类型、装箱和拆箱 --转载
    .NET框架与开发语言:相关框架、共用部分、开发语言、一些疑问
    c#原理:c#代码是怎么运行的、实例化时发生了什么、静态对象(类、方法、变量、属性)的原理
    EA:UML建模-流程图、时序图、部署图
  • 原文地址:https://www.cnblogs.com/youxia/p/cg004.html
Copyright © 2011-2022 走看看