天天看点

qt使用opengl简单的例子-01

一、重要流程步骤

二、重要概念

1、 渲染管线:

它是一系列数据处理过程,并且将应用程序的数据转换到最终渲染的图像。首先接受用户提供的几何数据,并且将它送到一系列着色器中处理。

2、缓存对象

openGl需要将所有的数据都保存在缓存对象中,它相当于由opengl服务端维护的一块内存区域,创建数据缓存,如glBufferData。

3、绘制

当将缓存初始化完毕之后,我们可以通过调用opengl的一个绘制命令来请求渲染几何图元, 如glDrawArrays。

opengl的绘制通常就是将顶点数据传输到openGl服务端,顶点可以视为一个需要统一处理的数据包,这个包中的数据可以是我们需要的任何数据

4、顶点着色

对于绘制命令传输的每个顶点,openGl都会调用一个顶点着色器来处理顶点相关的数据。顶点着色器可以很简单,也可以很复杂,通常一个程序包含多个顶点着色器,但同一时刻只能有一个顶点着色器起作用

5、细分着色

顶点着色器处理每个顶点的关联数据后,如果同时激活了细分着色器,那么它将进一步处理这些数据。细分着色器使用patch来描述一个物体的形状,结果是几何图元数量增加,模型外观会变得平顺

6、几何着色

允许在光栅化之前对每个几何图元做更进一步的处理,例如创建新的图元

7、剪切

顶点可能会落在视口之外,此时与顶点相关的图片会做出改动,保证相关像素不会在视口之外绘制,这个过程叫剪切

8、光栅化

剪切之后马上要执行的工作,将更新后的图元传递到光栅化单元,生成对应的片元,片元可以视为候选的像素,也就是放置在帧缓存中的像素,但是它也可能最终剔除。

9、片元着色

通过编程控制屏幕上显示颜色的阶段,叫做片元着色阶段,在这个阶段计算片元的最终颜色(可能被后一阶段改变)和它的深度值。

如果我们觉得不应该绘制某个片元,可以终止这个片元的处理,这个叫片元的丢弃(discard)

大致可以理解:顶点着色器(包括细分和几何)决定图元应该位于屏幕的什么位置,而片元着色器决定某片元的颜色

10、逐片元的操作

在这个阶段,使用深度测试和模板测试的方式来决定一个片元是否是可见的。一个片元通过所有激活测试,它就可以被直接绘制到帧缓存中

二、实际操作

完整例子

//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QOpenGLWidget>
#include  <QOpenGLShader>
#include <QOpenGLFunctions_4_3_Core>

namespace Ui {
class Widget;
}

class Widget : public QOpenGLWidget, public QOpenGLFunctions_4_3_Core
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int width, int height) override;

private:
    void    initModel();
    void    initShader();

private:
    Ui::Widget *ui;
    GLuint         m_VAO = 0;
    QOpenGLShaderProgram    *m_pGlShaderProgram = Q_NULLPTR;
};
#endif // WIDGET_H

//widget.cpp
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QOpenGLWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
}
Widget::~Widget()
{
    delete ui;
}
void Widget::initializeGL()
{
    
    initializeOpenGLFunctions(); //初始化qt的opengl环境,可以让qt中使用opengl相关的函数
    glViewport(0, 0, 800, 600);//设置位置和宽高

    initShader();//初始化shader程序
    initModel();//初始化定点程序
}

void Widget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT);

    glBindVertexArray(m_VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glFlush();
}

void Widget::resizeGL(int width, int height)
{
    glViewport(0, 0, 800, 600);
}

