前言

要想让自己的 3D 之旅多一点乐趣,肯定得想办法找一些有意思一点的 3D 模型。3D 模型有各种各样的格式,obj的,stl的,fbx的等等不一而足。特别是 obj 格式的 3D 模型,完全是纯文本格式,网络上很多高手都喜欢自己写程序对 obj 格式的模型文件进行解析。我自己收集了一些 3D 模型,有几个 obj 格式的,也有几个 stl 格式的,为我以后的学习做准备。当我需要查看这些模型的时候,我首选是使用 Blender。在我的程序中使用的时候,我首选 Assimp 库。在我之前的随笔中,已经对 Assimp 库做了介绍。见 为什么说使用 Linux 系统学习 OpenGL 更方便

使用 Assimp 加载 3D 模型

Assimp 的使用是非常简单的,直接参考 Assimp 的文档,依葫芦画瓢即可。这里我只简单介绍一下 Assimp 中的数据组织结构。Assimp 读入模型后,先有一个 aiScene 对象,在这个对象中,有一个根节点,根节点又有子结点,子结点还可能有子结点,形成一个树状的结构。节点的类型是 aiNode。每一个节点包含一个或多个 aiMesh,而 aiMesh 又包含顶点数据和索引数据,索引数据是储存在 aiFace 类型中的,一般来说,我们的每一个 aiFace 都应该是一个三角形。每一个 aiMesh 又有对应的材质信息,因为我这里还没有涉及到光照和贴图,所以暂不考虑材质信息。只需要递归地进行解析,我们就可以很容易获得模型中的所有顶点信息和索引信息。

在这里,我创建了一个 Model 类,同时利用了之前的 Mesh 类,一个 Model 类的对象中可以包含多个 Mesh 类的对象。然后,在 Model 类的 loadModel() 方法中,使用 Assimp 读取模型数据,遍历各 aiNode, 再遍历 aiMesh,将 aiMesh 中的数据拷贝到 Mesh 中,然后调用所有 Mesh 对象的 setup() 方法和 render() 方法,即可完成模型的渲染。Model 类的内容如下:

#ifndef __MODEL_HPP__
#define __MODEL_HPP__ #include "mesh.hpp"
#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>
#include <assimp/scene.h> class Model
{
private:
std::vector<Mesh> meshes; public:
void loadModel(std::string filename)
{
Assimp::Importer importer;
const aiScene *scene = importer.ReadFile(filename,
aiProcess_Triangulate |
aiProcess_GenNormals |
aiProcess_FlipUVs);
processNode(scene->mRootNode, scene);
} void processNode(aiNode *node, const aiScene *scene)
{
for (int i = 0; i < node->mNumMeshes; i++)
{
aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
Vertex tempVertex;
std::vector<Vertex> tempVertices;
std::vector<GLuint> tempIndices;
//先读取顶点信息
for (int j = 0; j < mesh->mNumVertices; j++)
{
if (mesh->HasPositions())
{
tempVertex.position.x = mesh->mVertices[j].x;
tempVertex.position.y = mesh->mVertices[j].y;
tempVertex.position.z = mesh->mVertices[j].z;
tempVertex.position.w = 1.0f;
}
if (mesh->HasNormals())
{
tempVertex.normal.x = mesh->mNormals[j].x;
tempVertex.normal.y = mesh->mNormals[j].y;
tempVertex.normal.z = mesh->mNormals[j].z;
}
if (mesh->HasTextureCoords(0))
{
tempVertex.texCoord.x = mesh->mTextureCoords[0][j].x;
tempVertex.texCoord.y = mesh->mTextureCoords[0][j].y;
} tempVertices.push_back(tempVertex);
}
//再读取索引信息
for (int i = 0; i < mesh->mNumFaces; i++)
{
for (int j = 0; j < mesh->mFaces[i].mNumIndices; j++)
{
tempIndices.push_back(mesh->mFaces[i].mIndices[j]);
}
}
Mesh tempMesh; tempMesh.setVertices(std::move(tempVertices));
tempMesh.setIndices(std::move(tempIndices));
tempMesh.setup();
this->meshes.push_back(std::move(tempMesh));
} if (node->mNumChildren != 0)
{
for (int k = 0; k < node->mNumChildren; k++)
{
processNode(node->mChildren[k], scene);
}
}
return;
} void render()
{
for (auto i = meshes.begin(); i != meshes.end(); i++)
{
i->render();
}
}
}; #endif

