【QT】——OpenGL学习(一)

发布时间:2024年01月17日

OpenGL简介

OpenGL(英语:Open Graphics Library,译名:开放图形库或者“开放式图形库”)是用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(API)。
这个接口由近350个不同的函数调用组成,用来绘制从简单的图形比特到复杂的三维景象。而另一种程序接口系统是仅用于Microsoft Windows上的Direct3D。OpenGL常用于CAD、虚拟现实、科学可视化程序和电子游戏开发。
OpenGL的高效实现(利用了图形加速硬件)存在于Windows,部分UNIX平台和Mac OS。这些实现一般由显示设备厂商提供,而且非常依赖于该厂商提供的硬件。开放源代码库Mesa是一个纯基于软件的图形API,它的代码兼容于OpenGL。但是,由于许可证的原因,它只声称是一个“非常相似”的API。

Qt中的OpenGL

通过继承QOpenGLWidget和QOpenGLExtraFunctions,重载void initializeGL(),void paintGL()还有void resizeGL(int w, int h)三个函数进行绘图

创建一个QWidget

继承QOpenGLWidget和QOpenGLExtraFunctions

#include <QOpenGLWidget>
#include <QOpenGLExtraFunctions>

class OpenGLWidget : public QOpenGLWidget, public QOpenGLExtraFunctions
重载initializeGL(),paintGL()和resizeGL(int w, int h)
protected:
    virtual void initializeGL() override;
    virtual void resizeGL(int x, int y) override;
    virtual void paintGL() override;

在initializeGL()函数中做一些基本的初始化
OpenGL本身的API只提供了“函数定义”,所以所有的实现实际上是操作系统或者其它库的工作。
初始化OpenGL函数的目的,就是加载这些OpenGL的实现。

initializeOpenGLFunctions();

设置一些OpenGL的特性,例如深度测试。
深度测试是指,“近处的物体会遮挡远处的物体”这种在现实中最为基础的法则。

glEnable(GL_DEPTH_TEST);

设置一下刷新时的背景颜色,四个参数分别为R,G,B,A,的值,取值范围[0, 1]。

glClearColor(0, 0.5, 0.7, 1);
创建缓存

OpenGL的世界中,想要绘制3D图像,3个点,就可以确认一个三角形。将顶点中的x,y,z每个值,一个一个的放到缓存中。
OpenGL中存在两个概念:
VAO指的是顶点列表对象,VBO指的是顶点缓存对象。
VAO可以帮助我们在绘制多个3D物品时,将各自物品的绘制状态给隔离。即:每个物品都可以有自己的顶点缓存,shader,以及其它的各种各样的状态。VAO会帮你把这些状态保存下来,下一次执行的时候,你就不需要重复的设置这些状态了。简单来说就是:一次设置,到处使用。

#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>

QOpenGLVertexArrayObject m_vao;
QOpenGLBuffer m_vbo;


m_vao.create();
m_vbo.create();
Shader

需要使用Shader控制渲染流程。在Qt中,提供了QOpenGLShaderProgram类帮助用户来使用shader。
in 用在函数的参数中,表示这个参数是输入的,在函数中改变这个值,并不会影响对调用的函数产生副作用。(相当于C语言的传值),这个是函数参数默认的修饰符
out 用在函数的参数中,表示该参数是输出参数,值是会改变的。

图形处理之Shader语言(一)GLSL语法篇

#include <QOpenGLShaderProgram>

QOpenGLShaderProgram *m_program;

    m_program = new QOpenGLShaderProgram();
    ///gl_Position	vec4	输出属性-变换后的顶点的位置,用于后面的固定的裁剪等操作。
    /// 所有的顶点着色器都必须写这个值。
    /// 顶点着色器(Vertex Shader)
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, u8R"(

#version 330 core
in vec3 vPos;
in vec2 vTexture;
out vec2 oTexture;
void main()
{
    gl_Position = vec4(vPos, 1.0);
    oTexture = vTexture;
}

)");
   ///sampler2D	二维纹理句柄
   /// uniform	一致变量。这个值在编译时期是未知的是由着色器外部初始化的。
   ///只能在全局范围进行声明。
   ///gl_FragColor	vec4	输出的颜色用于随后的像素操作
   ///  Fragment Shader(片段着色器)

    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, u8R"(

#version 330 core
in vec2 oTexture;
uniform sampler2D uTexture;
void main()
{
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    //gl_FragColor = texture(uTexture, oTexture);
}
)");
顶点缓存

一个一维数组,存储了顶点信息

float _vertex[] = {
        -0.5, -0.5, 0.0,
        -0.5,  0.5, 0.0,
         0.5,  0.5, 0.0,
         0.5, -0.5, 0.0,
};

可以将其看作四个点,按照x,y,z,x,y,z,x,y,z这个顺序排列。

使用vPos这个输入变量的名字来告诉OpenGL,我们的顶点缓存中,是按照vPos这个变量的类型,即vec3的标准来保存顶点信息的。

