Qt中的OpenGL

一、OpenGL简介

1.1什么是OpenGL

  1. Open Graphics Library,它是一个由Khronos组织制定并维护的规范(Specification)
  2. OpenGL核心是一个C库,同时也支持多种语言的派生

1.2 核心模式(Core-profile)

也叫可编程管线,提供了更多的灵活性,更高的效率,更重要的是可以更深入的理解图形编程。

其中,顶点着色器和片段着色器需要自己去编写,几何着色器可选择性编写,有这三个可以编辑的部分,即为可编程管线(管线可以理解为一个处理流程)。

1.2.1 立即渲染模式(Immediate mode)

  1. 早期的OpenGL使用的模式(也就是固定渲染管线)
  2. OpenGL的大多数功能都被库隐藏起来,容易使用和理解,但是效率太低
  3. 开发者很少能控制OpenGL如何进行计算
  4. 因此从OpenGL3.2开始,推出核心模式

1.3 状态机(State Machine)

Ps:状态机是一种理论模型,用于描述一个系统在不同时间点的行为和状态转移规律。

  1. OpenGL自身是一个巨大的状态机
  2. 描述该如何操作的所有变量的大集合
  3. OpenGL的状态通常被称为上下文(Context)
  4. 状态设置函数(State-changing Function)
  5. 状态应用的函数(State-using Function)

1.4 对象(Object)

  • 一个对象是指一些选项的集合,代表OpenGL状态的一个子集

通常把OpenGL上下文比作一个大的结构体,包含很多子集:

  • 当前状态只有一份,如果每次显示不同的效果,都重新配置会很麻烦
  • 这时候我们就需要一些小助理(对象),帮忙记录某些状态信息,以便复用。

二、QT中的OpenGL

2.1 QOpenGLWidget(不需要GLFW)

QOpenGLWidget提供了三个便捷的虚函数,可以重载,用来重新实现典型的OpenGL任务:

  • paintGL:渲染OpenGL场景。Widget需要更新时调用。
  • resizeGL:设置OpenGL视口、投影等。Widget调整大小(或首次显示)时调用。
  • initializeGL:设置OpenGL资源和状态。第一次调用resizeGL()/paintGL()之前调用一次。
  • 如果需要从paintGL()以外的位置触发重新绘制(典型示例是使用计时器设置场景动画),则应调用widget的update()函数来安排更新。

调用paintGL()、resizeGL()、initializeGL()时,widget的OpenGL呈现上下文将变为当前。如果需要从其他位置(例如,在widget的构造函数或自己的绘制函数中)调用标准OpenGL API函数,则必须首先调用makeCurrent()。

注意:在paintGL()以外的地方调用绘制函数没有意义,绘制图像最终将被paintGL()覆盖。

2.2 QOpenGLFunctiongs_X_X_Core(不需要GLAD)

2.3 VAO与VBO

2.4 着色器

2.5 索引缓冲对象EBO/IBO

EBO(Element Buffer Object),也叫IBO(Index Buffer Object)

可以绘制两个三角形来组成一个矩形(OpenGL主要处理三角形)。这会生成下面的顶点的集合:

两个三角形,每个三角形绘制自己的部分,6个点

优化,点位不重复的一共四个,图形用了哪几个点就标注出来

绑定EBO

三、代码片段

cpp 复制代码
#pragma once

#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLWidget>

class OpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
public:
    explicit OpenGLWidget(QWidget* parent = nullptr);

protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();

signals:

};
cpp 复制代码
#include "openglwidget.h"

unsigned int VBO, VAO, EBO;
unsigned int shaderProgram;

float vertices[] =
{
    0.5f, 0.5f, 0.0f,//右上
    0.5f, -0.5f, 0.0f,//右下
    -0.5f, -0.5f, 0.0f,//左下
    -0.5f, 0.5f, 0.0f//左上
};
unsigned int indices[] = //note that we start from 0!
{
    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.0f, 0.0f, 1.0f);\n" "}\n\0";

OpenGLWidget::OpenGLWidget(QWidget* parent) : QOpenGLWidget(parent)
{

}

void OpenGLWidget::initializeGL()
{
    initializeOpenGLFunctions();

    //创建VBO和VAO对象,并赋予ID
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    //绑定VBO和VAO对象
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    //为当前绑定到target的缓冲区对象创建一个新的数据存储
    //如果data不是NULL,则使用来自此指针的数据初始化数据存储
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //告知显卡如何解析缓冲里的属性值
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //开启VAO管理的第一个属性值
    glEnableVertexAttribArray(0);

    //顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    //片段着色器
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);

    //着色器连接
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    //着色器使用后删除
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    //多边形模式:线条填充
//    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    //EBO创建与绑定
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    //VAO和VAO绑定为0,相当于释放休息
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

void OpenGLWidget::resizeGL(int w, int h)
{
    Q_UNUSED(h)
    Q_UNUSED(w)
}

void OpenGLWidget::paintGL()
{
    //red:0.0f无色,1.0f全红
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    //    glDrawArrays(GL_TRIANGLES, 0, 6);//三角形3个点,矩形6个点
        /*
         * EBO解绑时VAO会记录,如果EBO上面解绑了,即
         * glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
         * 那么glDrawElements画要素需要索引,即
         * glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, &indices)
         * VBO不存在这个问题,VAO不存储VBO的绑定函数调用
        */
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0/*&indices*/);
}

四、运行结果

相关推荐
用户805533698032 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner2 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz7 天前
QML Hello World 入门示例
qt
xcyxiner10 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner11 天前
DicomViewer (添加模型类)3
qt
xcyxiner12 天前
DicomViewer (目录调整) 2
qt
xcyxiner12 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00614 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术14 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript