基于Qt的OpenGL(三):QOpenGLShaderProgram和GLSL

QOpenGLShaderProgram是对ShaderProgram编译过程的封装,不管是加载SourceCode还是SourceFile,采用Qt的封装都是非常棒的。如果不封装,就会像下图一样,先把代码写入字符串,没有颜色标识,而且每行还得有换行符,非常麻烦。因此我们希望能像普通的C++代码一样编写Shader,好在Qt已经帮我们封装好了
基于Qt的OpenGL(三):QOpenGLShaderProgram和GLSL

为了在QT中正常加载并编辑shader文件:在工具->选项 中设置编码规则为UTF-8。

基于Qt的OpenGL(三):QOpenGLShaderProgram和GLSL

  • 步骤1:在pro文件目录新建txt文本,修改名字为shapes.vert(顶点着色器),shapes.frag(片段着色器)
    基于Qt的OpenGL(三):QOpenGLShaderProgram和GLSL
  • 步骤2:通过资源文件的方式加载进Qt。
    基于Qt的OpenGL(三):QOpenGLShaderProgram和GLSL
    先上代码,看完代码继续如何写GLSL
//类.h文件
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
class myopenglwidget : public QOpenGLWidget,QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
public:
    enum Shape{None,Rect,Circle,Triangle};
    explicit myopenglwidget(QWidget *parent = nullptr);
    ~myopenglwidget();
    void drawShape(Shape shape);
    void setWirefame(bool wireframe);
protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();
private:
    Shape m_shape;
    QOpenGLShaderProgram sharedprogram;
};
#endif // MYOPENGLWIDGET_H
//类.cpp文件
#include "myopenglwidget.h"
//创建VAO和VBO对象,并赋予ID
unsigned int VBO,VAO,EBO;
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[] = {
    0, 1, 3,
    1, 2, 3
};
myopenglwidget::myopenglwidget(QWidget *parent) : QOpenGLWidget(parent)
{
}
myopenglwidget::~myopenglwidget()
{
    makeCurrent();//调用当前状态 不用管为什么,加就好了
    glDeleteBuffers(1,&VBO);
    glDeleteBuffers(1,&EBO);
    glDeleteVertexArrays(1,&VAO);
    doneCurrent();//改变当前状态 不用管为什么,加就好了
}
void myopenglwidget::drawShape(myopenglwidget::Shape shape)
{
    m_shape = shape;
    update();
}
void myopenglwidget::setWirefame(bool wireframe)
{
    makeCurrent();
    if(wireframe)
        glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
    else
        glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
    update();
    doneCurrent();

}
void myopenglwidget::initializeGL()
{
    initializeOpenGLFunctions();
    glGenVertexArrays(1,&VAO);
    glGenBuffers(1,&VBO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER,VBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER,0);
    sharedprogram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shapes.vert");
    sharedprogram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shapes.frag");
    bool success = sharedprogram.link();
    if (!success)
        qDebug()<<"error";
    glGenBuffers(1,&EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);
    glBindVertexArray(0);
}
void myopenglwidget::resizeGL(int w, int h)
{
}
void myopenglwidget::paintGL()
{
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    sharedprogram.bind();
    glBindVertexArray(VAO);
    switch(m_shape){
    case Rect:
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        break;
    default:
        break;
    }
}
//shapes.frag
#version 330 core
out vec4 FragColor;
void main()
{
    FragColor = vec4(1.0, 0.5, 0.2, 1.0);
}
//shapes.vert
#version 330 core\n
layout (location = 0) in vec3 aPos;
void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0f);
}

GLSL:OpenGL Shading Language

一个典型的shader程序应该是这样的

  • 版本号
  • in或out或uniform关键字。in是从流水线流进的数据,需要用一个变量接受。out是流水线流出的数据,需要用变量传出去。如果CPU需要给几何着色器或者片段着色器值,并不需要完全按照流水线从顶点着色器传下来,也可以用uniform直接从CPU穿给需要的地方。
  • main函数。和c语言的main是一回事,顺序执行。
  • 注意:顶点着色器比较特殊,输入变量为顶点属性。我们能声明的顶点属性数量是有上限的,可以通过下面的代码获取。OpenGL确保至少有16个包含4分量的顶点属性可用,有些硬件允许更多的顶点属性。对于一般的应用开发来说,16个属性是够用的。
nt nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);//nrAttributes≥16

