基于Qt的OpenGL(一):OpenGL理论及QOpenGLWidget

框架:OpenGL3.3、Qt 5.12.3MinGW(3.2以前是传统模式(固定管线模式-已被丢弃),3.3是现在模式(可编程管线))
优势:安装简单,Qt已经封装好了,如GLFW和GLAD。自己有支持OpenGL的窗口。
在这里插入图片描述
介绍:OpenGL是Open Graphics Library的缩写,它是由Khronos组织制定并维护的一个规范。OpenGL的核心库是用C语言编写,但同时也支持多种语言的派生。
核心模式:3.3以后版本新推出的模式,也叫现在模式或可编程模式。
GPU渲染是一个流水线:比如输入一大堆零件,出来一个小坦克。首先把顶点位置给顶点着色器,接着装备成一个具体的图元,第三步利用几何着色器做一些图形的修改,第四步把图形变成一个个的像素,第五步是上色,第六步是融合,如像素遮挡、透明度等。
下图中蓝色的部分是3.3之后版本的不同
在这里插入图片描述

OpenGL的状态通常被叫做上下文。OpenGL本身是一个巨大的状态机,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。

在这里插入图片描述
比如给一堆点,如果没有上下文,电脑并不知道是要这些点怎么办,是连线呢,还是亮与不亮,还是一个三角形等等。

OpenGL的代码分为两种

  • 状态设置:glXXX等函数,状态的改变。
  • 状态应用:使用现有状态来绘图。

对象:在OpenGL的代码里,有一个非常重要的概念是对象。一个对象是指一些选项的集合,代表OpenGL状态的一个子集。例如可以用一个对象来代表绘图窗口的设置:大小,颜色位数等。OpenGL就是一个大的结构体,对象就是其中的元素。

以下部分对接下来的OpenGL代码理解非常重要

//OpenGL有自己的数据类型,这样的好处是支持跨平台,和平台无关。,但改成uint也没问题
GLuint objectid = 0;//用以记录对象的ID
glGenObject(1,&objectid);//创建一个对象,并把该对象的ID赋给变量
//绑定对象至上下文
glBindObject(GL_WINDOW_TARGET,objectld);//用对象记录状态机的子集GL_WINDOW_TARGET的变化
//改变子集的状态
glSetObjectOption(GL_WINDOW_TARGET,GL_OPTION_WINDOW_WIDTH,800);
glSetObjectOption(GL_WINDOW_TARGET,GL_OPTION_WINDOW_HEIGHT,600);
//解绑
glBindObject(GL_WINDOW_TARGET,0);//不用记录就绑0,这时候对象可以休息了,需要查看记录只需要喊过来即可。

QOpenGLWidget:不需要GLFW

Qt提供了QOpenGLWidget窗口类,可以在该类中写OpenGL的代码。QOpenGLWidget提供了三个便捷的虚函数,可以重载用来实现经典的OpenGL任务:

  • paintGL:渲染OpenGL场景,widget需要更新时调用
  • resizeGL:设置OpenGL视口,投影等。widget首次显示或调整大小时调用
  • initializeGL:设置OpenGL资源和状态,第一次调用resizeGL或paintGL之前调用一次

需要注意的是:

  • 如果需要从paintGL()以外的位置触发重新绘制(典型示例是使用计时器设置场景动画),则应调用widget的update()函数来安排更新。
  • 调用paintGL()、resizeGL()或initializeGL()时,widget的OpenGL呈现上下文将变为当前,如果需要从其他位置调用标准OpenGL API函数,则必须首先调用makeCurrent()。
  • 在paintGL()以外的地方调用绘制函数,没有意义,因为绘制函数最终将被paintGL()覆盖。
  • 我们编程的函数都是函数指针,只有指针指向内存里的函数才会被调用,而这个工作是由QOpenGLFunctions_X_X_Core完成。QOpenGLFunctions_X_X_Core提供OpenGL X.X版本核心模式的所有功能,是对OpenGL函数的封装。在initializeOpenGLFunctions 之前,会有很指针都是空的,运行之后就会报错,当initializeOpenGLFunctions 执行后,指针都指向了实际的函数,参数就可以传递到显卡的内存中,就可以调用了

使用QOpenGLWidget

  • 第一步:在ui界面拖拽一个OpenGLWidget
    在这里插入图片描述
  • 第二步:添加一个类,继承QOpenGLWidget和QOpenGLFunctions_3_3_Core,并将第一步的对象提升为该类。代码如下:
    在这里插入图片描述
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
class myopenglwidget : public QOpenGLWidget,QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
public:
    explicit myopenglwidget(QWidget *parent = nullptr);
protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();
signals:
public slots:
};
#endif // MYOPENGLWIDGET_H

#include "myopenglwidget.h"
myopenglwidget::myopenglwidget(QWidget *parent) : QOpenGLWidget(parent)
{
}
void myopenglwidget::initializeGL()
{
    initializeOpenGLFunctions();
}
void myopenglwidget::resizeGL(int w, int h)
{
}
void myopenglwidget::paintGL()
{
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
}

接下来进入到几个比较重要的知识点环节

  • 标准化设备坐标(Normalized Device Coordinates, NDC):顶点着色器中处理过后,就变成标准化设备坐标了,x、y、z的值在-1.0 ~ 1.0之间,落在范围外的坐标都将被裁剪。
  • 顶点着色器:其会在GPU上创建内存, 用于存储我们的顶点数据。通过顶点缓冲对象(Vertex Buffer Objects,VBO)管理。顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER,绑定这种类型的缓冲,就是绑定显卡的某个区域,就可以把数据从内存放到显卡。那么显卡如何解释这些内存呢?通过顶点数组对象(Vertex Array Objects,VAO)管理,其类型不像VBO不固定,类型唯一,用于GPU解释数据,如Array中的Pos,Color等。VAO并不保存实际数据,而是存放顶点结构定义。
  • 下面一段代码有助于进一步理解:

//创建VAO和VBO对象,并赋予ID
unsigned int VBO,VAO;
glGenVertexArrays(1,&VAO);//顶点数组用来存数据的结构
glGenBuffers(1,&VBO);//缓冲区用来存放顶点数据
//绑定VBO和VAO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
//为当前绑定到数据的缓冲区对象开辟一个新的数据空间,如果数据不为空,则数据从内存传到显存
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
//告知显卡如何解析传过去的数据,该过程会被VAO偷偷记录
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);//第0个属性、三个值、浮点型、不需要标准化,步长,偏移量
//开启VAO管理的第一个属性值
glEnableVertexAttribArray(0);
//小助理可以休息了,养成好习惯,因为以后有很多的小助理
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindVertexArray(0);
  • 索引缓冲对象:(Element Buffer Object,EBO,也叫Index Buffer Object,IBO)在OpenGL或计算机图形学的世界里面,东西都是由三角形组成的,所以画一个矩形原本需要4个点,但是两个三角形有6个点,重复了2个点,不必要的开销为50%。

版权声明:本文为qq_42308217原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。