然后来测试一下我之前收集的那些模型文件,其主文件 LoadModel.cpp 内容如下:

#include "../include/app.hpp"
#include "../include/shader.hpp"
#include "../include/model.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};
Model allis;
Model buma;
Model saber;
Model cottage;
Model bugatti;
Shader* simpleShader; public:
void init(){ ShaderInfo shaders[] = {
{GL_VERTEX_SHADER, "simpleShader.vert"},
{GL_FRAGMENT_SHADER, "simpleShader.frag"},
{GL_NONE, ""}
};
simpleShader = new Shader(shaders);
allis.loadModel( "allis.stl");
buma.loadModel("buma.stl");
saber.loadModel( "saber.stl");
bugatti.loadModel("bugatti/bugatti.obj"); 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, -10.0f))
* glm::rotate(I, t, Y); glm::mat4 projection_matrix = glm::perspective(glm::radians(45.0f), aspect, 1.0f, 100.0f); glm::mat4 allis_model_matrix = glm::translate(I, glm::vec3(-3.0f, -1.5f, 0.0f))
* glm::scale(I, glm::vec3(1.0f, 1.0f, 1.0f)) * glm::rotate(I, glm::radians(-90.0f), X); simpleShader->setModelMatrix(allis_model_matrix);
simpleShader->setViewMatrix(view_matrix);
simpleShader->setProjectionMatrix(projection_matrix);
simpleShader->setCurrent();
allis.render(); glm::mat4 buma_model_matrix = glm::translate(I, glm::vec3(-1.0f, -1.5f, 0.0f))
* glm::scale(I, glm::vec3(0.03f, 0.03f, 0.03f)) * glm::rotate(I, glm::radians(0.0f), X);
simpleShader->setModelMatrix(buma_model_matrix);
buma.render(); glm::mat4 saber_model_matrix = glm::translate(I, glm::vec3(7.5f, 0.6f, 1.0f))
* glm::scale(I, glm::vec3(0.03f, 0.03f, 0.03f)) * glm::rotate(I, glm::radians(-90.0f), X);
simpleShader->setModelMatrix(saber_model_matrix);
saber.render(); glm::mat4 bugatti_model_matrix = glm::translate(I, glm::vec3(5.0f, -1.0f, 0.0f))
* glm::scale(I, glm::vec3(0.2f, 0.2f, 0.2f)) * glm::rotate(I, glm::radians(0.0f), X);
simpleShader->setModelMatrix(bugatti_model_matrix);
bugatti.render(); } ~MyApp(){
if(simpleShader != NULL){
delete simpleShader;
}
} }; DECLARE_MAIN(MyApp)

效果如下:

我这里只展示了三个美女和一个汽车,从左侧的文件列表中可以看出,我还收集了不少其它的模型,以后再逐渐与大家见面。

目前看起来还只是白茫茫一片,那是因为模型太精细了,所以看不清楚网格。下一步,我将在我的程序框架中添加改变窗口大小、在场景中漫游、线框模型和面模型之间切换等功能。今天就到这里吧。

版权申明

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

