errorman

不积跬步,无以至千里

导航

Qt+OpengGL使用教程(2)绘制三角形

Posted on 2023-03-20 14:56  Clemens  阅读(265)  评论(0编辑  收藏  举报

Qt+OpengGL使用教程(1)中我们建立了程序的基本结构,接下来我们需要使用qt的API绘制一个三角形,同时参考和对照:LearnOpenGL (2) 绘制三角形

一、准备工作

环境:Ubuntu 16.04 LTS + Qt 5.14.2.

 

二、绘制三角形

顶点数组坐标

OpenGL中我们指定的所有坐标都是3D坐标(x、y和z)。OpenGL不是简单地把所有的3D坐标变换为屏幕上的2D像素;OpenGL仅当3D坐标在3个轴(x、y和z)上-1.0到1.0的范围内时才处理它。所有在这个范围内的坐标叫做标准化设备坐标(Normalized Device Coordinates),此范围内的坐标最终显示在屏幕上(在这个范围以外的坐标则不会显示)。

float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

试一试上面代码中的坐标的绝对值大于1会出现什么情况?

 

顶点着色器程序

// 顶点着色器程序
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";

 

片段着色器程序

const char *fragmentShaderSource = "#version 330 core\n"
    "out vec4 FragColor;\n"
    "void main()\n"
    "{\n"
    "   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "}\n\0";

 

创建顶点/片段着色器对象,并编译顶点/片段着色器程序

在我们创建的GLWidget中添加成员变量:

QOpenGLShader *m_vshader;           //顶点着色器
QOpenGLShader *m_fshader;           //片段着色器

 在initializeGL()中创建:

// 创建顶点着色器对象,并编译
m_vshader = new QOpenGLShader(QOpenGLShader::Vertex);
m_vshader->compileSourceCode(vertexShaderSource);
// 创建片段着色器对象,并编译
m_fshader = new QOpenGLShader(QOpenGLShader::Fragment);
m_fshader->compileSourceCode(fragmentShaderSource);

 

创建着色器程序对象

着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。

在我们创建的GLWidget中添加成员变量:

QOpenGLShaderProgram *m_program;    //着色器程序对象

在initializeGL()中创建:

// 创建着色器程序对象,添加顶点着色器和片段着色器,并链接它们
m_program = new QOpenGLShaderProgram;
m_program->addShader(m_vshader);
m_program->addShader(m_fshader);
m_program->link();

着色器程序链接的作用是什么?

 为什么把着色器对象链接到着色器程序后,就可以删除着色器对象了?

 

创建顶点数组对象(VAO)

我们可以把VAO和VBO的创建放到一块。正确的顺序是:1.创建VAO和VBO,2.绑定VAO,3.绑定和设置VBO,4.配置顶点属性。

为什么要先绑定VAO然后再绑定VBO?

在我们创建的GLWidget中添加成员变量:

QOpenGLVertexArrayObject *m_vao;    //顶点数组对象

在initializeGL()中创建:

// 创建顶点数组对象,并绑定到当前上下文
m_vao = new QOpenGLVertexArrayObject;
m_vao->create();
QOpenGLVertexArrayObject::Binder vaoBinder(m_vao);

 

创建顶点缓冲对象(VBO)

OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。

// 创建顶点缓冲对象,
m_vbo = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_vbo->create();
m_vbo->bind();
// 分配显存大小,并搬运至显存
m_vbo->allocate(vertices, sizeof(vertices));

我们已经把输入顶点数据发送给了GPU,并指示了GPU如何在顶点和片段着色器中处理它。但OpenGL还不知道它该如何解释显存中的顶点数据,以及它该如何将顶点数据链接到顶点着色器的属性上。

 

链接顶点属性并启用

即告诉OpenGL应该如何解释这些顶点数据(应用到逐个的顶点属性上)。

// 链接顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
  • 第一个参数指定我们要配置的顶点属性。由我们在顶点着色器程序中,指定的layout (location = 0)中的location的值。我们希望把数据传递到这一个顶点属性中,即为0.
  • 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)

顶点属性默认是禁用的,我们在配置完如何解释数据后,应该启用顶点属性。

m_program->enableAttributeArray(0);

参数同样为顶点属性的location值。

 

解绑VBO和VAO

是否需要解绑VAO和VBO?

m_vao->release();
m_vbo->release();

 

渲染循环内代码

首先,设置颜色缓冲区,清空屏幕的颜色缓冲对象,将屏幕颜色设置成我们设置的颜色。

// 清空颜色缓冲区和深度缓冲区
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

然后,绘制我们的三角形。

paintGL()中:

// 绘制图形
// 绑定着色器程序至当前上下文,相当于调用glUseProgram()
m_program->bind();
// 绑定VAO,调用设置的一组状态
QOpenGLVertexArrayObject::Binder vaoBinder(m_vao);
// 绘制三角形
glDrawArrays(GL_TRIANGLES, 0, 3);
m_program->release();
// 交换缓存
auto *context = QOpenGLContext::currentContext();
context->swapBuffers(context->surface());
//使用swapBuffers后,必须调用makeCurrent后才能使用其他OpenGL函数。
makeCurrent();

 

