Qt和OpenGL:使用Open Asset Import Library(ASSIMP)加载3D模型-第2部分
翻译自:https://www.ics.com/blog/qt-and-opengl-loading-3d-model-open-asset-import-library-assimp-part-2
By John Stone Wednesday, May 18, 2016
Twitter LinkedIn Facebook Reddit
欢迎回来。这是我们基于Eric Stone的文章 “Qt和OpenGL:使用Open Asset Import Library(ASSIMP)加载3D模型”的后续文章。上一次我们介绍了如何使用ASSIMP将模型加载到我们的数据结构中,以便可以使用OpenGL进行渲染。这次,我们将介绍如何编写程序来实际渲染这些模型。
显然,我们将使用Qt作为平台层(就像此博客的标题中写的那样),我们将在可将OpenGL与Qt集成的各种方法中选择QOpenGLWindow。 (有关的详细信息,请参见此网络研讨会:http://www.ics.com/webinars/state-art-opengl-and-qt)。
这是我们项目骨架的清单:
#include <QtGui/QGuiApplication>
#include <QtGui/QKeyEvent>
#include <QtGui/QOpenGLWindow>
#include <QtGui/QOpenGLBuffer>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLShaderProgram>
#include <QtGui/QOpenGLVertexArrayObject>
#include "modelloader.h"
static QString vertexShader =
"#version 330 core\n"
"\n"
"layout(location = 0) in vec3 vertexPosition;\n"
"layout(location = 1) in vec3 vertexNormal;\n"
"\n"
"out vec3 normal;\n"
"out vec3 position;\n"
"\n"
"uniform mat4 MV;\n"
"uniform mat3 N;\n"
"uniform mat4 MVP;\n"
" \n"
"void main()\n"
"{\n"
" normal = normalize( N * vertexNormal );\n"
" position = vec3( MV * vec4( vertexPosition, 1.0 ) );\n"
" gl_Position = MVP * vec4( vertexPosition, 1.0 );\n"
"}\n"
;
static QString fragmentShader =
"#version 330 core\n"
"\n"
"in vec3 normal;\n"
"in vec3 position;\n"
"\n"
"layout (location = 0) out vec4 fragColor;\n"
"\n"
"struct Light\n"
"{\n"
" vec4 position;\n"
" vec3 intensity;\n"
"};\n"
"uniform Light light;\n"
"\n"
"struct Material {\n"
" vec3 Ka;\n"
" vec3 Kd;\n"
" vec3 Ks;\n"
" float shininess;\n"
"};\n"
"uniform Material material;\n"
"\n"
"void main()\n"
"{\n"
" vec3 n = normalize( normal);\n"
" vec3 s = normalize( light.position.xyz - position);\n"
" vec3 v = normalize( -position.xyz);\n"
" vec3 h = normalize( v + s);\n"
" float sdn = dot( s, n);\n"
" vec3 ambient = material.Ka;\n"
" vec3 diffuse = material.Kd * max( sdn, 0.0);\n"
" vec3 specular = material.Ks * mix( 0.0, pow( dot(h, n), material.shininess), step( 0.0, sdn));\n"
" fragColor = vec4(light.intensity * (ambient + diffuse + specular), 1);\n"
"}\n"
;
struct Window : QOpenGLWindow, QOpenGLFunctions
{
Window() :
m_vbo(QOpenGLBuffer::VertexBuffer),
m_nbo(QOpenGLBuffer::VertexBuffer),
m_ibo(QOpenGLBuffer::IndexBuffer)
{
}
void createShaderProgram()
{
if ( !m_pgm.addShaderFromSourceCode( QOpenGLShader::Vertex, vertexShader)) {
qDebug() << "Error in vertex shader:" << m_pgm.log();
exit(1);
}
if ( !m_pgm.addShaderFromSourceCode( QOpenGLShader::Fragment, fragmentShader)) {
qDebug() << "Error in fragment shader:" << m_pgm.log();
exit(1);
}
if ( !m_pgm.link() ) {
qDebug() << "Error linking shader program:" << m_pgm.log();
exit(1);
}
}
void createGeometry()
{
}
void initializeGL()
{
QOpenGLFunctions::initializeOpenGLFunctions();
createShaderProgram(); m_pgm.bind();
m_pgm.setUniformValue("light.position", QVector4D( -1.0f, 1.0f, 1.0f, 1.0f ));
m_pgm.setUniformValue("light.intensity", QVector3D( 1.0f, 1.0f, 1.0f ));
createGeometry();
m_view.setToIdentity();
m_view.lookAt(QVector3D(0.0f, 0.0f, 1.2f), // Camera Position
QVector3D(0.0f, 0.0f, 0.0f), // Point camera looks towards
QVector3D(0.0f, 1.0f, 0.0f)); // Up vector
glEnable(GL_DEPTH_TEST);
glClearColor(.9f, .9f, .93f ,1.0f);
}
void resizeGL(int w, int h)
{
glViewport(0, 0, w, h);
m_projection.setToIdentity();
m_projection.perspective(60.0f, (float)w/h, .3f, 1000);
update();
}
void draw()
{
}
void paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_pgm.bind();
m_vao.bind();
draw();
m_vao.release();
update();
}
void keyPressEvent(QKeyEvent * ev)
{
if (ev->key() == Qt::Key_Escape) exit(0);
}
ModelLoader m_loader;
QMatrix4x4 m_projection, m_view;
QOpenGLShaderProgram m_pgm;
QOpenGLVertexArrayObject m_vao;
QOpenGLBuffer m_vbo, m_nbo;
QOpenGLBuffer m_ibo;
GLsizei m_cnt;
};
int main(int argc, char *argv[])
{
QGuiApplication a(argc,argv);
QSurfaceFormat f;
f.setMajorVersion( 3 );
f.setMinorVersion( 3 );
f.setProfile( QSurfaceFormat::CoreProfile );
Window w;
w.setFormat(f);
w.setWidth(800); w.setHeight(600);
w.show();
return a.exec();
}
使用此代码,我们几乎可以绘制您可以给我们的任何几何图形,因此我们的任务将变成如何填充空的createGeometry() 方法和空的 draw() 方法。让我们首先解决创建几何图形的问题。如果您还记得我们以以下代码结束了我们的第一篇博客文章:
ModelLoader model;
if (!model.Load("head.3ds"))
{
m_error = true;
return;
}
QVector<float> *vertices;
QVector<float> *normals;
QVector<unsigned int=""> *indices;
model.getBufferData(&vertices, &normals, &indices);
m_rootNode = model.getNodeData();
我们说:“到此,您已经拥有了使用OpenGL显示模型所需的所有数据。”让我们看看如何将其转换为代码。
void createGeometry()
{
if(!m_loader.Load("velociraptor_mesh_materials.dae", ModelLoader::RelativePath)) {
qDebug() << "ModelLoader failed to load model" << m_pgm.log();
exit(1);
}
// Get the loaded model data from the model-loader: (v)ertices, (n)ormals, and (i)ndices
QVector<glfloat> *v, *n; QVector<gluint> *i; m_loader.getBufferData(&v, &n, &i);
// Initialize and bind the VAO that's going to capture all this vertex state
m_vao.create();
m_vao.bind();
// Put all the vertex data in a FBO
m_vbo.create();
m_vbo.setUsagePattern( QOpenGLBuffer::StaticDraw );
m_vbo.bind();
m_vbo.allocate(&(*v)[0], v->size() * sizeof((*v)[0]));
// Configure the attribute stream
m_pgm.enableAttributeArray(0);
m_pgm.setAttributeBuffer(0, GL_FLOAT, 0, 3);
// Put all the normal data in a FBO
m_nbo.create();
m_nbo.setUsagePattern( QOpenGLBuffer::StaticDraw );
m_nbo.bind();
m_nbo.allocate(&(*n)[0], n->size() * sizeof((*n)[0]));
// Configure the attribute stream
m_pgm.enableAttributeArray(1);
m_pgm.setAttributeBuffer(1, GL_FLOAT, 0, 3);
// Put all the index data in a IBO
m_ibo.create();
m_ibo.setUsagePattern( QOpenGLBuffer::StaticDraw );
m_ibo.bind();
m_ibo.allocate(&(*i)[0], i->size() * sizeof((*i)[0]));
// Okay, we've finished setting up the vao
m_vao.release();
}
我们正在做的是使用上一篇博客文章中的ModelLoader
对象,要求它加载一个“ velociraptor”模型,然后该模型在QVector
对象中为我们提供模型数据。然后,我们获取模型数据,并在显卡上创建单独的Vertex-Buffer和Index-Buffer对象,然后将模型数据上传到显卡。在那里,我们为顶点着色器输入配置属性流,因为它们指向这些缓冲区对象,之后我们准备进行渲染。
现在,让我们进行实际的渲染。回顾第一篇文章,ModelLoader
对象返回一棵网格数据树,每个网格对象包含一堆有关如何渲染它的元数据(包括材质颜色数据)。我们要做的是创建一个算法,遍历此“网格树”,依次渲染网格。让我们看看我们如何在代码中做到这一点。
void drawNode(const QMatrix4x4& model, const Node *node, QMatrix4x4 parent)
{
// Prepare matrices
QMatrix4x4 local = parent * node->transformation;
QMatrix4x4 mv = m_view * model * local;
m_pgm.setUniformValue("MV", mv);
m_pgm.setUniformValue("N", mv.normalMatrix());
m_pgm.setUniformValue("MVP", m_projection * mv);
// Draw each mesh in this node
for(int i = 0; i<node->meshes.size(); ++i)
{
const Mesh& m = *node->meshes[i];
if (m.material->Name == QString("DefaultMaterial")) {
m_pgm.setUniformValue("material.Ka", QVector3D( 0.05f, 0.2f, 0.05f ));
m_pgm.setUniformValue("material.Kd", QVector3D( 0.3f, 0.5f, 0.3f ));
m_pgm.setUniformValue("material.Ks", QVector3D( 0.6f, 0.6f, 0.6f ));
m_pgm.setUniformValue("material.shininess", 50.f);
} else {
m_pgm.setUniformValue("material.Ka", m.material->Ambient);
m_pgm.setUniformValue("material.Kd", m.material->Diffuse);
m_pgm.setUniformValue("material.Ks", m.material->Specular);
m_pgm.setUniformValue("material.shininess", m.material->Shininess);
}
glDrawElements(GL_TRIANGLES, m.indexCount, GL_UNSIGNED_INT, (const GLvoid*)(m.indexOffset * sizeof(GLuint)));
}
// Recursively draw this nodes children nodes
for(int i = 0; i < node->nodes.size(); ++i)
drawNode(model, &node->nodes[i], local);
}
void draw()
{
QMatrix4x4 model;
model.translate(-0.2f, 0.0f, .5f);
model.rotate(55.0f, 0.0f, 1.0f, 0.0f);
drawNode(model, m_loader.getNodeData().data(), QMatrix4x4());
}
如您所见,我们已经建立了一个优雅的递归算法来遍历网格树。请注意,在每个递归步骤中,当前的本地转换矩阵是如何成为下一级别的父级转换矩阵的。另外,请注意我们是如何使用每个网格物体的照明元数据更新照明参数,以便正确渲染该网格物体的。
就是这样,我们正在使用Qt,OpenGL和ASSIMP渲染迅猛龙!
感谢特约作者:Eric Stone,ICS的软件工程师。
关于作者
John Stone
John Stone是一位经验丰富的软件开发人员,具有广泛的计算机科学教育和创业背景,并在图形可视化和虚拟现实(VR)方面具有丰富的实践经验。作为德克萨斯大学奥斯汀分校VR实验室的前技术总监和研究员,他花了很多年的时间磨练自己的技能,用OpenGL为科学实验和其他项目开发基于VR的数据采集系统。
原文:https://www.ics.com/blog/qt-and-opengl-loading-3d-model-open-asset-import-library-assimp-part-2
欢迎关注我的公众号 江达小记