void    Widget::initModel()
{
    glGenVertexArrays(1, &m_VAO);//返回顶点数组名称,保存在m_VAO中
    glBindVertexArray(m_VAO);    //绑定VAO,后续所有缓存对象都会被当前这个VAO保存,对VBO操作之前先绑定一个VAO

    GLfloat vertices[3][3] = {
        {-0.5, -0.5, 0.0},
         {0.5, -0.5, 0.0},
         {0.0,  0.5, 0.0}
    };

    GLuint nVBO;
    glGenBuffers(1, &nVBO);//生成一个缓存对象名称
    glBindBuffer(GL_ARRAY_BUFFER, nVBO);//绑定缓存对象(VBO)
    glBufferData(GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW);//将顶点数据存入VBO对象中

    GLuint nPos = 0;
    glVertexAttribPointer(nPos, 3, GL_FLOAT, GL_FALSE, 0, 0);//从顶点着色器中获取索引为0的位置,并绑定缓存数据
    glEnableVertexAttribArray(nPos);//设置可以开启顶点数据
//    int attr = -1;
//    attr =m_pGlShaderProgram->attributeLocation("aPos");
//    m_pGlShaderProgram->setAttributeBuffer(attr, GL_FLOAT, 0, 3, sizeof(GLfloat) * 3);
//    m_pGlShaderProgram->enableAttributeArray(attr);
}

void    Widget::initShader()
{
    const char* _vertexPath = "vertexShader.glsl";
    const char* _fragPath = "fragmentShader.glsl";

    m_pGlShaderProgram = new QOpenGLShaderProgram;
    m_pGlShaderProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, _vertexPath);
    m_pGlShaderProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, _fragPath);


    m_pGlShaderProgram->link();
    m_pGlShaderProgram->bind();
}
           

二、 OpenGl的初始化过程

1、InitModel 初始化数据

opengl中对象需要从显存中获取,并进行绑定,后续所有对应操作,都会被opengl记住,并作用于该对象。

1) 分配顶点数组对象

通过glGenVertexArrays(Glsizer n, GLuint arrays);返回n个未使用的对象名到数组arrays中,返回的名字可以用来分配更多的缓存对象。

openGl中,分配的机制叫做绑定对象,通过一系列glBind函数实现,glBindVertexArrays()将顶点数组对象进行绑定。

注意:顶点数组对象负责保存一系列顶点的数据,这些数据保存到缓存对象当中,并且由当前绑定的顶点数组对象管理。顶点数组本身并不保存任何数据,可以看作一系列缓存对象的集合的引用。

2) 分配顶点缓存对象

缓存对象就是openGl服务端分配和管理的一块内存区域,并且几乎所有传入openGl的数据都是存储在缓存对象中。

缓存对象初始化和顶点数组初始化类似,多了一步向缓存中添加数据:glBufferData。glBufferData两个任务,分配顶点数据所需的存储空间,然后将数据从应用程序中的数据组中拷贝到OpenGL服务端的内存中。

2、opengl渲染

3.1以上的opengl都至少需要指定两个着色器,顶点着色器和片源着色器,上述例子中就是使用了这两个着色器
           

1)顶点着色器

#version 430 core    //430指定了opengl版本
layout (location = 0) in vec3 aPos; // vec3表示数据类型,in表示数据流入着色器的流向,layout为布局限定符,目的是为变量提供元数据,代码中可以利用这个限定符为aPos变量提供顶点数据
void main()
{
   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); //gl_Position 为全局变量,是顶点着色器指定的输出位置
};
           
  1. 片源着色器
#version 430 core
out vec4 FragColor; //out 指定流向为输出
void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); //输出指定的颜色,前面三个值是RGB值,最后一个是alpha值。用来表示透明度
};
           
  1. 代码关联着色器

    为了方便,使用qt的着色器程序,与opengl原生的加载着色器操作步骤类似,先链接编译,再绑定,告诉opengl,使用该着色器程序。并利用glVertexAttribPointer与顶点着色器中的aPos变量关联,将数据输入该变量(注释部分写了qt方式的关联数据,与原生函数类似)

4)渲染数据

首先,需要清楚帧缓存的数据再进行渲染,由glClear完成

调用glBindVertexArray来选择作为顶点数据使用的顶点数组

调用glDrawArrays()来实现顶点数据向OpenGl管线的传输

调用glFlush(),强制所有进行中的OpenGL命令立即完成并传输到OpenGl服务端处理