扒几个 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
扒几个 3D 模型备用的更多相关文章
- Unity3D游戏开发初探—2.初步了解3D模型基础
一.什么是3D模型? 1.1 3D模型概述 简而言之,3D模型就是三维的.立体的模型,D是英文Dimensions的缩写. 3D模型也可以说是用3Ds MAX建造的立体模型,包括各种建筑.人物.植被. ...
- Mask裁切UI粒子特效或者3D模型
刚好前几天有人问我这个问题,再加上新项目也可能用,所以这两天就研究了一下.其实如果粒子特效 和3D模型 都用RenderTexture来做的话就不会有裁切的问题,但是粒子特效用RenderTextur ...
- 如何让NGUI的对象在3D模型之上
假设场景中有两台摄像机, 一台是NGUI的摄像机, 另外一台是投影摄像机. 投影摄像机看的是3D模型, Depth比NGUI的摄像机要大, Clear Flags设置的是Depth only. 现在想 ...
- HT for Web自定义3D模型的WebGL应用
有不少朋友询问<HTML5 Web 客户端五种离线存储方式汇总>文章例子的3D表计模型是如何生成的,这个例子是通过导入3dmax设计好的表计模型,然后通过obj格式导入到HT for We ...
- 8月7号晚7点Autodesk北京办公室,我们来聊聊HTML5/ WebGL 3D 模型浏览技术
Autodesk 发布了一款完全无需插件的三维模型浏览器 Autodesk 360 Viewer,大家有没有兴趣,下班后过来聊聊吧! 8月7号 周四, 19:00~21:00 Autodesk北京 ...
- WPF 3D模型 3D场景
1.首先得说明的是这并不是真正的3D,模型被导出为一系列的单个图片,例如一个3D户型图,以某个视角旋转360°,渲染出一系列连续的单个图片文件. 2.在Image.MouseMove事件中添加相应代码 ...
- unity3d设置3D模型显示在2D背景之前(多个相机分层显示)(转)
解决步骤: 1.添加一个摄像机,命名为BackgroundCamera,然后在Layer添加一个background层.并且将plane拖放到改相机节点下. 然后将BackgroundCamera和P ...
- 关于PCB 3D 模型的快速导入方法
altium designer中创建的3D library 只能查看3D效果,并没有其他功能,经测试在原理图编辑界面通过给元件添加 PCB 3D 并不能真正添加3D模型,这样添加根本没有效果(显示不出 ...
- 【pano2vr】网页Flash中简单实现炫酷的3D模型制作
花了两天时间学习如何能够高效的实现3D模型效果,毕竟是从0开始学习,感觉pano2vr这款软件挺容易上手,并且可以很容易实现简单的热点交互,可以根据交互需求设置皮肤,故将这一款软件推荐给大家: 1.简 ...
随机推荐
- 2019牛客暑期多校训练营(第七场)E-Find the median(思维+树状数组+离散化+二分)
>传送门< 题意:给n个操作,每次和 (1e9范围内)即往数组里面插所有 的所有数,求每次操作后的中位数思路:区间离散化然后二分答案,因为小于中位数的数字恰好有个,这显然具有单调性.那么问 ...
- Codeforces Round #657 (Div. 2) B. Dubious Cyrpto(数论)
题目链接:https://codeforces.com/contest/1379/problem/B 题意 给出三个正整数 $l,r,m$,判断在区间 $[l,r]$ 内是否有 $a,b,c$ 满足存 ...
- zjnu1762 U (线段树)
Description Mirko is hungry as a bear, scratch that, programmer and has stumbled upon a local restau ...
- 【uva 1151】Buy or Build(图论--最小生成树+二进制枚举状态)
题意:平面上有N个点(1≤N≤1000),若要新建边,费用是2点的欧几里德距离的平方.另外还有Q个套餐,每个套餐里的点互相联通,总费用为Ci.问让所有N个点连通的最小费用.(2组数据的输出之间要求有换 ...
- Codeforces Round #646 (Div. 2) E. Tree Shuffling dfs
题意: 给你n个节点,这n个节点构成了一颗以1为树根的树.每一个节点有一个初始值bi,从任意节点 i 的子树中选择任意k个节点,并按他的意愿随机排列这些节点中的数字,从而产生k⋅ai 的成本.对于一个 ...
- 四、Jmeter 集合点(实际场景应用)
一.jmeter集合点的作用域及作用范围 先明确一些概念:1)定时器是在每个sampler(采样器)之前执行的,而不是之后: 是的,你没有看错,不管这个定时器的位置放在sampler之后,还是之下,它 ...
- SPI/QSPI通信协议详解和应用
SPi是高速全双工的串行总线,通常应用在通讯速率较高的场合. SS:从设备选择信号线,也称片选信号线 每个从设备都有一个独立的SS信号线,信号线独占主机的一个引脚,及有多少个从设备就有多少个片选信号线 ...
- Python 分析热卖年货,今年春节大家都在送啥?
今年不知道有多少小伙伴留在原地过年,虽然今年过年不能回老家,但这个年也得过,也得买年货,给家人长辈送礼.于是我出于好奇心的想法利用爬虫获取某宝数据,并结合 Python 数据分析和第三方可视化平台来分 ...
- [Python] Pandas 对数据进行查找、替换、筛选、排序、重复值和缺失值处理
目录 1. 数据文件 2. 读数据 3. 查找数据 4. 替换数据 4.1 一对一替换 4.2 多对一替换 4.3 多对多替换 5. 插入数据 6. 删除数据 6.1 删除列 6.2 删除行 7. 处 ...
- Ubuntu Live CD联网修复
此模式下可以联网修复ubuntu系统下绝大多数问题.进入LIVE CD模式,打开终端执行以下命令: #此处/dev/sda1为ubuntu根分区,工作中根据实际分区情况更改 sudo mount /d ...