一、Qt UI调用OpenGL控件功能
1、如果需要从paintGL()以外位置触发重新绘制(典型示例是使用计时器设置场景动画),则应调用Widget的update()函数来安排更新。
2、在paintGL()、resizeGL()、initializeGL()里调用标准OpenGL API函数,Widget的OpenGL上下文将自动变为当前。
3、但在其它函数里调用标准OpenGL API函数,则必须首先调用makeCurrent(),最后调用doneCurrent()。
4、在paintGL()以外的地方调用OpenGL的绘制函数,没有意义,因为绘制图像会被paintGL()覆盖。
5、VAO、VBO、EBO、Shader、ShaderProgram创建对象后,也是需要删除回收的。
glDeleteVertexArrays()
glDeleteBuffers()
glDeleteShader()
glDeleteProgram()
二、着色器
1、着色器(Shader)是运行在GPU上的小程序。
2、这些小程序为图形渲染管线的某个特定部分而运行。
3、着色器是一种把输入转化为输出的程序。
4、着色器是一种非常独立的程序,因为它们之间不能互相通信,唯一的沟通只有通过输入和输出。
三、GLSL
1、着色器是用GLSL写的,GLSL是一种类C语言。
2、GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。
3、着色器结构:
#version version_number //声明版本
in type in_variable_name; //输入变量1
in type in_variable_name; //输入变量2...
out type out_variable_name; //输出变量
uniform type uniform_name; //uniform
int main() //main函数
{
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
4、在顶点着色器中,每个输入变量也叫顶点属性(Vertex Attribute),属性如位置、颜色、纹理...。
5、开发者能声明的顶点属性有上限,一般由硬件决定,OpenGL确保至少有16个包含4分量的顶点属性可用,通过glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &n)可查询上限。
四、数据类型
1、GLSL包含基本数据类型: int 、 float 、 double 、 uint 、 bool。
2、GLSL包含两种容器类型: 向量(Vector)和矩阵(Matrix)。
3、GLSL的向量是一个可以包含2、3或4个分量的容器,分量的类型可以是基本数据类型的任意一个。
vecn 包含n个float分量的默认向量
bvecn 包含n个bool分量的向量
ivecn 包含n个int分量的向量
uvecn 包含n个uint分量的向量
dvecn 包含n个double分量的向量
4、获取向量的分量方式:
* 通过vec.x、vec.y、vec.z、vec.w获取向量的第1、2、3、4个分量。
* 通过vec.r、vec.g、vec.b、vec.a获取代表颜色的向量的第1、2、3、4个分量。
* 通过vec.s、vec.t、vec.p、vec.q获取代表纹理的向量的第1、2、3、4个分量。
* 通过重组(Swizzling),如:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);
五、输入与输出
1、着色器虽是各自独立的小程序,但又是渲染管线整体的一部分,因此着色器有输入和输出,数据才能交流与传递。
2、GLSL用in关键字设定输入,用out关键字设定输出。
3、顶点着色器中, 用layout(location = 0),把顶点着色器链接到顶点数据,从顶点数据中直接接收输入。
4、片段着色器中,需要一个vec4颜色输出变量,如果没有定义输出颜色,OpenGL会把物体渲染为黑色(或白色)。
5、若开发者打算从一个着色器向另一个着色器发送数据,则:
在发送着色器中声明一个输出变量;
在接收着色器中声明一个输入变量;
这两个变量类型和名字都一样,OpenGL就会把两个变量链接到一起,它们之间就能发送数据了。
6、例如: 从顶点着色器给片段着色器发送数据
顶点着色器:
#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0
out vec4 vertexColor; // 为片段着色器指定一个颜色输出
void main()
{
gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数
vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把输出变量设置为暗红色
}
片段着色器:
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同)
void main()
{
FragColor = vertexColor;
}
六、Uniform
1、Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式。
2、Uniform是全局的(Global),Uniform变量在着色器中必须独一无二。
3、Uniform变量值在着色器程序中不可被改变(测试改了会渲染成白色),只有在CPU应用中可被重置或更新。
4、可以在着色器中添加uniform关键字至类型和变量名前来声明一个GLSL的uniform变量。
5、例如: 从CPU应用中向着色器发送数据
顶点着色器
#version 330 core
layout(location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
片段着色器
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; //在OpenGL程序代码中设定这个变量
void main()
{
FragColor = ourColor;
}
6、注意,如果在着色器中声明了一个uniform变量,却在着色器中没有用过它,编译器会静默移除这个变量,导致最后编译出的版本中并不会包含它,这可能导致错误。
7、在CPU应用中,得先通过glGetUniformLocation(shaderProgram, "ourColor")找到着色器中uniform变量的位置值,返回-1表示没有找到位置值。
8、得到uniform变量的位置值后,可以通过glUniform4f()函数设置uniform变量值。
9、注意,必须在设置uniform变量值之前,先使用着色器程序(glUseProgram()),因为它是在当前激活的着色器程序中设置uniform的。
10、glUniform()函数有后缀,用来标识设定的uniform的类型。
f 函数需要一个float值
i 函数需要一个int值
ui 函数需要一个unsigned int值
3f 函数需要三个float值
fv 函数需要一个float向量/数组值
......
11、uniform对于设置一个在渲染迭代中会改变的属性是一个非常有用的工具。
七、更多属性
1、我们可以把颜色数据加入到顶点数据中,传入到VBO。
2、运行代码,会发现一个大调色盘,这是片段插值(Fragment Interpolation)的结果。
当渲染一个矩形时,光栅化阶段通常会造成比原指定顶点更多的片段。
光栅化阶段会根据每个片段在矩形上相对位置,插值(Interpolate)所有片段着色器的输入变量。
比如一个线段,上面的端点是绿色,下面的端点是蓝色,如果一个片段着色器在线段的70%位置(从下到上)运行,则它的颜色输入变量会被插值为30%蓝+70%绿。
3、片段插值会被应用到片段着色器的所有输入属性上。
八、代码
//MyShader.h
#pragma once
#include
class MyShader : public QOpenGLFunctions_4_5_Core
{
public:
MyShader(const std::string & vtShaderPath, const std::string & fmShaderPath);
~MyShader();
void Use();
void SetBool(const std::string & name, bool value);
void SetInt(const std::string & name, int value);
void SetFloat3(const std::string & name, float value1, float value2, float value3);
private:
void CheckCompileErrors(unsigned int id, std::string type);
private:
unsigned int m_shaderProgramID;
};
//MyShader.cpp
#include "MyShader.h"
#include
#include
#include
MyShader::MyShader(const std::string & vtShaderPath, const std::string & fmShaderPath)
{
std::string vtShaderCode;
std::string fmShaderCode;
std::ifstream vtShaderFile;
std::ifstream fmShaderFile;
//确保ifstream对象可以抛异常
vtShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fmShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try
{
vtShaderFile.open(vtShaderPath.c_str());
fmShaderFile.open(fmShaderPath.c_str());
std::stringstream vtShaderStrStream;
std::stringstream fmShaderStrStream;
vtShaderStrStream << vtShaderFile.rdbuf();
fmShaderStrStream << fmShaderFile.rdbuf();
vtShaderFile.close();
fmShaderFile.close();
vtShaderCode = vtShaderStrStream.str();
fmShaderCode = fmShaderStrStream.str();
}
catch (std::ifstream::failure & e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl;
}
initializeOpenGLFunctions();
unsigned int vertexShaderID, fragmentShaderID;
const char * vtShaderCodeStr = vtShaderCode.c_str();
const char * fmShaderCodeStr = fmShaderCode.c_str();
//顶点着色器
vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShaderID, 1, &vtShaderCodeStr, NULL);
glCompileShader(vertexShaderID);
CheckCompileErrors(vertexShaderID, "VERTEX");
//片段着色器
fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShaderID, 1, &fmShaderCodeStr, NULL);
glCompileShader(fragmentShaderID);
CheckCompileErrors(fragmentShaderID, "FRAGMENT");
//着色器程序
m_shaderProgramID = glCreateProgram();
glAttachShader(m_shaderProgramID, vertexShaderID);
glAttachShader(m_shaderProgramID, fragmentShaderID);
glLinkProgram(m_shaderProgramID);
CheckCompileErrors(m_shaderProgramID, "PROGRAM");
//删除着色器
glDeleteShader(vertexShaderID);
glDeleteShader(fragmentShaderID);
}
MyShader::~MyShader()
{
glDeleteProgram(m_shaderProgramID);
}
void MyShader::Use()
{
glUseProgram(m_shaderProgramID);
}
void MyShader::SetBool(const std::string & name, bool value)
{
glUniform1i(glGetUniformLocation(m_shaderProgramID, name.c_str()), (int)value);
}
void MyShader::SetInt(const std::string & name, int value)
{
glUniform1i(glGetUniformLocation(m_shaderProgramID, name.c_str()), value);
}
void MyShader::SetFloat3(const std::string & name, float value1, float value2, float value3)
{
glUniform3f(glGetUniformLocation(m_shaderProgramID, name.c_str()), value1, value2, value3);
}
void MyShader::CheckCompileErrors(unsigned int id, std::string type)
{
int success = 0;
char infoLog[512] = { 0 };
if (type != "PROGRAM")
{
glGetShaderiv(id, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(id, 512, NULL, infoLog);
std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
else
{
glGetProgramiv(id, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(id, 512, NULL, infoLog);
std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
}
//MyOpenGLWidget.h
#pragma once
//需要两个头文件
#include
#include
#include "MyShader.h"
class MyOpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{
Q_OBJECT
public:
MyOpenGLWidget(QWidget * parent = nullptr);
~MyOpenGLWidget();
enum Shape { None, Rect, Circle, Triangle };
void DrawShape(Shape shape);
void WireFrame(bool bWire);
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
private:
unsigned int VAO;
unsigned int VBO;
unsigned int EBO;
float m_ramValue = 0.0f;
Shape m_shape = None;
MyShader * m_shaderProgram = nullptr;
};
//MyOpenGLWidget.cpp
#include "MyOpenGLWidget.h"
#include
MyOpenGLWidget::MyOpenGLWidget(QWidget * parent) : QOpenGLWidget(parent)
{
}
MyOpenGLWidget::~MyOpenGLWidget()
{
makeCurrent();
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glDeleteVertexArrays(1, &VAO);
delete m_shaderProgram;
doneCurrent();
}
void MyOpenGLWidget::DrawShape(Shape shape)
{
//触发重新绘制
m_shape = shape;
update();
}
void MyOpenGLWidget::WireFrame(bool bWire)
{
makeCurrent();
if (bWire)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
else
{
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
doneCurrent();
update();
}
void MyOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
//创建VAO、VBO、EBO,并赋予ID
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
//绑定VAO、VBO、EBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); //注意, 因为VAO已经绑定,此句会被VAO的最后一个指针偷偷记录下来, 指向EBO。
//顶点数据
float vertices[] =
{
//位置 //颜色
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // 左下角
-0.5f, 0.5f, 0.0f, 0.3f, 0.5f, 0.8f, // 左上角
};
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//索引数据
unsigned int indices[] =
{
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//配置VAO位置属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
//配置VAO颜色属性指针
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
//解绑VAO、VBO、EBO
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //注意,因为VAO已经解绑了,所以此句不会被VAO记录下来,VAO的最后一个指针依然指向EBO,这样好处是绘制前,只要再绑定VAO即可,EBO、VBO都不用再绑定。
//(1) 顶点着色器向片段着色器传递数据
//(2) 从CPU应用中向GPU着色器传递数据
//(3) 从顶点数据发送更多属性数据到GPU
m_shaderProgram = new MaLanShader("VertexShaderCode.txt", "FragmentShaderCode.txt");
}
void MyOpenGLWidget::resizeGL(int w, int h)
{
}
//绘制都是在此函数中,其它地方都是触发绘制
void MyOpenGLWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(VAO);
m_shaderProgram->Use();
//(2) 从CPU应用中向GPU着色器传递数据
/*if (m_ramValue >= 1.0f)
{
m_ramValue = 0.0f;
}
m_ramValue += 0.1f;
m_shaderProgram->SetFloat3("ourColor", 0.0f, m_ramValue, 0.0f);*/
switch (m_shape)
{
case MaLanOpenGLWidget::None:
break;
case MaLanOpenGLWidget::Rect:
{
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
break;
}
case MaLanOpenGLWidget::Circle:
break;
case MaLanOpenGLWidget::Triangle:
break;
default:
break;
}
}
//MainWindow.h
#pragma once
#include
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindowClass; };
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget * parent = nullptr);
~MainWindow();
private:
void ActDrawRectTriggered(bool checked);
void ActClearTriggered(bool checked);
void ActWireFrameTriggered(bool checked);
private:
Ui::MainWindowClass * ui;
};
//MainWindow.cpp
#include "MainWindow.h"
#include "ui_MainWindow.h"
MainWindow::MainWindow(QWidget * parent): QMainWindow(parent), ui(new Ui::MainWindowClass())
{
ui->setupUi(this);
setCentralWidget(ui->openGLWidget);
connect(ui->actDrawRect, &QAction::triggered, this, &MainWindow::ActDrawRectTriggered);
connect(ui->actClear, &QAction::triggered, this, &MainWindow::ActClearTriggered);
connect(ui->actWireFrame, &QAction::triggered, this, &MainWindow::ActWireFrameTriggered);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::ActDrawRectTriggered(bool checked)
{
ui->openGLWidget->DrawShape(MaLanOpenGLWidget::Rect);
}
void MainWindow::ActClearTriggered(bool checked)
{
ui->openGLWidget->DrawShape(MaLanOpenGLWidget::None);
}
void MainWindow::ActWireFrameTriggered(bool checked)
{
ui->openGLWidget->WireFrame(checked);
}
九、VertexShaderCode.txt内容
1、顶点着色器向片段着色器传递数据
#version 330 core
layout(location = 0) in vec3 aPos;
out vec4 vertexColor;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
vertexColor = vec4(0.5, 0.0, 0.0, 1.0);
}
2、从CPU应用中向GPU着色器传递数据
#version 330 core
layout(location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
3、从顶点数据发送更多属性数据到GPU
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;
out vec3 ourColor;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
ourColor = aColor;
}
十、FragmentShaderCode.txt内容
1、顶点着色器向片段着色器传递数据
#version 330 core
in vec4 vertexColor;
out vec4 FragColor;
void main()
{
FragColor = vertexColor;
}
2、从CPU应用中向GPU着色器传递数据
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor;
void main()
{
FragColor = ourColor;
}
3、从顶点数据发送更多属性数据到GPU
#version 330 core
in vec3 ourColor;
out vec4 FragColor;
void main()
{
FragColor = vec4(ourColor, 1.0);
}