QOpenGLShaderProgram是对ShaderProgram编译过程的封装,不管是加载SourceCode还是SourceFile,采用Qt的封装都是非常棒的。如果不封装,就会像下图一样,先把代码写入字符串,没有颜色标识,而且每行还得有换行符,非常麻烦。因此我们希望能像普通的C++代码一样编写Shader,好在Qt已经帮我们封装好了。
为了在QT中正常加载并编辑shader文件:在工具->选项 中设置编码规则为UTF-8。
-
步骤1:在pro文件目录新建txt文本,修改名字为shapes.vert(顶点着色器),shapes.frag(片段着色器)
-
步骤2:通过资源文件的方式加载进Qt。
先上代码,看完代码继续如何写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数据是一致的