前言

在上一篇中,我展示了 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. linux驴杂记

    1. 命令行 vim下 的Ctrl + S ,ctrl +Q: 这个功能可以用于长时间编译的时候随时把屏幕输出停住,但是编译仍然会继续,这样就不会影响到编译时间了. S:是锁屏  q:是开屏 2. S ...

  2. python--基础2 (数据类型及应用)

    资源池 链接:https://pan.baidu.com/s/1OGq0GaVcAuYEk4F71v0RWw 提取码:h2sd python数据类型 字符串 列表 字典 数字(整数) 数字(浮点数) ...

  3. 看KubeEdge携手K8S,如何管理中国高速公路上的10万边缘节点

    摘要:为保证高速公路上门架系统的落地项目的成功落地,选择K8s和KubeEdge来进行整体的应用和边缘节点管理. 一.项目背景 本项目是在高速公路ETC联网和推动取消省界收费站的大前提下,门架系统的落 ...

  4. P2805 [NOI2009]植物大战僵尸 (拓扑排序 + 最小割)

    题意:N*M的矩阵 每个点上都有一颗植物 僵尸只能从每一行的最右边向左进攻 每个植物有攻击范围 可以保护在攻击范围内的植物 同时每一颗植物也保护他左边的植物 摧毁每个植物能获得价值 如果这个植物被保护 ...

  5. UVALive 7276 Wooden Signs

    详细题目见:http://7xjob4.com1.z0.glb.clouddn.com/0f10204481da21e62f8c145939e5828e 思路:记dp[i][j]表示第i个木板尾部在j ...

  6. Proud Merchants HDU - 3466 01背包&&贪心

    最近,我去了一个古老的国家.在很长一段时间里,它是世界上最富有.最强大的王国.结果,这个国家的人民仍然非常自豪,即使他们的国家不再那么富有.商人是最典型的,他们每个人只卖一件商品,价格是Pi,但是如果 ...

  7. Codeforces Round #656 (Div. 3) C. Make It Good (贪心,模拟)

    题意:给你一个数组\(a\),可以删除其前缀,要求操作后得到的数组是"good"的.对于"good":可以从数组的头和尾选择元素移动到新数组,使得所有元素移动后 ...

  8. Dubbo和SpringCloud的优劣势比较--总体架构

    从整体架构上来看 二者模式接近,都需要服务提供方,注册中心,服务消费方.差异不大.详见下方: Dubbo Provider: 暴露服务的提供方,可以通过jar或者容器的方式启动服务 Consumer: ...

  9. MySQL数据库系列(四)- InnoDB下的共享表空间和独立表空间详解

    一.概念 共享表空间: Innodb的所有数据保存在一个单独的表空间里面,而这个表空间可以由很多个文件组成,一个表可以跨多个文件存在,所以其大小限制不再是文件大小的限制,而是其自身的限制.从Innod ...

  10. 【非原创】codeforces 1060E Sergey and Subway 【树上任意两点距离和】

    学习博客:戳这里 本人代码: 1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 con ...