m_program->bind();
// 绑定顶点坐标信息, 从0 * sizeof(float)字节开始读取3个float, 因为一个顶点有3个float数据, 所以下一个数据需要偏移3 * sizeof(float)个字节
m_program->setAttributeBuffer("vPos", GL_FLOAT, 0, 3, 3 * sizeof(float));
m_program->enableAttributeArray("vPos");

释放VAO和Shader

m_program->release();
m_vao.release();
完整的初始化代码
void OpenGLWidget::initializeGL()
{
    initializeOpenGLFunctions();
    glEnable(GL_DEPTH_TEST);
    glClearColor(0, 0.5, 0.7, 1);
    m_vao.create();
    m_vbo.create();
    m_program = new QOpenGLShaderProgram();
    ///gl_Position	vec4	输出属性-变换后的顶点的位置,用于后面的固定的裁剪等操作。
    /// 所有的顶点着色器都必须写这个值。
    /// 顶点着色器(Vertex Shader)
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, u8R"(

#version 330 core
in vec3 vPos;
in vec2 vTexture;
out vec2 oTexture;
void main()
{
    gl_Position = vec4(vPos, 1.0);
    oTexture = vTexture;
}

)");
   ///sampler2D	二维纹理句柄
   /// uniform	一致变量。这个值在编译时期是未知的是由着色器外部初始化的。
   ///只能在全局范围进行声明。
   ///gl_FragColor	vec4	输出的颜色用于随后的像素操作
   ///  Fragment Shader(片段着色器)

    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, u8R"(

#version 330 core
in vec2 oTexture;
uniform sampler2D uTexture;
void main()
{
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    //gl_FragColor = texture(uTexture, oTexture);
}
)");
    m_program->link();
    float _vertex1[] = {
        -0.5, -0.5, 0.0,
        -0.5,  0.5, 0.0,
         0.5,  0.5, 0.0,
         0.5, -0.5, 0.0,
    };
    // 顶点缓存中前三个是顶点坐标, 后两个是纹理坐标, 一个顶点由5个float值组成
    float _vertex0[] = {
        //  顶点		 纹理
        -1,  1, 0,	0, 1, // 左上
        -1, -1, 0,	0, 0, // 左下
         1, -1, 0,	1, 0, // 右下
         1,  1, 0,	1, 1, // 右上
    };

    m_vao.bind();
    m_vbo.bind();

    //m_vbo.allocate(_vertex, 9 * sizeof(float));
    m_vbo.allocate(_vertex1, sizeof(_vertex1));

    m_program->bind();
     // 绑定顶点坐标信息, 从0 * sizeof(float)字节开始读取3个float, 因为一个顶点有5个float数据, 所以下一个数据需要偏移3 * sizeof(float)个字节
    m_program->setAttributeBuffer("vPos", GL_FLOAT, 0, 3, 3 * sizeof(float));
    m_program->enableAttributeArray("vPos");
	m_program->release();
    m_vao.release();

}
绘制图形
void OpenGLWidget::paintGL()
{
    m_vao.bind();
    m_program->bind();
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    //GL_TRIANGLE_FAN绘制各三角形形成一个扇形序列,以v0为起始点,(v0,v1,v2)、(v0,v2,v3)、(v0,v3,v4)。
    m_program->release();
    m_vao.release();
}

一般情况下有三种绘制一系列三角形的方式,分别是

  • GL_TRIANGLES
  • GL_TRIANGLE_STRIP
  • GL_TRIANGLE_FAN

在这里插入图片描述

GL_TRIANGLES是以每三个顶点绘制一个三角形。第一个三角形使用顶点v0,v1,v2,第二个使用v3,v4,v5,以此类推。如果顶点的个数n不是3的倍数,那么最后的1个或者2个顶点会被忽略。
GL_TRIANGLE_STRIP则稍微有点复杂。

其规律是:
构建当前三角形的顶点的连接顺序依赖于要和前面已经出现过的2个顶点组成三角形的当前顶点的序号的奇偶性(如果从0开始):

  • 如果当前顶点是奇数: 组成三角形的顶点排列顺序:T = [n-1、n-2、n].
  • 如果当前顶点是偶数: 组成三角形的顶点排列顺序:T = [n-2、n-1、n].

以上图为例,第一个三角形,顶点v2序号是2,是偶数,则顶点排列顺序是v0,v1,v2。第二个三角形,顶点v3序号是3,是奇数,则顶点排列顺序是v2,v1,v3,第三个三角形,顶点v4序号是4,是偶数,则顶点排列顺序是v2,v3,v4,以此类推。
注意:顶点个数n至少要大于3,否则不能绘制任何三角形。

GL_TRIANGLE_FAN绘制各三角形形成一个扇形序列,以v0为起始点,(v0,v1,v2)、(v0,v2,v3)、(v0,v3,v4)。

运行结果

在这里插入图片描述

参考学习

在Qt中使用OpenGL(一)
在Qt中使用OpenGL(二)
理解GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN绘制三角形序列的三种方式

文章来源:https://blog.csdn.net/vhcjgc/article/details/124915567
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。