OpenGL中的QOpenGLBuffer对象有点像GPU上动态内存的唯一ID。这样理解有点困难,但是对于那些不熟悉的人,可以将它们近似为GPU动态内存。我们可以给GPU提供有关如何使用内存的提示,并且根据我们的选择,它会更改信息的访问位置和访问效率。
Qt5提供了一个帮助器类,该类封装了提供OpenGL缓冲区的所有功能,称为QOpenGLBuffer。
这些缓冲区不会在CPU端动态分配(通过new / delete),它们在内部由OpenGL返回给我们的整数id表示。因此,QOpenGLBuffers可以自由传递,而不会出现效率问题。
但是,当调用destroy()时,该缓冲区会变得无效,并且之后不应在该点之后使用。
一遍又一遍地为对象绑定和取消绑定OpenGL缓冲区会变得很麻烦。因此,引入了QOpenGLVertexArrayObjects(VAO)。顶点数组对象只是存储在GPU上的对象,该对象跟踪所有缓冲区并绑定与绘图调用关联的信息。
这只是减少代码重复的另一种方法。它几乎是一种便利,它可能带来也可能不会带来速度上的好处。但是,我们希望以创建美观且易于理解的OpenGL代码为目标。我们将在初始化时绑定信息,然后允许“顶点数组对象”(VAO)为我们记住我们的绑定信息。
典型的使用模式是:
然后,我们不再渲染时为每个对象再次绑定所有属性信息,我们可以
这确实是本教程的核心。 通常,对于首次使用OpenGL的用户而言,着色器编译有些棘手。 但是,Qt5提供了功能的QOpenGLShaderProgram类抽象,使我们的编程更加轻松。 此类可以采用源字符串,QOpenGLShader类型或文件路径。
着色器程序(shader program,如果您不熟悉它们的话)是在GPU上运行以处理数据的一些代码。 通常,我们从CPU端获取数据,该数据将位置信息与一些属性信息配对,这就是着色器程序将处理的数据。 保留位置和属性信息的结构通常称为“顶点”(Vertex)。
简单的着色器管道仅涉及单遍和两种着色器类型,可以概括如下:
着色器有很多种,但是现在我们仅使用如上所述的“顶点着色器”和“片段着色器”。
由于顶点信息(组成一个几何图形的信息)是用户定义的,因此Qt不会提供现成的QVertex类。为了简化问题,我们将创建一个类作为顶点信息
我们将创建一个名为vertex.h的新头文件,并将以下代码放入。
#ifndef VERTEX_H
#define VERTEX_H
#include
class Vertex
{
public:
// Constructors
Q_DECL_CONSTEXPR Vertex();
Q_DECL_CONSTEXPR explicit Vertex(const QVector3D &position);
Q_DECL_CONSTEXPR Vertex(const QVector3D &position, const QVector3D &color);
// Accessors / Mutators
Q_DECL_CONSTEXPR const QVector3D& position() const;
Q_DECL_CONSTEXPR const QVector3D& color() const;
void setPosition(const QVector3D& position);
void setColor(const QVector3D& color);
// OpenGL Helpers
static const int PositionTupleSize = 3;
static const int ColorTupleSize = 3;
static Q_DECL_CONSTEXPR int positionOffset();
static Q_DECL_CONSTEXPR int colorOffset();
static Q_DECL_CONSTEXPR int stride();
private:
QVector3D m_position;
QVector3D m_color;
};
/*******************************************************************************
* Inline Implementation
******************************************************************************/
// Note: Q_MOVABLE_TYPE means it can be memcpy'd.
Q_DECLARE_TYPEINFO(Vertex, Q_MOVABLE_TYPE);
// Constructors
Q_DECL_CONSTEXPR inline Vertex::Vertex() {}
Q_DECL_CONSTEXPR inline Vertex::Vertex(const QVector3D &position) : m_position(position) {}
Q_DECL_CONSTEXPR inline Vertex::Vertex(const QVector3D &position, const QVector3D &color) : m_position(position), m_color(color) {}
// Accessors / Mutators
Q_DECL_CONSTEXPR inline const QVector3D& Vertex::position() const { return m_position; }
Q_DECL_CONSTEXPR inline const QVector3D& Vertex::color() const { return m_color; }
void inline Vertex::setPosition(const QVector3D& position) { m_position = position; }
void inline Vertex::setColor(const QVector3D& color) { m_color = color; }
// OpenGL Helpers
Q_DECL_CONSTEXPR inline int Vertex::positionOffset() { return offsetof(Vertex, m_position); }
Q_DECL_CONSTEXPR inline int Vertex::colorOffset() { return offsetof(Vertex, m_color); }
Q_DECL_CONSTEXPR inline int Vertex::stride() { return sizeof(Vertex); }
#endif // VERTEX_H
在进行下一步之前,让我们讨论一下这个Vertex类。实际上,这里只有一些Qt特定的东西。
如您所见,我们还将QVector3D类用于位置和颜色。通常,我这样做是为了减少创建自己的向量类型的需要。稍后,这可能会很有用,尽管您可以告诉您仅定义哑的Vertex类型就可以。
如果您不熟悉OpenGL或Vertex信息,可能会对* TupleSize,* Offset()和stride()符号感到困惑。
在此应用程序中,我们将在屏幕上绘制一些内容。 因此,我们将不得不创建和分配一些信息。 但是,我们仍然做得很少,因此更改将很小。
您需要对window.h进行的更改在下面突出显示:
#ifndef WINDOW_H
#define WINDOW_H
#include
#include
#include
#include
class QOpenGLShaderProgram;
class Window : public QOpenGLWindow,
protected QOpenGLFunctions
{
Q_OBJECT
// OpenGL Events
public:
~Window();
void initializeGL();
void resizeGL(int width, int height);
void paintGL();
void teardownGL();
private:
// OpenGL State Information
QOpenGLBuffer m_vertex;
QOpenGLVertexArrayObject m_object;
QOpenGLShaderProgram *m_program;
// Private Helpers
void printVersionInformation();
};
#endif // WINDOW_H
实现部分需要将其分解为几个部分,让我来讨论一下。
因此,第2节中此点之后的所有内容都将与window.cpp有关。
第一个更改是在window.cpp的顶部:
#include "window.h"
#include
#include
#include
#include "vertex.h"
// Create a colored triangle
static const Vertex sg_vertexes[] = {
Vertex( QVector3D( 0.00f, 0.75f, 1.0f), QVector3D(1.0f, 0.0f, 0.0f) ),
Vertex( QVector3D( 0.75f, -0.75f, 1.0f), QVector3D(0.0f, 1.0f, 0.0f) ),
Vertex( QVector3D(-0.75f, -0.75f, 1.0f), QVector3D(0.0f, 0.0f, 1.0f) )
};
// ...
乍一看可能有点怪异,但请记住我们所讨论的QOpenGLShaderProgram和Vertex信息。 特定的顶点信息数组形成一个适合屏幕边界的三角形,每个位置具有一个属性(在这种情况下,该属性将被认为是颜色,尽管可以代表任何东西)。
因此,我们有一些点可以逆时针移动以形成三角形:
(0.0f,0.75f,1.0f)->(-0.75f,-0.75f,1.0f)->(0.75f,-0.75f,1.0f)
每个点对应一个不同的属性,我们称其为颜色:
红色->绿色->蓝色
接下来,我们将编辑initializeGL()函数:
// ...
void Window::initializeGL()
{
// Initialize OpenGL Backend
initializeOpenGLFunctions();
printVersionInformation();
// Set global information
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// Application-specific initialization
{
// Create Shader (Do not release until VAO is created)
m_program = new QOpenGLShaderProgram();
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/simple.vert");
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/simple.frag");
m_program->link();
m_program->bind();
// Create Buffer (Do not release until VAO is created)
m_vertex.create();
m_vertex.bind();
m_vertex.setUsagePattern(QOpenGLBuffer::StaticDraw);
m_vertex.allocate(sg_vertexes, sizeof(sg_vertexes));
// Create Vertex Array Object
m_object.create();
m_object.bind();
m_program->enableAttributeArray(0);
m_program->enableAttributeArray(1);
m_program->setAttributeBuffer(0, GL_FLOAT, Vertex::positionOffset(), Vertex::PositionTupleSize, Vertex::stride());
m_program->setAttributeBuffer(1, GL_FLOAT, Vertex::colorOffset(), Vertex::ColorTupleSize, Vertex::stride());
// Release (unbind) all
m_object.release();
m_vertex.release();
m_program->release();
}
}
// ...
以上就是我们的大部分变化。通常,我们将准备大量数据,并准备使用glDraw *()进行绘制。让我们来谈谈这些部分,
创建着色器(Shader)
这是唯一通过new动态分配的东西,因为这个对象可能会被当作参数传递.删除实例即可从GPU内存中释放着色器。
正如我们所讨论的,顶点着色器将采用我们的顶点(Vertex )类型,并生成插值的片段数据。 Fragmet着色器将从Vertex Shader获取片段输出,并将最终结果数据输出到某个缓冲区。在我们的例子中,结果数据将被简单地绘制到屏幕上。
将所有已加载的着色器链接在一起。理想情况下,我们应该检查此调用的返回值,因为它可能会失败。
绑定着色器,使其成为当前活动的着色器。
创建缓冲区(Buffer)
创建顶点数组对象(Vertex Array Object)
释放(取消绑定)全部
接下来,我们可以更新paintGL()以实际绘制三角形:
// ...
void Window::paintGL()
{
// Clear
glClear(GL_COLOR_BUFFER_BIT);
// Render using our shader
m_program->bind();
{
m_object.bind();
glDrawArrays(GL_TRIANGLES, 0, sizeof(sg_vertexes) / sizeof(sg_vertexes[0]));
m_object.release();
}
m_program->release();
}
// ...
如您所见,这很简单,因为我们使用的是VAO。
最后,我们需要通过teardownGL()函数发布信息:
// ...
void Window::teardownGL()
{
// Actually destroy our OpenGL information
m_object.destroy();
m_vertex.destroy();
delete m_program;
}
// ...
接下来,我们需要一些文件才能实际加载到着色器程序中。 稍后,我们将动态加载文件,但现在,我们将它们作为Qt资源烘焙到可执行文件中。
因此,创建一个Qt资源,我们将开始使用(我将资源文件命名为resources.qrc)。
之后,我们将从“ GLSL”“文件和类”模板创建两个着色器。 一个将是一个名为simple.vert的顶点着色器,另一个将是一个名为simple.frag的片段着色器。 我们将它们保存在项目目录中创建的子目录中。
注意:这两个文件都应保存在:
/ shaders / simple.{vert | frag}
:= .pro文件所在的位置。
因此,如果我的项目名为“ 1_BasicRendering”,并且保存在“ C:\ QtProjects \ 1_BasicRendering \”中,那么我将创建一个名为“ shaders”的文件夹,并保存以下两个文件:
“ C:\ QtProjects \ 1_BasicRendering \ shaders \ simple.vert”
“ C:\ QtProjects \ 1_BasicRendering \ shaders \ simple.frag”
然后,我们将通过首先创建一个空的前缀,然后添加这两个文件,将这两个文件添加到resources.qrc中。
如果正确完成,您的项目布局应如下所示:
最后我们所需要做的是编辑我们的着色器代码
shaders/simple.vert:
#version 330
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
out vec4 vColor;
void main()
{
gl_Position = vec4(position, 1.0);
vColor = vec4(color, 1.0);
}
在文件顶部,通常会加一个注释,指出您希望该文件在哪个版本的着色器语言上运行。 这说明着色器应在OpenGL 3.3上运行。 着色器接受两个输入(位置和颜色),并具有一个输出:vColor。 在着色器的源代码中,我们要做的就是通过强制转换将两种vec3类型升级为vec4。
shaders/simple.frag:
#version 330
in vec4 vColor;
out vec4 fColor;
void main()
{
fColor = vColor;
}
片段着色器更简单,仅仅是将入参vColor传给fColor,
在本教程中,我们学习了……
代码可以在Gitee上下载