之前学习了window+vs的方式使用assimp库进行模型加载,但是咧,并不支持跨平台使用,所以又搞了一次linux+qt来完成
本章只包含assimp库内容以及assimp与qt交互的问题,其他内容不过多介绍,不熟悉者还是边看learnopengl官网学习边参考
不过大家有问题还是可以问我,我知道的会给大家解答,也可以提出我代码的问题,我会进行修改
1.assimp库导入qt
在此之前需要进行assimp库编译,linux编译都差不多,用3个命令
cmake CMakeLists.txt
make
make install
这里不过多介绍
然后导入我使用一种简单(直接链接)的方法(正式项目不要用哦)
在.pro文件中加入
LIBS +=
/root/assimp/assimp-3.1.1/lib/libassimp.so
这是我assimp库的绝对路径,.so就是linux的库文件,类似window下的.lib文件
2.使用qt封装好的类,而不再使用glew和glut
QOpenGLWidget
用于进行渲染的窗体,我们需要继承并重写3个函数
virtual void initializeGL();
virtual void resizeGL(int w, int h);
virtual void paintGL();
这3个函数不需要手动调用,qt会自动调用
QOpenGLFunctions_3_3_Core
版本化的函数包装器,类似glew,用于使用opengl函数,针对给定的opengl版本和配置文件,通常在渲染类(有shader的类中)和main函数中继承或使用
QOpenGLExtraFunctions
函数包装器,对OpenGL ES 3进行跨平台访问,类似QOpenGLFunctions_
3_3_Core,不过是通用的,一般在封装的类中继承,因为封装的类并不进行渲染,而是渲染程序调用时由调用的渲染程序进行渲染
QOpenGLShaderProgram
封装好的着色器程序,我们不用再自己编写shader了
QOpenGLTexture
封装好的纹理,我们不用再自己编写纹理载入代码了
3.渲染窗口
我就不过多介绍了,与window上不同的地方,我会用注释解释
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QTime>
#include <QTimer>
#include <QVector3D>
#include <QKeyEvent>
#include <QMouseEvent>
#include "camera.h"
#include <QDebug>
#include "model.h"
#include <QCoreApplication>
#include <QFileDialog>
#define PI 3.1415926
class MyOpenGLWidget : public QOpenGLWidget,protected QOpenGLFunctions_3_3_Core
{
//继承QOpenGLFunctions_3_3_Core用于使用openGL函数库
//继承QOpenGLWidget用于实现我们自己的渲染窗口
Q_OBJECT
public:
explicit MyOpenGLWidget(QWidget *parent = nullptr);
~MyOpenGLWidget();
protected:
virtual void initializeGL();
virtual void resizeGL(int w, int h);
virtual void paintGL();
//用于实现camera移动,观看不同的视角
virtual void keyPressEvent(QKeyEvent *event);
virtual void mouseMoveEvent(QMouseEvent *event);
virtual void mousePressEvent(QMouseEvent *event);
virtual void mouseReleaseEvent(QMouseEvent *event);
//camera,learnopengl教程中的入门就有,不多介绍
Camera *camera;
//着色器程序
QOpenGLShaderProgram *colorShader;
Model ourModel;
QTimer timer;
QTime m_time;
float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间
QPoint lastPos;
bool leftButtonIsPressed = false;
float sensitivity = 0.2f;
private:
signals:
public slots:
void on_timer_timeout();
};
#endif // MYOPENGLWIDGET_H
这里先不介绍cpp文件,先实现mesh和model文件,最后再介绍
4.mesh类
头文件
#ifndef MESH_H
#define MESH_H
#include <QString>
#include <QVector3D>
#include <QOpenGLShaderProgram>
#include <QOpenGLExtraFunctions>
#include <QOpenGLTexture>
#include <QDebug>
using std::vector;
struct Vertex {
QVector3D Position;
QVector3D Normal;
QVector3D TexCoords;
};
struct Texture {
unsigned int num;
QString type;
QString filename;
};
class Mesh : protected QOpenGLExtraFunctions
{
//继承QOpenGLExtraFunctions用于使用opengl函数
public:
vector<Vertex> vertices;
vector<unsigned int> indices;
vector<Texture> textures;
Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures);
~Mesh();
void Draw(QOpenGLShaderProgram *shader,vector<QOpenGLTexture*> textureInstances);
//draw时传入着色器程序和纹理
private:
/* 渲染数据 */
unsigned int VAO, VBO, EBO;
/* 函数 */
void setupMesh();
//用于初始化网格数据
};
#endif // MESH_H
cpp文件
#include "mesh.h"
Mesh::Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures)
{
this->vertices = vertices;
this->indices = indices;
this->textures = textures;
setupMesh();
}
Mesh::~Mesh()
{
}
void Mesh::Draw(QOpenGLShaderProgram* shader,vector<QOpenGLTexture*> textureInstances)
{
shader->bind();
for(unsigned int i = 0; i < textures.size(); i++)
{
//遍历网格纹理,获取纹理类型,并读出纹理对象
QString name = textures[i].type;
if(name == "texture_diffuse")
{
//bind(),QOpenGLTexture函数,直接绑定到指定的纹理单元,而不用再先glActiveTexture了,很方便
textureInstances[textures[i].num]->bind(0);
//shaderProgram也直接封装了,setUnifromxxx的函数也直接封装成setUnifromValue()这一个函数,很方便
shader->setUniformValue(("material." + name).toLatin1().data(), 0);
//toLatin1()从QStirng 转为QByteArray
//data()转为char *
//设置着色器中此变量纹理也第0槽纹理,也就是刚bind 的
}
else if(name == "texture_specular")
{
//同上
textureInstances[textures[i].num]->bind(1);
shader->setUniformValue(("material." + name).toLatin1().data(), 1);
}
}
// 绘制网格
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
shader->release();
}
void Mesh::setupMesh()
{
initializeOpenGLFunctions();
//类似glew的init函数,初始化opengl函数库,必须在第一步用,否则下面的函数都不能使用
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int),&indices[0], GL_STATIC_DRAW);
// 顶点位置
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
// 顶点法线
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
// 顶点纹理坐标
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords));
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
}
5.model类
头文件
#ifndef MODEL_H
#define MODEL_H
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QImage>
#include "mesh.h"
#include <QString>
#include <string>
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <QDebug>
using std::string;
class Model
{
public:
Model();
~Model();
void loadModel(string path);
void Draw(QOpenGLShaderProgram *shader);
private:
/* 模型数据 */
vector<Mesh> meshes;
QString directory;
/* 函数 */
void processNode(aiNode *node, const aiScene *scene);
Mesh processMesh(aiMesh *mesh, const aiScene *scene);
vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type,QString typeName);
//用于存放纹理,待传入mesh中使用,由于是指针,放在类私有成员等析构函数delete掉
vector<QOpenGLTexture *> m_textureInstance;
//纹理数(不是个数,而是在vector中的索引)
unsigned int m_num = 0;
};
#endif // MODEL_H
cpp文件
#include "model.h"
Model::Model()
{
}
Model::~Model()
{
//把指针内存释放掉
for(unsigned int i = 0; i < m_textureInstance.size(); ++i)
{
delete m_textureInstance[i];
}
}
void Model::Draw(QOpenGLShaderProgram *shader)
{
//for循环分别绘制每个网格
for(unsigned int i = 0; i < meshes.size(); i++)
meshes[i].Draw(shader,m_textureInstance);
}
//加载模型
//通过assimp::Importer类的ReadFile函数读取模型文件,它返回一个aiScene*是assimp库的类,只要遵循它库的规则,用正确的类接受,这里没啥问题
void Model::loadModel(string path)
{
Assimp::Importer importer;
const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
//判断是否读取文件成功
if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
{
qDebug() << "ERROR::ASSIMP::" << importer.GetErrorString() << endl;
return;
}
//获取目录,从最后一个“/"往前的所有字符串,一会用来读取其他文件
directory = QString::fromStdString(path.substr(0, path.find_last_of('/')));
//遍历节点(简单介绍:scene中存放着root节点,网格数据和metaril材
//质,root节点指向子节点,每个子节点分别还有自己的子节点,类似树的结
//构,每个节点下有它所对应的网格数据的索引,通过索引去scene中获取自己
//对应的网格数据,挨着递归就可以了,mesh类中存放了顶点数据,法线数
//据,纹理坐标数据,还有面的数据,以及纹理索引,这里的面是立方体的面的
//意思,每个面中有它对应顶点的索引,获取索引用于绘制面,最后是纹理索
//引,通过纹理索引去scene中获取纹理,大体就是这样)
processNode(scene->mRootNode, scene);
}
void Model::processNode(aiNode *node, const aiScene *scene)
{
// 处理节点所有的网格(如果有的话)
for(unsigned int i = 0; i < node->mNumMeshes; i++)
{
aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
meshes.push_back(processMesh(mesh, scene));
}
// 接下来对它的子节点重复这一过程
for(unsigned int i = 0; i < node->mNumChildren; i++)
{
processNode(node->mChildren[i], scene);
}
}
Mesh Model::processMesh(aiMesh *mesh, const aiScene *scene)
{
vector<Vertex> vertices;
vector<unsigned int> indices;
vector<Texture> textures;
for(unsigned int i = 0; i < mesh->mNumVertices; i++)
{
Vertex vertex;
// 处理顶点位置、法线和纹理坐标
QVector3D vector;
vector.setX(mesh->mVertices[i].x);
vector.setY(mesh->mVertices[i].y);
vector.setZ(mesh->mVertices[i].z);
vertex.Position = vector;
vector.setX(mesh->mNormals[i].x);
vector.setY(mesh->mNormals[i].y);
vector.setZ(mesh->mNormals[i].z);
vertex.Normal = vector;
if(mesh->mTextureCoords[0]) // 网格是否有纹理坐标?
{
QVector2D vec;
vec.setX(mesh->mTextureCoords[0][i].x);
vec.setY(mesh->mTextureCoords[0][i].y);
vertex.TexCoords = vec;
}
else
vertex.TexCoords = QVector2D(0.0f, 0.0f);
vertices.push_back(vertex);
}
// 处理索引
for(unsigned int i = 0; i < mesh->mNumFaces; i++)
{
aiFace face = mesh->mFaces[i];
for(unsigned int j = 0; j < face.mNumIndices; j++)
indices.push_back(face.mIndices[j]);
}
// 处理材质
if(mesh->mMaterialIndex >= 0)
{
aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];
vector<Texture> diffuseMaps = loadMaterialTextures(material,
aiTextureType_DIFFUSE, "texture_diffuse");
textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
vector<Texture> specularMaps = loadMaterialTextures(material,
aiTextureType_SPECULAR, "texture_specular");
textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
}
return Mesh(vertices, indices, textures);
}
vector<Texture> Model::loadMaterialTextures(aiMaterial *mat, aiTextureType type, QString typeName)
{
//这个函数不多将,learnopengl网站写的很详细
vector<Texture> textures_temp;
vector<Texture> textures_loaded;
for(unsigned int i = 0; i < mat->GetTextureCount(type); i++)
{
aiString str;
mat->GetTexture(type, i, &str);
QString filename = QString::fromStdString(str.C_Str()); //类型转换aiString转为QString
bool skip = false;
for(unsigned int j = 0; j < textures_loaded.size(); j++)
{
if(textures_loaded[j].filename == filename)
{
textures_temp.push_back(textures_loaded[j]);
skip = true;
break;
}
}
if(!skip)
{ // 如果纹理还没有被加载,则加载它
Texture texture;
QString path = directory+"/"+filename;
//new出纹理对象放在vector中供使用,以及纹理的一些配置,都是
//qt封装好的,就是st方向的环绕方式和缩小放大筛选器,类似
//glTexParametri()
QOpenGLTexture *textureInstance = new QOpenGLTexture(QImage(path).mirrored());
textureInstance->setWrapMode(QOpenGLTexture::DirectionS,QOpenGLTexture::Repeat);
textureInstance->setWrapMode(QOpenGLTexture::DirectionT,QOpenGLTexture::Repeat);
textureInstance->setMagnificationFilter(QOpenGLTexture::Linear);
textureInstance->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
m_textureInstance.push_back(textureInstance);
texture.num = m_num++;
texture.type = typeName;
texture.filename = filename;
textures_temp.push_back(texture);
textures_loaded.push_back(texture); // 添加到已加载的纹理中
}
}
return textures_temp;
}
6.渲染
#include "myopenglwidget.h"
MyOpenGLWidget::MyOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
setFocusPolicy(Qt::StrongFocus);
setMouseTracking(true);
connect(&timer, SIGNAL(timeout()), this, SLOT(on_timer_timeout()));
timer.start(100);
m_time.start();
}
MyOpenGLWidget::~MyOpenGLWidget()
{
delete camera;
delete colorShader;
}
void MyOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
camera = new Camera(QVector3D(0.0f,0.0f,3.0f),0.0f,-90.0f,QVector3D(0.0f,1.0f,0.0f));
glEnable(GL_DEPTH_TEST);
colorShader = new QOpenGLShaderProgram();
colorShader->addCacheableShaderFromSourceFile(QOpenGLShader::Vertex,":/shader/color.vert");
colorShader->addCacheableShaderFromSourceFile(QOpenGLShader::Fragment,":/shader/color.frag");
bool success = colorShader->link();
if(!success)
{
qDebug() << colorShader->log();
}
//linux+qt读文件是读相对路径,记得把模型文件(文件夹)放到编译后的目录中
//由于使用的是assimp的ReadFile,使用qt的.qrc文件我这里没成功
ourModel.loadModel("./model/nanosuit.obj");
}
void MyOpenGLWidget::resizeGL(int w, int h)
{
Q_UNUSED(w);
Q_UNUSED(h);
}
void MyOpenGLWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_TEST);
colorShader->bind();
colorShader->setUniformValue("viewPos", camera->getPostion());
QMatrix4x4 projection;
projection.perspective(45.0f,(float)width()/(float)height(),0.1f, 100.0f);
QMatrix4x4 view;
view = camera->getViewMatrix();
colorShader->setUniformValue("projection", projection);
colorShader->setUniformValue("view", view);
QMatrix4x4 model;
colorShader->setUniformValue("model", model);
ourModel.Draw(colorShader);
colorShader->release();
}
void MyOpenGLWidget::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_W:
camera->speedZ = 1.0f* deltaTime;
break;
case Qt::Key_S:
camera->speedZ = -1.0f * deltaTime;
break;
case Qt::Key_D:
camera->speedX = 1.0f * deltaTime;
break;
case Qt::Key_A:
camera->speedX = -1.0f * deltaTime;
break;
case Qt::Key_Up:
camera->speedY = 1.0f * deltaTime;
break;
case Qt::Key_Down:
camera->speedY = -1.0f * deltaTime;
break;
case Qt::Key_Escape:
this->parentWidget()->setFocus();
default:
break;
}
camera->updataCameraPos();
update();
}
void MyOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
if(leftButtonIsPressed)
{
QPoint currentPos = event->pos();
QPoint deltaPos = currentPos - lastPos;
lastPos = currentPos;
deltaPos *= sensitivity;
camera->processMouseMove(deltaPos.x(),-deltaPos.y());
update();
}
}
void MyOpenGLWidget::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
lastPos = event->pos();
leftButtonIsPressed = true;
}
}
void MyOpenGLWidget::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
leftButtonIsPressed = false;
}
}
void MyOpenGLWidget::on_timer_timeout()
{
float currentFrame = m_time.elapsed()/1000.0f;
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
}
vertex着色器文件和fragment着色器和learnopengl上的一模一样
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
TexCoords = aTexCoords;
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
#version 330 core
in vec2 TexCoords;
out vec4 FragColor;
struct Material {
sampler2D texture_diffuse;
sampler2D texture_specular;
};
uniform Material material;
void main()
{
vec4 diffuse = texture2D(material.texture_diffuse, TexCoords);
FragColor = diffuse;
}
最后在ui界面添加一个QOpenGLWidget,然后promote为自己定义的myOpenGLWidget
并在mianWindow中设置中心组件
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setCentralWidget(ui->openGLWidget);
showFullScreen ();
}
成果展示:
最后总结一下用到的assimp库的东西
Assimp::Importer
importer.ReadFile
importer.GetErrorString
aiProcess_Triangulate
aiProcess_FlipUVs
aiScene
scene->mRootNode
scene->mMeshes
scene->mMaterials
aiNode
node->mNumMeshes
node->mNumChildren
node->mChildren
aiMesh
mesh->mVertices
mesh->mNormals
mesh->mTextureCoords
mesh->mNumFaces
mesh->mFaces
mesh->mMaterialIndex
aiFace
face.mNumIndices
face.mIndices
aiMaterial
mat->GetTextureCount
mat->GetTexture
aiTextureType_DIFFUSE
aiTextureType_SPECULAR
aiString
str.C_Str()