结束后释放资源

资源不需要了就释放,养成良好的习惯

GLWidget::~GLWidget()
{
    if (m_program == nullptr) { return; }
    makeCurrent();
    delete m_program;
    delete m_vshader;
    delete m_fshader;
    m_vbo->destroy();
    m_vao->destroy();
    doneCurrent();
}

 

运行程序

没有错误的话,会在画面中弹出我们想要的三角形和背景颜色。

 

 

完整代码如下:

查看代码
 //glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLExtraFunctions>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
public:
    explicit GLWidget(QWidget *parent = nullptr);
    ~GLWidget();

protected:
    void resizeGL(int w, int h) override;
    void initializeGL() override;
    void paintGL() override;
private:
    QOpenGLVertexArrayObject *m_vao;    //顶点数组对象
    QOpenGLBuffer *m_vbo;               //顶点缓冲对象
    QOpenGLBuffer *m_ebo;               //元素缓冲对象
    QOpenGLShader *m_vshader;           //顶点着色器
    QOpenGLShader *m_fshader;           //片段着色器
    QOpenGLShaderProgram *m_program;    //着色器程序对象
};
#endif // GLWIDGET_H


//glwidget.cpp
#include "glwidget.h"
#include <QDebug>
float vertices[] = {
    -0.5f, -0.5f, 0.0f,     // left
        0.5f, -0.5f, 0.0f, // right
        0.0f,  0.5f, 0.0f  // top
    };
unsigned int indices[] = {
    // 注意索引从0开始!
    // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
    // 这样可以由下标代表顶点组合成矩形
    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

const char *vertexShaderSource =
    "#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
    "}\0";

const char *fragmentShaderSource =
    "#version 330 core\n"
    "out vec4 FragColor;\n"
    "void main()\n"
    "{\n"
    "    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "} \0";


GLWidget::GLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    makeCurrent();

}

GLWidget::~GLWidget()
{
    if (m_program == nullptr) { return; }
    makeCurrent();
    delete m_program;
    delete m_vshader;
    delete m_fshader;
    m_vbo->destroy();
    m_vao->destroy();
    doneCurrent();
}

//设置OpenGL视口、投影等。每当窗口部件被调整大小时调用(也在第一次显示时调用,因为所有新创建的窗口部件都自动获得调整大小事件)。
void GLWidget::resizeGL(int w, int h)
{

}

//设置OpenGL资源和状态。在第一次调用resizeGL()或paintGL()之前调用一次。
void GLWidget::initializeGL()
{
    initializeOpenGLFunctions();
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);// 设置清屏颜色
    glEnable(GL_DEPTH_TEST);    // 启用深度测试
    glEnable(GL_COLOR_BUFFER_BIT);  // 启用颜色缓冲区

    // 创建顶点着色器对象,并编译
    m_vshader = new QOpenGLShader(QOpenGLShader::Vertex);
    m_vshader->compileSourceCode(vertexShaderSource);
    // 创建片段着色器对象,并编译
    m_fshader = new QOpenGLShader(QOpenGLShader::Fragment);
    m_fshader->compileSourceCode(fragmentShaderSource);
    // 创建着色器程序对象,添加顶点着色器和片段着色器,并链接它们
    m_program = new QOpenGLShaderProgram;
    m_program->addShader(m_vshader);
    m_program->addShader(m_fshader);
    m_program->link();
    // 创建顶点数组对象,并绑定到当前上下文
    m_vao = new QOpenGLVertexArrayObject;
    m_vao->create();
    QOpenGLVertexArrayObject::Binder vaoBinder(m_vao);
    // 创建顶点缓冲对象,
    m_vbo = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    m_vbo->create();
    m_vbo->bind();
    // 分配显存大小,并搬运至显存
    m_vbo->allocate(vertices, sizeof(vertices));
    // 链接顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
    // 顶点属性默认是禁用的,启用顶点属性0(location=0)
    m_program->enableAttributeArray(0);

    m_vao->release();
    m_vbo->release();
}

//渲染OpenGL场景。每当窗口部件需要更新时调用。
void GLWidget::paintGL()
{
    // 清空颜色缓冲区和深度缓冲区
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 绘制图形
    // 绑定着色器程序至当前上下文,相当于调用glUseProgram()
    m_program->bind();
    // 绑定VAO,调用设置的一组状态
    QOpenGLVertexArrayObject::Binder vaoBinder(m_vao);
    // 绘制三角形
    glDrawArrays(GL_TRIANGLES, 0, 3);
    m_program->release();
    // 交换缓存
    auto *context = QOpenGLContext::currentContext();
    context->swapBuffers(context->surface());
    //使用swapBuffers后,必须调用makeCurrent后才能使用其他OpenGL函数。
    makeCurrent();
}