本篇完整工程见gitee:QTOpenGL
对应点的tag,由turbolove提供技术支持,您可以关注博主或者私信博主。
什么是opengl
open graphics library 他是一个由Khronos组织制定并且维护的规范
opengl核心是一个c库,同时也支持多种语言的派生
核心模式(core-profile):
立即渲染模式(Immediate mode)
状态机(state machine)
对象(Object)
// 创建对象
GLunit objId = 0;
glGenObject(1, &objId);
// 绑定对象到上下文
glBindObject(GL_WINDOW_TARGET, objId);
// 设置GL_WINDOW_TARGET对象的一些选项
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
// 将上下文的GL_WINDOW_TARGET对象设置成默认值
glBindObject(GL_WINDOW_TARGET, 0);
// 一旦我们重新绑定这个对象到GL_WINDOW_TARGET位置,这些选项就会重新生效
QOpenGLWidget提供了三个便捷的虚拟函数,可以冲在,用来实现典型的opengl任务
如果需要从paintGL()意外的位置触发重新绘制(典型示例是使用计时器设置场景动画),则应该调用widget的update()函数来安排更新。
调用paintGL()、resizeGL()、initializeGL()时,widget的opengl呈现上下文变为当前。如果需要从其他位置(例如在widget的构造函数或者自己的绘制函数中)调用openglAPI函数,则必须首先调用makeCurrent()。
QOpenGLFunction_X_X_Core提供OpenGL x.x版本核心模式的所有功能,是对OpenGL函数的封装
turboopenglwidget.h
#ifndef QTOPENGL_TURBOOPENGLWIDGET_H
#define QTOPENGL_TURBOOPENGLWIDGET_H
#include
#include
class TurboOpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{
Q_OBJECT
public:
explicit TurboOpenGLWidget(QWidget *parent = 0);
protected:
void initializeGL() override;
void paintGL() override;
void resizeGL(int w, int h) override;
private:
};
#endif //QTOPENGL_TURBOOPENGLWIDGET_H
turboopenglwidget.cpp
#include "turboopenglwidget.h"
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
}
void TurboOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
QOpenGLWidget::initializeGL();
}
void TurboOpenGLWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
QOpenGLWidget::paintGL();
}
void TurboOpenGLWidget::resizeGL(int w, int h)
{
QOpenGLWidget::resizeGL(w, h);
}
float vertices[] =
{
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f,
};
标准化设备坐标:
订单着色器中处理过后,应该就是标准化设备坐标,x、y、z的值在-1.0到1.0的一小段空间(立方体)。落在范围外的坐标都会被裁减。
数组力的每一个项都对应一个属性的解析。
OpenGL允许我们同时绑定多个缓冲,只要他们是不同的缓冲类型(每个缓冲类型类似于前面说的子集,每个VBO是一个小助理)。
VAO并不保存实际数据,而是放顶点结构定义。
// 创建VAO和VBO对象,并且赋予ID
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// 绑定VAO和VBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 为当前绑定到target的缓冲区对象创建一个新的数据存储
// 如果data不是NULL, 则使用来自此指针的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 告知显卡如何解析缓冲里面的属性值
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);
// 开启VAO管理的第一个属性的值
glEnableVertexAttribArray(0);
// 释放VAO和VBO对象
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glBufferData
是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。
第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof
计算出顶点数据大小就行。
第三个参数是我们希望发送的实际数据。
第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
GL_STATIC_DRAW :数据不会或几乎不会改变。
GL_DYNAMIC_DRAW:数据会被改变很多。
GL_STREAM_DRAW :数据每次绘制时都会改变。
glVertexAttribPointer
layout(location = 0)
定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0
。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0
。vec3
,它由3个值组成,所以大小是3。vec*
都是由浮点数值组成的)。float
之后,我们把步长设置为3 * sizeof(float)
。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。void*
,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos, 1.0);
}
// 创建顶点着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader ,1 ,&vertexShaderSource, NULL);
glCompileShader(vertexShader);
glShaderSource
NULL
。#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
// 创建顶片段着色器
unsigned int fragShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragShader ,1 ,&fragShaderSource, NULL);
glCompileShader(fragShader);
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragShader);
glLinkProgram(shaderProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragShader);
turboopenglwidget.cpp
#include "turboopenglwidget.h"
float vertices[] =
{
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f,
};
const char *vertexShaderSource =
"#version 330 core \n"
"layout (location = 0) in vec3 aPos;\n"
"\n"
"void main()\n"
"{\n"
"\tgl_Position = vec4(aPos, 1.0);\n"
"}";
const char *fragShaderSource =
"#version 330 core\n"
"out vec4 FragColor;\n"
"\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"} ";
// 创建VAO和VBO对象,并且赋予ID
unsigned int VBO, VAO;
unsigned int shaderProgram;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
}
void TurboOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// 绑定VAO和VBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 为当前绑定到target的缓冲区对象创建一个新的数据存储
// 如果data不是NULL, 则使用来自此指针的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 告知显卡如何解析缓冲里面的属性值
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);
// 开启VAO管理的第一个属性的值
glEnableVertexAttribArray(0);
// 释放VAO和VBO对象
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// 创建顶点着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader ,1 ,&vertexShaderSource, NULL);
glCompileShader(vertexShader);
// 创建顶片段着色器
unsigned int fragShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragShader ,1 ,&fragShaderSource, NULL);
glCompileShader(fragShader);
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragShader);
glLinkProgram(shaderProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragShader);
}
void TurboOpenGLWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
void TurboOpenGLWidget::resizeGL(int w, int h)
{
}
可以绘制两个三角形来组合成一个矩形,这会生成下面的顶点的集合:
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, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
值得庆幸的是,元素缓冲区对象的工作方式正是如此。 EBO是一个缓冲区,就像一个顶点缓冲区对象一样,它存储 OpenGL 用来决定要绘制哪些顶点的索引。这种所谓的索引绘制(Indexed Drawing)正是我们问题的解决方案。首先,我们先要定义(不重复的)顶点,和绘制出矩形所需的索引:
代码展示:
#include "turboopenglwidget.h"
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 // 第二个三角形
};
const char *vertexShaderSource =
"#version 330 core \n"
"layout (location = 0) in vec3 aPos;\n"
"\n"
"void main()\n"
"{\n"
"\tgl_Position = vec4(aPos, 1.0);\n"
"}";
const char *fragShaderSource =
"#version 330 core\n"
"out vec4 FragColor;\n"
"\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"} ";
// 创建VAO和VBO对象,并且赋予ID
unsigned int VBO, VAO;
unsigned int EBO;
unsigned int shaderProgram;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
}
void TurboOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
// 绑定VAO和VBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 为当前绑定到target的缓冲区对象创建一个新的数据存储
// 如果data不是NULL, 则使用来自此指针的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 告知显卡如何解析缓冲里面的属性值
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);
// 开启VAO管理的第一个属性的值
glEnableVertexAttribArray(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 释放VAO和VBO对象
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// 创建顶点着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader ,1 ,&vertexShaderSource, NULL);
glCompileShader(vertexShader);
// 创建顶片段着色器
unsigned int fragShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragShader ,1 ,&fragShaderSource, NULL);
glCompileShader(fragShader);
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragShader);
glLinkProgram(shaderProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragShader);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
void TurboOpenGLWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 6);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
void TurboOpenGLWidget::resizeGL(int w, int h)
{
}
代码示例:
turboopenglwidget.h
#ifndef QTOPENGL_TURBOOPENGLWIDGET_H
#define QTOPENGL_TURBOOPENGLWIDGET_H
#include
#include
#include
class TurboOpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{
Q_OBJECT
public:
enum Shape
{
None,
Rect,
Circle,
Triangle,
};
explicit TurboOpenGLWidget(QWidget *parent = 0);
~TurboOpenGLWidget() override;
void drawShape(Shape shape);
void setWireFrame(bool mode);
protected:
void initializeGL() override;
void paintGL() override;
void resizeGL(int w, int h) override;
private:
Shape shape_;
QOpenGLShaderProgram shader_program_;
};
#endif //QTOPENGL_TURBOOPENGLWIDGET_H
turboopenglwidget.cpp
#include
#include "turboopenglwidget.h"
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 // 第二个三角形
};
// 创建VAO和VBO对象,并且赋予ID
unsigned int VBO, VAO;
unsigned int EBO;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
}
TurboOpenGLWidget::~TurboOpenGLWidget()
{
makeCurrent();
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glDeleteVertexArrays(1, &VAO);
doneCurrent();
}
void TurboOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
// 绑定VAO和VBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 为当前绑定到target的缓冲区对象创建一个新的数据存储
// 如果data不是NULL, 则使用来自此指针的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 告知显卡如何解析缓冲里面的属性值
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);
// 开启VAO管理的第一个属性的值
glEnableVertexAttribArray(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 释放VAO和VBO对象
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
bool success = false;
shader_program_.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/resources/shader.vert");
shader_program_.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/resources/shader.frag");
success = shader_program_.link();
if(!success)
{
std::cout << "shader is failed" << std::endl;
}
// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
void TurboOpenGLWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
shader_program_.bind();
glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 6);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
switch(shape_)
{
case Rect:
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
break;
}
update();
}
void TurboOpenGLWidget::resizeGL(int w, int h)
{
}
void TurboOpenGLWidget::drawShape(TurboOpenGLWidget::Shape shape)
{
shape_ = shape;
}
void TurboOpenGLWidget::setWireFrame(bool mode)
{
makeCurrent();
if(mode)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
else
{
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
}
mainwidow.h
#ifndef QTOPENGL_MAINWINDOW_H
#define QTOPENGL_MAINWINDOW_H
#include
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
protected slots:
void drawRect();
void clearPic();
void lineModel(const bool &mode);
private:
Ui::MainWindow *ui;
QToolBar *tool_bar_{nullptr};
};
#endif //QTOPENGL_MAINWINDOW_H
mainwidow.cpp
#include "mainwindow.h"
#include "ui_MainWindow.h"
#include
#include
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
setCentralWidget(ui->openGLWidget);
tool_bar_ = new QToolBar(this);
auto *action = new QAction(tr("绘制矩形"), this);
auto *action2 = new QAction(tr("清空图形"), this);
auto *action3 = new QAction(tr("线框模式"), this);
action3->setCheckable(true);
connect(action, &QAction::triggered, this, &MainWindow::drawRect);
connect(action2, &QAction::triggered, this, &MainWindow::clearPic);
connect(action3, &QAction::triggered, this, &MainWindow::lineModel);
tool_bar_->addAction(action);
tool_bar_->addAction(action2);
tool_bar_->addAction(action3);
addToolBar(tool_bar_);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::drawRect()
{
ui->openGLWidget->drawShape(TurboOpenGLWidget::Rect);
}
void MainWindow::clearPic()
{
ui->openGLWidget->drawShape(TurboOpenGLWidget::None);
}
void MainWindow::lineModel(const bool &mode)
{
ui->openGLWidget->setWireFrame(mode);
}
OpenGL Shading Languaage
一个典型的shader程序结构:
#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
int main()
{
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
我们能声明的顶点数量是有限的,可以通过下面的代码获取:
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
OpenGL确保至少有16个包含4分量的顶点属性可用,但是有些硬件可能会允许更多的顶点属性。
类型:
GLSL中包含C等其他语言大部分默认的基础数据类型
int float double uint bool
GLSL也有两种容器类型
类型 | 含义 |
---|---|
vecn |
包含n 个float分量的默认向量 |
bvecn |
包含n 个bool分量的向量 |
ivecn |
包含n 个int分量的向量 |
uvecn |
包含n 个unsigned int分量的向量 |
dvecn |
包含n 个double分量的向量 |
向量这一数据类型也允许一些有趣而灵活的分量选择方式,叫做重组(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);
顶点着色器
#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;
}
顶点着色器接收的是一种特殊形式的输入,否则就会效率低下
从顶点数据中直接接收输入。为了定义顶点数据该如何管理,我们使用location这一元数据(metadata)指定输入变量,这样我们才可以在CPU上配置顶点属性。例如: layout(location = 0)。 layout这个标识,使得我们能把它链接到顶点数据。
可以忽略layout( location = 0) 标识符,通过在OPenGL代码中使用glGetAttrribLocation查询属性位置(Location),或者是glBindAttribLocation属性位置值(Location),但是推荐在着色器中设置他们,这样会更容易理解而且节省你和(OpenGL)的工作量
#include
#include "turboopenglwidget.h"
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 // 第二个三角形
};
// 创建VAO和VBO对象,并且赋予ID
unsigned int VBO, VAO;
unsigned int EBO;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
}
TurboOpenGLWidget::~TurboOpenGLWidget()
{
makeCurrent();
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glDeleteVertexArrays(1, &VAO);
doneCurrent();
}
void TurboOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
// 绑定VAO和VBO对象
glBindVertexArray(VAO);
shader_program_.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/resources/shader.vert");
shader_program_.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/resources/shader.frag");
bool success = false;
success = shader_program_.link();
if(!success)
{
std::cout << "shader is failed" << std::endl;
}
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 为当前绑定到target的缓冲区对象创建一个新的数据存储
// 如果data不是NULL, 则使用来自此指针的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 告知显卡如何解析缓冲里面的属性值
int location = shader_program_.attributeLocation("aPos");
glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);
// 开启VAO管理的第一个属性的值
glEnableVertexAttribArray(location);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 释放VAO和VBO对象
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void TurboOpenGLWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
shader_program_.bind();
glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 6);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
switch(shape_)
{
case Rect:
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
break;
}
update();
}
void TurboOpenGLWidget::resizeGL(int w, int h)
{
}
void TurboOpenGLWidget::drawShape(TurboOpenGLWidget::Shape shape)
{
shape_ = shape;
}
void TurboOpenGLWidget::setWireFrame(bool mode)
{
makeCurrent();
if(mode)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
else
{
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
}
另一种从CPU的应用,向GPU中的着色器发送数据的方式
uniform是全局的,可以被任意的着色器程序在任一阶段访问
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量
void main()
{
FragColor = ourColor;
}
如果声明了一个uniform却没有用过,编译器会默认移除这个变,导致最后编译出的版本中并不会包含它,这可能导致几个非常麻烦的错误,切记
这次我们不去给像素单独传递一个颜色,而是让他随着时间改变颜色。
OpenGL在其核心是一个C库,所以他不支持类型重载,在函数参数类型不同时候就要为其定义新的函数,glUniform是一个典型的例子。这个函数有特定的后缀,用来标识设定的uniform的类型。可能的后缀有:
后缀 | 含义 |
---|---|
f |
函数需要一个float作为它的值 |
i |
函数需要一个int作为它的值 |
ui |
函数需要一个unsigned int作为它的值 |
3f |
函数需要3个float作为它的值 |
fv |
函数需要一个float向量/数组作为它的值 |
QT 为我们封装了这个函数,因此我们可以不用太过关注该函数的详细内容,但是你要是用原生的OpenGL的话,需要关注该函数。
当我们需要给图形赋予真实的颜色的时候,不大可能使用前面的方法为每一个顶点指定第一个颜色,通常我们会采用纹理贴图。
每个顶点关联一个纹理坐标(Texture Coordinate),之后在图形的其他片段上进行片段插值
我们只需要告诉OpenGL如何对纹理采样即可
顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 1) in vec2 aTexCord;
out vec3 ourColor; // 向片段着色器输出一个颜色
out vec2 texCord; // 向片段着色器输出一个颜色
void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor; // 将ourColor设置为我们从顶点数据那里得到的输入颜色
texCord = aTexCord;
}
片段着色器
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 texCord;
uniform sampler2D texture0;
void main()
{
FragColor = texture(texture0, texCord);
}
对应的显示代码:
#include
#include "turboopenglwidget.h"
float vertices[] = {
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上角
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f // 左上角
};
unsigned int indices[] = {
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
// 创建VAO和VBO对象,并且赋予ID
unsigned int VBO, VAO;
unsigned int EBO;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
connect(&timer, &QTimer::timeout, this, &TurboOpenGLWidget::timeout);
// timer.start(100);
}
TurboOpenGLWidget::~TurboOpenGLWidget()
{
if(!isValid()) return;
makeCurrent();
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glDeleteVertexArrays(1, &VAO);
doneCurrent();
}
void TurboOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
// 绑定VAO和VBO对象
glBindVertexArray(VAO);
shader_program_.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/resources/shader.vert");
shader_program_.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/resources/shader.frag");
bool success = false;
success = shader_program_.link();
if(!success)
{
std::cout << "shader is failed" << std::endl;
}
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 为当前绑定到target的缓冲区对象创建一个新的数据存储
// 如果data不是NULL, 则使用来自此指针的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 告知显卡如何解析缓冲里面的属性值
int location = shader_program_.attributeLocation("aPos");
glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0);
// 开启VAO管理的第一个属性的值
glEnableVertexAttribArray(location);
int location2 = shader_program_.attributeLocation("aColor");
glVertexAttribPointer(location2, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float)));
// 开启VAO管理的第一个属性的值
glEnableVertexAttribArray(location2);
int location3 = shader_program_.attributeLocation("aTexCord");
glVertexAttribPointer(location3, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float)));
// 开启VAO管理的第一个属性的值
glEnableVertexAttribArray(location3);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
texture_wall_ = new QOpenGLTexture(QImage(":/resources/wall.jpg"));
// 释放VAO和VBO对象
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void TurboOpenGLWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
shader_program_.bind();
glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 6);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
switch(shape_)
{
case Rect:
texture_wall_->bind();
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
break;
}
update();
}
void TurboOpenGLWidget::resizeGL(int w, int h)
{
}
void TurboOpenGLWidget::drawShape(TurboOpenGLWidget::Shape shape)
{
shape_ = shape;
}
void TurboOpenGLWidget::setWireFrame(bool mode)
{
makeCurrent();
if(mode)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
else
{
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
}
#include
void TurboOpenGLWidget::timeout()
{
if(shape_)
{
return;
}
makeCurrent();
int time = QTime::currentTime().second();
float green = (sin(time) / 2.0f) + 0.5f;
shader_program_.setUniformValue("ourColor", 0.0f, green, 0.0f, 1.0f);
}
OpenGL保证至少16个纹理单元,也就是说你可以激活从GL_TEXTURE0到GL_TEXTURE15。他们都是按照顺序定义的, GL_TEXTURE0+8可以获得GL_TEXTURE8
以下是QT主要代码,在gitee项目中查看完整代码。
texture_wall_ = new QOpenGLTexture(QImage(":/resources/wall.jpg").mirrored());
texture_le_ = new QOpenGLTexture(QImage(":/resources/awesomeface.png").mirrored());
shader_program_.bind();
shader_program_.setUniformValue("textureWall", 0);
shader_program_.setUniformValue("textureSmile", 1);
环绕方式 | 描述 |
---|---|
GL_REPEAT | 对纹理的默认行为。重复纹理图像。 |
GL_MIRRORED_REPEAT | 和GL_REPEAT一样,但每次重复图片是镜像放置的。 |
GL_CLAMP_TO_EDGE | 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。 |
GL_CLAMP_TO_BORDER | 超出的坐标为用户指定的边缘颜色。 |
相关代码请到gitee查看,这里不复制
纹理坐标不依赖于分辨率,OpenGL需要知道怎么将纹理像素映射到纹理坐标;
可以想象你打开一张图片,不断放大,会发现它是由无数像素点组成的,这个点就是纹理像素
大图片贴小面片时:纹理的精度高,相邻纹理像素往往色差不打,无需融合,直接就近选取即可。
主要函数:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
相关代码请到gitee查看,这里不复制
简单来说就是一系列的纹理图像,根据观察者与物体的距离,参考临界值,选择最适合物体的距离的那个纹理
OpenGL有一个glGenerateMipmaps函数,可以生产多级渐远纹理
过滤方式 | 描述 |
---|---|
GL_NEAREST_MIPMAP_NEAREST | 使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样 |
GL_LINEAR_MIPMAP_NEAREST | 使用最邻近的多级渐远纹理级别,并使用线性插值进行采样 |
GL_NEAREST_MIPMAP_LINEAR | 在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样 |
GL_LINEAR_MIPMAP_LINEAR | 在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样 |
主要函数:
texture_small_->generateMipMaps();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
相关代码请到gitee查看,这里不复制