前言

在上一篇中,我展示了 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

扒几个 3D 模型备用的更多相关文章

  1. Unity3D游戏开发初探—2.初步了解3D模型基础

    一.什么是3D模型? 1.1 3D模型概述 简而言之,3D模型就是三维的.立体的模型,D是英文Dimensions的缩写. 3D模型也可以说是用3Ds MAX建造的立体模型,包括各种建筑.人物.植被. ...

  2. Mask裁切UI粒子特效或者3D模型

    刚好前几天有人问我这个问题,再加上新项目也可能用,所以这两天就研究了一下.其实如果粒子特效 和3D模型 都用RenderTexture来做的话就不会有裁切的问题,但是粒子特效用RenderTextur ...

  3. 如何让NGUI的对象在3D模型之上

    假设场景中有两台摄像机, 一台是NGUI的摄像机, 另外一台是投影摄像机. 投影摄像机看的是3D模型, Depth比NGUI的摄像机要大, Clear Flags设置的是Depth only. 现在想 ...

  4. HT for Web自定义3D模型的WebGL应用

    有不少朋友询问<HTML5 Web 客户端五种离线存储方式汇总>文章例子的3D表计模型是如何生成的,这个例子是通过导入3dmax设计好的表计模型,然后通过obj格式导入到HT for We ...

  5. 8月7号晚7点Autodesk北京办公室,我们来聊聊HTML5/ WebGL 3D 模型浏览技术

    Autodesk 发布了一款完全无需插件的三维模型浏览器 Autodesk 360 Viewer,大家有没有兴趣,下班后过来聊聊吧!   8月7号 周四, 19:00~21:00 Autodesk北京 ...

  6. WPF 3D模型 3D场景

    1.首先得说明的是这并不是真正的3D,模型被导出为一系列的单个图片,例如一个3D户型图,以某个视角旋转360°,渲染出一系列连续的单个图片文件. 2.在Image.MouseMove事件中添加相应代码 ...

  7. unity3d设置3D模型显示在2D背景之前(多个相机分层显示)(转)

    解决步骤: 1.添加一个摄像机,命名为BackgroundCamera,然后在Layer添加一个background层.并且将plane拖放到改相机节点下. 然后将BackgroundCamera和P ...

  8. 关于PCB 3D 模型的快速导入方法

    altium designer中创建的3D library 只能查看3D效果,并没有其他功能,经测试在原理图编辑界面通过给元件添加 PCB 3D 并不能真正添加3D模型,这样添加根本没有效果(显示不出 ...

  9. 【pano2vr】网页Flash中简单实现炫酷的3D模型制作

    花了两天时间学习如何能够高效的实现3D模型效果,毕竟是从0开始学习,感觉pano2vr这款软件挺容易上手,并且可以很容易实现简单的热点交互,可以根据交互需求设置皮肤,故将这一款软件推荐给大家: 1.简 ...

随机推荐

  1. hbase 集群(完全分布式)方式安装

    一,环境 1,  主节点一台: ubuntu desktop 16.04 zhoujun      172.16.12.1 从节点(slave)两台:ubuntu server 16.04 hadoo ...

  2. Leetcode LRU缓存,数组+结构体实现

    一.算法思路 LRUCache类有以下函数和变量: LRUCache(int capacity): capacity是当前对象能够存储的键值对(key,value)最大个数. int get(int ...

  3. D - Wireless Network(无线网络)

    题意:给你 N 台坏了的电脑的坐标 ,和一个距离范围 d . (在距离范围内的电脑可以相互通信,每台电脑也可以连接两台不同的电脑,使他们之间能够通信) 输入任意次数操作: O   x        表 ...

  4. 【noi 2.6_1481】Maximum sum(DP)

    题意:求不重叠的2段连续和的最大值. 状态定义f[i]为必选a[i]的最大连续和,mxu[i],mxv[i]分别为前缀和后缀的最大连续和. 注意:初始化f[]为0,而max值为-INF.要看好数据范围 ...

  5. 关于最小生成树 Kruskal 和 Prim 的简述(图论)

    模版题为[poj 1287]Networking. 题意我就不说了,我就想简单讲一下Kruskal和Prim算法.卡Kruskal的题似乎几乎为0.(●-`o´-)ノ 假设有一个N个点的连通图,有M条 ...

  6. HDU 3537 Daizhenyang's Coin 翻硬币博弈

    题意: 给你n个硬币,你可以从中拿出来1.2.3个硬币,它们不一定要连续,你只需要保证拿出来的硬币中那个下标最大的硬币一定要是正面朝上,最后谁不能操作,谁就输了 题解: 翻硬币游戏 结论: 局面的SG ...

  7. Codeforces Round #531 (Div. 3) D. Balanced Ternary String (贪心)

    题意:给你一个长度为\(3*n\)的字符串,要求修改最少的次数,使得字符串中\(0,1,2\)的个数相同,并且在最少次数的情况下使字典序最小. 题解:贪心,\(0\)一定放在前面,\(1\)和\(2\ ...

  8. Bing壁纸-20200417

  9. MySQL5.6 与 MySQL5.7 的区别

    目录 编译安装区别 初始化的区别 其他区别 编译安装区别 # 5.7在编译安装的时候多了一个 boost 库 [root@db02 mysql-5.7.20]# yum install -y gcc ...

  10. 动态规划算法 All In One

    动态规划算法 All In One dynamic programming leetcode https://leetcode.com/tag/dynamic-programming/ https:/ ...