使用 Assimp 库加载 3D 模型的更多相关文章

  1. cesium加载gltf模型点击以及列表点击定位弹窗

    前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. 之 ...

  2. Android OpenGL库加载过程源码分析

    Android系统采用OpenGL绘制3D图形,使用skia来绘制二维图形:OpenGL源码位于: frameworks/native/opengl frameworks/base/opengl 本文 ...

  3. WPF动态加载3D 放大-旋转-平移

    原文:WPF动态加载3D 放大-旋转-平移 WavefrontObjLoader.cs 第二步:ModelVisual3DWithName.cs public class ModelVisual3DW ...

  4. xBIM 实战01 在浏览器中加载IFC模型文件

    系列目录    [已更新最新开发文章,点击查看详细]  一.创建Web项目 打开VS,新建Web项目,选择 .NET Framework 4.5  选择一个空的项目 新建完成后,项目结构如下: 二.添 ...

  5. cesium加载gltf模型

    cesium加载gltf模型 一.采用vue-cesium:在项目里加载依赖包.命令如下: npm i --save vue-cesium 在main.js中加入如下代码: https://www.n ...

  6. WebGL three.js学习笔记 加载外部模型以及Tween.js动画

    WebGL three.js学习笔记 加载外部模型以及Tween.js动画 本文的程序实现了加载外部stl格式的模型,以及学习了如何把加载的模型变为一个粒子系统,并使用Tween.js对该粒子系统进行 ...

  7. 安卓奇葩问题之.so库加载不了

    真是哔了狗了. 今天突然遇到一个问题:之前用第三方的密码控件,给了一个.so库文件.然后我就放在了/jniLibs/armeabi目录下. 运行,一切都很OK. 然后重点来了.N天之后的今天,突然打包 ...

  8. LIB库加载方法-引用百度百科

    LIB库加载方法,有三种,如下: 1.LIB文件直接加入到工程文件列表中 在VC中打开File View一页,选中工程名,单击鼠标右键,然后选中\"Add Files to Project\ ...

  9. linux动态库加载RPATH, RUNPATH

    摘自http://gotowqj.iteye.com/blog/1926771 linux动态库加载RPATH, RUNPATH 链接动态库 如何程序在连接时使用了共享库,就必须在运行的时候能够找到共 ...

随机推荐

  1. moment常用方法

    1.subtract方法,时间加减处理 console.log(moment().format("YYYY-MM-DD HH:mm:ss")); //当前时间 console.lo ...

  2. 『心善渊』Selenium3.0基础 — 8、使用CSS选择器定位元素

    目录 1.CSS选择器介绍 2.CSS选择器定位语法 3.Selenium中使用CSS选择器定位元素 (1)通过属性定位元素 (2)通过标签定位元素 (3)通过层级关系定位元素 (4)通过索引定位元素 ...

  3. 通过浏览器运行cmd命令、启动steam

    我们先来看看实现起来的效果,我们在浏览器中输入ping so.com 试试打开计算器.启动steam 要实现这个效果其实用到了浏览器自定义协议,我们可以通过自定义协议打开wechat.扣扣.emali ...

  4. Tkinter 吐槽之一:多线程与 UI 交互

    背景 最近想简单粗暴的用 Python 写一个 GUI 的小程序.因为 Tkinter 是 Python 自带的 GUI 解决方案,为了部署方便,就直接选择了 Tkinter. 本来觉得 GUI 发展 ...

  5. 数据备份[APIO/CTSC 2007]题解

    题目描述 你在一家IT公司为大型写字楼或办公楼的计算机数据做备份. 然而数据备份的工作是枯燥乏味的,因此你想设计一个系统让不同的办公楼彼此之间互相备份,而你则坐在家中尽享计算机游戏的乐趣. 已知办公楼 ...

  6. 使用 python 把一个文件生成 C 语言中的数组并保存到头文件中

    (一)要做什么 之前有这么一个需求,是要把一个二进制文件里面的数据,转换成 C 代码里面的数组,可以看之前的一篇文章: NUC980 运行 RT-Thread 驱动 SPI 接口 OLED 播放 ba ...

  7. 升级Ubuntu 16.04 到 Ubuntu 18.04 的方法

    特别注意,在进行升级前,请做好重要数据备份工作,防止升级失败或者其他奇怪原因,导致数据丢失或损坏 sudo vim /etc/apt/sources.list 将 http://archive.ubu ...

  8. WebService:CXF的JaxWsDynamicClientFactory实现调用WebService接口

    首先需要引入依赖jar包 #版本只供参考,具体看项目 <dependency> <grouId>org.apache.cxf</grouId> <artifa ...

  9. Linux创建ftp并设置权限以及忘记ftp帐号(密码)修改 (转)

      忘记ftp密码修改方法:1.登录服务器 cd  /etc/vsftpdcat ftpusers找到对应的ftp用户名 (如果用户名也忘记了 那么 cd /etc 然后cat passwd 查看用户 ...

  10. MySQL索引类型总结和使用技巧以及注意事项 (转)

      在数据库表中,对字段建立索引可以大大提高查询速度.假如我们创建了一个 mytable表:  代码如下: CREATE TABLE mytable(   ID INT NOT NULL,    us ...