大家可以去B站看课程的视频支持一下作者哈:
OpenGL,Qt实现:1入门篇(已更完)_哔哩哔哩_bilibili课程相关源码、PPT、安装包,完整课程合集(1:入门篇;2:基础光照;3:模型加载;4:高级OpenGL;5:高级光照 ;程序员的数学3:线性代数):https://ke.qq.com/course/package/40726?flowToken=1041265https://www.bilibili.com/video/BV1UL411W71w
准备工作
1、关于OpenGL
OpenGL是一种规范,具体的API由各个厂家自行实现的。
2、GPU渲染管线(流水线)
3、状态
个人理解为参与对输入数据的处理的属性和条件。
上面的图片中,输入三个点,不同的状态参与对输入数据的处理会输出不同的内容。
4、状态机
储存了状态的集合。
OpenGL本身是一个巨大的状态机(包含了很多状态的属性)。
OpenGL的状态一般称为上下文(Context)。
OpenGL的函数可以分为状态设置函数和状态应用函数,学习的时候可以注意这一点。
5、OpenGL对象
一个对象指的是一些选项(状态)的集合,是OpenGL状态的一个子集。
OpenGL是一个大的状态机,包含了大量状态属性,从中取出一部分构成一个对象。
例如从OpenGL的状态中取出一些窗口大小、窗口名称、窗口颜色等选项集合成一个结构体,就构成一个窗口对象。
6、对象的作用
OpenGL的状态处于变化之中,如果每次变化都需要重新配置状态则不太行,可以使用对象记录OpenGL的状态。
总之:对象就是用来记录状态的。
理解下面的代码:
// 创建对象
//uint objectId = 0;
GLuint objectId = 0;
glGenObject(1, &objectId); //给对象一个编号。
glBindObject(GL_WINDOW_TARGET, objectId);// 绑定对象至上下文,objectId 记录 GL_WINDOW_TARGET 的状态
// 设置 GL_WINDOW_TARGET 对象的一些状态,GL_WINDOW_TARGET 状态的改变被 objectId 记录下来了
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
glBindObject(GL_WINDOW_TARGET, 0); //取消 GL_WINDOW_TARGET 和 objectId 的绑定
......//GL_WINDOW_TARGET的其他操作
//一旦 GL_WINDOW_TARGET 重新绑定 objectId,objectId 记录的 GL_WINDOW_TARGET 选项就会重新生效
创建OpenGL窗口
1、QOpenGLWidget::makeCurrent()
如上所述,OpenGL是一个大的状态机,它的当前状态只有一个,在?paintGL()、resizeGL()、initializeGL() 之外的函数中调用标准OpenGL API函数,则必须首先调用此函数以获取OpenGL状态。
2、标准化设备坐标
OpenGL为了让坐标运算不受显示器分辨率的影响,将三维坐标标准化到了[-1,1]之间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在屏幕上。
标准化设备坐标看起来就像是下面这样:
一个标准化坐标数组:
3、顶点着色器
- 顶点着色器会在GPU的内存(显存)中储存顶点数据。
- 顶点数据通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理(顶点数据实际是储存到VBO中的)。注意:顶点缓冲对象是对象,如上面所述,对象的作用是记录状态。
- 顶点缓冲对象的缓冲类型是 GL_ARRAY_BUFFER。使用缓冲区是为了减少将内存中的数据传到显存需要的开销。将数据先放到内存中的缓冲区,数据满了再一次性传给显存。
- 将顶点缓冲对象绑定为?GL_ARRAY_BUFFER,即可将数据传到显存。
4、顶点数组对象(Vertex Array Objects, VAO)
前面说了将数据传到显存。VAO的作用是配置OpenGL如何解释数据(记录数据是怎么定义的),有了VAO以后OpenGL才知道如何使用数据。
注意:这也是对象,对象的作用是记录状态。牢记。
理解以下代码:
//1、创建VBO和VAO对象,并赋予ID
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
//2、绑定VBO和VAO对象
glBindVertexArray(VAO);//使OpenGL知道这个uint类型的对象表示一个VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//为当前绑定到GL_ARRAY_BUFFER的缓冲区对象创建一个新的数据存储,使用参数三的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//告知显卡如何解析缓冲里的属性值:
//第0个数据:有3个值;float类型;不需要标准化;步长;偏移量是0(因为是第一个数据,所以没有偏移量)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//开启VAO管理的第一个属性值
glEnableVertexAttribArray(0);
//VAO、VBO已经记录了足够的信息了,解除绑定
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
关于OpenGL函数可查询:Khronos OpenGL? and OpenGL? ES Reference Pages - The Khronos Group Inchttps://www.khronos.org/registry/OpenGL-Refpages/
使用上面的代码画出一个三角形:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
class Widget : public QOpenGLWidget,QOpenGLFunctions_4_5_Core
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
protected:
virtual void initializeGL();
virtual void paintGL();
};
#endif // WIDGET_H
#include "widget.h"
Widget::Widget(QWidget *parent)
: QOpenGLWidget(parent)
{
}
Widget::~Widget()
{
}
unsigned int VBO, VAO;
float vertices[] =
{
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
void Widget::initializeGL()
{
initializeOpenGLFunctions();
//1、创建VBO和VAO对象,并赋予ID
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
//2、绑定VBO和VAO对象
glBindVertexArray(VAO);//使OpenGL知道这个uint类型的对象表示一个VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//为当前绑定到GL_ARRAY_BUFFER的缓冲区对象创建一个新的数据存储,使用参数三的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//告知显卡如何解析缓冲里的属性值:
//第0个数据:有3个值;float类型;不需要标准化;步长;偏移量是0(因为是第一个数据,所以没有偏移量)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//开启VAO管理的第一个属性值
glEnableVertexAttribArray(0);
//VAO、VBO已经记录了足够的信息了,解除绑定
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void Widget::paintGL()
{
glClearColor(0.2f,0.3f,0.3f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(VAO);//绑定VAO以便知道该如何使用数据。
glDrawArrays(GL_TRIANGLES,0,3);
}
这里没有定义着色器而是使用默认的着色器绘制一个三角形。
使用OpenGL原生的方式编译链接着色器
Qt 对着色器编译是有封装的,先不管它,使用OpenGL原生的方式编译链接着色器看看。
#include "widget.h"
Widget::Widget(QWidget *parent)
: QOpenGLWidget(parent)
{
}
Widget::~Widget()
{
}
unsigned int VBO, VAO;
unsigned int shaderProgram;
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"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
void Widget::initializeGL()
{
initializeOpenGLFunctions();
//1、创建VBO和VAO对象,并赋予ID
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
//2、绑定VBO和VAO对象
glBindVertexArray(VAO);//使OpenGL知道这个uint类型的对象表示一个VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//为当前绑定到GL_ARRAY_BUFFER的缓冲区对象创建一个新的数据存储,使用参数三的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//告知显卡如何解析缓冲里的属性值:
//第0个数据:有3个值;float类型;不需要标准化;步长;偏移量是0(因为是第一个数据,所以没有偏移量)
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);//创建顶点着色器对象 GL_VERTEX_SHADER表示顶点着色器
glShaderSource(vertexShader, 1, &vertexShaderSource,nullptr);//顶点着色器绑定源码
glCompileShader(vertexShader);//编译顶点着色器
int success;
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);//检查编译错误
if (!success)
{
char infoLog[512];
glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
qDebug()<< "编译顶点着色器出错,错误信息:" << infoLog ;
}
//片段着色器
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);//创建片段着色器 GL_FRAGMENT_SHADER表示片段着色器
glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
char infoLog[512];
glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
qDebug()<< "编译片段着色器出错,错误信息:" << infoLog ;
}
//链接两个编译好的着色器
shaderProgram = glCreateProgram();//创建着色器程序对象
glAttachShader(shaderProgram, vertexShader);//添加要编译的程序
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);//链接着色器
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); //检测链接错误
if (!success)
{
char infoLog[512];
glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
qDebug()<< "链接着色器出错,错误信息:" << infoLog;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
}
void Widget::paintGL()
{
glClearColor(0.2f,0.3f,0.3f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);//使用此着色器,没有这行则使用默认的着色器
glBindVertexArray(VAO);//绑定VAO以便知道该如何使用数据。
glDrawArrays(GL_TRIANGLES,0,3);
}
|