GLSL的基础知识

  • GLSL包含C等语言大部分默认数据类型:int、float、double、uint和bool

  • GLSL也有两中容器类型:
    1: 向量(Vector):vecn-代表有n个float型数据;bvecn-代表n个bool型数据;ivecn-代表n个int型数据;uvecn-代表n个unsiged int型数据;dvecn-代表n个double型数据
    2: 矩阵(Matrix)。

  • 向量允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling),如下所示:

vec2 vect = vec2(0.1, 0.2);
vec2 res = vec4(vect, 0.3, 0.0);//(0.1, 0.2, 0.3, 0.0)
vec2 res2 = vec4(res.xyz, 0.5);//(0.1, 0.2, 0.3, 0.5)
vec2 res3 = vec4(res.xxx, 0.7);//(0.1, 0.1, 0.1, 0.7)

GLSL如何使用

  • 在发送方着色器中声明一个输出。
  • 在接收方着色器中声明一个类似的输入。
  • 当类型和名字都一致,OpenGL就可以将其链接到一起。

顶点着色器接收的是一种特殊形式的输入,否则就会效率低下

  • 为了定义顶点数据如何管理,我们使用location这一元数据(比较高层的数据,类似元代码)指定输入变量,这样我们才可以在CPU上配置顶点属性。例如:layout (location = 0)。layout这个标识,使得我们能把它链接到顶点数据。
  • 当然我们可以不用layout (location = 0)语句,通过glGetAttribLocation()函数问GPU某个变量的位置值location是几,则CPU内存glVertexAttribPointer就是几,这样就可以对上了。也可以通过glBindAttribLocation()函数绑定位置值location。
  • 如果程序只有一个属性,那么写不写位置值location都是0。
    代码如下:
//myopenglwidget.cpp
#include "myopenglwidget.h"
//创建VAO和VBO对象,并赋予ID
unsigned int VBO,VAO,EBO;
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[] = {
    0, 1, 3,
    1, 2, 3
};
myopenglwidget::myopenglwidget(QWidget *parent) : QOpenGLWidget(parent)
{
}
myopenglwidget::~myopenglwidget()
{
    makeCurrent();//调用当前状态 不用管为什么,加就好了
    glDeleteBuffers(1,&VBO);
    glDeleteBuffers(1,&EBO);
    glDeleteVertexArrays(1,&VAO);
    doneCurrent();//改变当前状态 不用管为什么,加就好了
}
void myopenglwidget::drawShape(myopenglwidget::Shape shape)
{
    m_shape = shape;
    update();
}
void myopenglwidget::setWirefame(bool wireframe)
{
    makeCurrent();//调用当前状态
    if(wireframe)
        glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
    else
        glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
    update();
    doneCurrent();//改变当前状态
}
void myopenglwidget::initializeGL()
{
    initializeOpenGLFunctions();
    sharedprogram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shapes.vert");
    sharedprogram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shapes.frag");
    bool success = sharedprogram.link();
    if (!success)
        qDebug()<<"error";
    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);
    sharedprogram.bind();
    GLint posLocation = 2;
    sharedprogram.bindAttributeLocation("aPos",posLocation);//绑定变量aPos的位置
    //告知显卡如何解析传过去的数据,该过程会被VAO偷偷记录
    glVertexAttribPointer(posLocation,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);//第0个属性、三个值、浮点型、不需要标准化,步长,偏移量
    //开启VAO管理的第一个属性值,因为默认所有属性都是关闭的
    glEnableVertexAttribArray(posLocation);
    //小助理可以休息了,养成好习惯,因为以后有很多的小助理
    glBindBuffer(GL_ARRAY_BUFFER,0);
    //绑定VEO对象
    glGenBuffers(1,&EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);
    glBindVertexArray(0);
}
void myopenglwidget::resizeGL(int w, int h)
{
}
void myopenglwidget::paintGL()//绘制都在这里面,别的地方只是进行一个触发
{
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    sharedprogram.bind();
    glBindVertexArray(VAO);
    switch(m_shape){
    case Rect:
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        break;
    default:
        break;
    }
}
//shapes.vert
#version 330 core
in vec3 aPos;
out vec4 vertexColor;
void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0f);
    vertexColor = vec4(1.0, 0.5, 0.2, 1.0);
}

  • Uniform是一种从CPU代码向GPU中的着色器发送数据的方式,其是全局的,可以被任意着色器程序在任意阶段访问。可以理解为GPU和GPU,Uniform数据是一致的
上一篇:Opencv 拼接图片


下一篇:Halcon形状模板匹配