OpenGL万字超详解笔记(适合新手小白)
参考资料
20分钟让你了解OpenGL——OpenGL全流程详细解读
【OpenGL】基本API的详解及参考
OpenGL初学者入门——学习指南【共 9 篇文章】
LearnOpenlcn
OpenGL状态机
深度测试(Depth Test)概念
[OpenGL]VBO,VAO和EBO详解
基本概念
OpenGL是什么,是一个跨编程语言、跨平台的专业图形程序接口,是一个功能强大,调用方便的底层图形库。它将计算机的资源抽象称为一个个OpenGL的对象,对这些资源的操作抽象为一个个的OpenGL指令。
OpenGL的上下文(Context)
在开始之前首先我们要了解OpenGL的上下文(Context)
在任何应用程序调用OpenGL的指令之前都需要创建一个OpenGL的上下文,这个上下文是一个非常庞大的状态机,保存着OpenGL各种状态,是能执行OpenGL指令的基础。
上下文(Context)是线程私有的,在线程中绘制的时候,需要为每一个线程指定一个Current Context的,多个线程不能指向同一个Context。
OpenGL直到创建了OpenGL Context后才会存在。
如果你在函数a中初始化了一个OpenGL上下文,并在函数b中初始化了另一个OpenGL上下文,那么它们是不同的上下文。
当函数a调用函数b时,它们的状态机是相互独立的,因为它们操作的是不同的上下文。因此,它们不在同一个OpenGL上下文中。
OpenGL状态机
对于初学者,比如说我,对状态机这个概念是很模糊的,因此我们需要先了解什么是状态机。
首先我们要了解状态(status) 的概念,状态就是系统当前所处的状态,比如我们正在打游戏,那么此时我们的状态就是“打游戏”,再比如我们正在吃饭,那么此时我们的状态就是“吃饭”。
那么状态机就是一种能够记录和保存系统状态的机制,它能够记录和保存系统当前所处的状态,并且能够根据需要切换到不同的状态。
我们常见的状态机是有限状态机,它保存着有限的状态,比如一个灯的开关,一般的只存在“开启”和“关闭”两个状态,因此它是一个二元状态机。
在LearnOpenGL中是这样解释的:
OpenGL自身是一个巨大的状态机(StateMachine):一系列的变量描述OpenGL此刻应当如何运行。OpenGL的状态通常被称为OpenGL上下文(Context)。我们通常使用如下途径去更改OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。
假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。
当使用OpenGL的时候,我们会遇到一些状态设置函(State-changing Function),这类函数将会改变上下文。以及状态使用函数(State-using Function),这类函数会根据当前OpenGL的状态执行一些操作。只要你记住OpenGL本质上是个大状态机,就能更容易理解它的大部分特性。
一个基础OpenGL程序的实现
初始化阶段
在我的程序实现中,将其定义在了一个函数initializeGL中,这个函数主要实现以下步骤
- 激活glfunction
initializeOpenGLFunctions();
- 设置清屏颜色
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
- 开启深度测试(当只渲染一张二维图片时,没有深度信息,可以不开启)
glEnable(GL_DEPTH_TEST);
- 设置视口
glViewport(0, 0, width(), height());
这里要注意一下,视口大小不一定等于窗口大小,这个可能和各个系统显示的dpi有关系,此时可以用这个代码来获取视口的真实大小再与获取到的窗口大小做比较
GLint defaultViewPort [4];
glGetIntegerv(GL_VIEWPORT, defaultViewPort);
在Qt窗体中,可以通过这行代码来获取屏幕dpi
auto dpi = (float)this->devicePixelRatioF();
- 连接着色器
在固定渲染管线时代,这一步并不是必须的。而是由内置的一段包含了光照、坐标变换、裁剪等等诸多功能的固定shader程序来完成。
OpenGL和其他主流的图形API早在好几年前,就全面的将固定渲染管线架构变为了可编程渲染管线。因此,OpenGL在实际调用绘制函数之前,还需要指定一个由shader编译成的着色器程序。
常见的着色器主要有顶点着色器(VertexShader),片段着色器(FragmentShader)/像素着色器(PixelShader),几何着色器(GeometryShader),曲面细分着色器(TessellationShader)。
OpenGL在处理shader时,和其他编译器一样。通过编译、链接等步骤,生成了着色器程序(glProgram),着色器程序同时包含了顶点着色器和片段着色器的运算逻辑。在OpenGL进行绘制的时候,首先由顶点着色器对传入的顶点数据进行运算。再通过图元装配,将顶点转换为图元。然后进行光栅化,将图元这种矢量图形,转换为栅格化数据。最后,将栅格化数据传入片段着色器中进行运算。片段着色器会对栅格化数据中的每一个像素进行运算,并决定像素的颜色,也可以在这个阶段将某些像素丢弃。
在此,我们定义两个简单的着色器
顶点着色器(vertexShader):
#version 100
precision mediump float;
attribute vec2 position;
attribute vec2 texCoord;
varying vec2 fragTexCoord;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
fragTexCoord = texCoord;
}
顶点着色器是一段 GPU 程序,它对每个顶点执行一次,当渲染三角形时,会将三角形的每一个顶点数据作为输入参数去调用一次顶点着色器,顶点着色器的输出作为输入参数来调用片段着色器。
片段/片源着色器(fragmentShader):
#version 100
precision mediump float;
varying vec2 fragTexCoord;
uniform sampler2D textureSampler;
void main() {
vec4 texColor = texture2D(textureSampler, fragTexCoord);
gl_FragColor = texColor;
}
什么是片段(fragement)?片段可以理解为像素的内存模型,每一个像素对应一个片段,片段着色器针对每个像素执行一次,如果你的窗口分辨率为 800*600,那么每一帧将执行 800 * 600 次片段着色器
其中,我们要搞清楚每个参数的定义
首先是#version 100,声明的是版本号,一般的如果不声明版本将无法使用改着色器至着色器程序。
然后是precision mediump float,声明的是浮点数精度,这里使用的是中等精度。
还有attribute vec2 position和attribute vec2 texCoord 声明的是顶点属性,vec2表示的是2维向量,position和texCoord是属性名,分别表示顶点位置和纹理坐标,
attribute(属性)变量:
- attribute变量只能在顶点着色器中使用。
- 通常用来表示一些顶点的数据,例如顶点坐标、法线、纹理坐标、顶点颜色等。
- 在应用程序中,我们使用glBindAttribLocation()函数来绑定每个attribute变量的位置,并使用glVertexAttribPointer()函数为每个attribute变量赋值。
然后是varying vec2 fragTexCoord,声明的是顶点属性,vec2表示的是2维向量,fragTexCoord是属性名,表示的是片段着色器中的纹理坐标,varying是顶点着色器中的一个关键字,用于声明顶点属性。它的作用是将顶点属性传递给片段着色器。当顶点着色器处理一个顶点时,它将使用varying关键字声明的属性。这些属性将在片段着色器中使用,以便计算最终的渲染输出。
而gl_Position是OpenGL内置的顶点属性,表示的是顶点位置,它可以通过一系列运算实现对顶点位置的变换。比如在有mvp矩阵(或仅有tsp矩阵时)
而在片源着色器中,fragTexCoord接受的是来自顶点着色器的数据,uniform sampler2D textureSampler定义了一个纹理采样器,它接受一个纹理对象作为参数,并返回纹理的颜色值。
最后在主函数中,通过vec4 texColor = texture2D(textureSampler, fragTexCoord);gl_FragColor = texColor;
将纹理颜色值赋给gl_FragColor,从而实现纹理的渲染。
这时就会有人要问了,为森么不使用更新的glsl版本呢?比如说330,410,430等。
这就不得不提到硬件适配的问题了,像是OpenGL Mesh Shader需要图灵架构的GPU,支持的处理器如下图:
而OpenGL 2.0支持的处理器更多,支持的显卡也更多,因此OpenGL 2.0是兼容性最好的版本。
接下来就是创建着色器链接程序了
```c++
// 创建着色器程序
GLuint createShaderProgram(const char *vertexShaderSource, const char *fragmentShaderSource)
{
// 编译顶点着色器
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
glCompileShader(vertexShader);
checkShaderCompileErrors(vertexShader, "VERTEX");
// 编译片段着色器
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
glCompileShader(fragmentShader);
checkShaderCompileErrors(fragmentShader, "FRAGMENT");
// 创建着色器程序
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
checkProgramLinkErrors(shaderProgram);
// 删除着色器,因为它们已经链接到程序中不再需要
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return shaderProgram;
}
// 辅助函数:检查着色器编译错误
void checkShaderCompileErrors(GLuint shader, const char *type)
{
GLint success;
GLchar infoLog[512];
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
std::cerr << "ERROR::SHADER::" << type << "::COMPILATION_FAILED\n" << infoLog << std::endl;
}
}
// 辅助函数:检查着色器程序链接错误
void checkProgramLinkErrors(GLuint program)
{
GLint success;
GLchar infoLog[512];
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(program, 512, nullptr, infoLog);
std::cerr << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
}
他们的作用是绑定顶点着色器(fragmentShader)和片段(片源)着色器(fragmentShader)。
到这里,初始化的步骤就基本结束了
然后就是设置纹理阶段
接下来是设置图片阶段,首先要确认色彩空间是否和gl相对应,我这边是先做了一个转换为RGBA32
接着是创建纹理对象
glGenTextures(1, &m_textureID);
然后是绑定纹理对象
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_textureID);
然后是设置纹理环绕方式和过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
接下来是设置纹理数据
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_image.width, m_image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_image.data);
创建并绑定 VBO和VBO
glGenBuffers(1, &m_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
设置顶点属性指针
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), nullptr);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void *)(2 * sizeof(GLfloat)));
完成设置后,解绑纹理
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
这里我们会看到两个变量:vao和vbo,还有一个是我们没用到的ebo,这些又分别是什么呢
VAO和VBO
VAO和VBO是OpenGL中的两个概念,它们的作用分别是:
- VAO:Vertex Array Object,顶点数组对象,它存储了顶点属性指针和顶点缓冲对象的信息,可以理解为一个模型在内存中的表现形式,VAO 可以理解成一套解析规则或者一个状态,它记录了 glVertexAttribPointer 这个方法的调用结果,记录了每个顶点属性的指针,绘制时可以根据 VAO 中的指针去取顶点数据,而不需要每次绘制都重新进行VBO的解析。
- VBO:Vertex Buffer Object,顶点缓冲对象,它存储了顶点数据,可以理解为一个模型在内存中的表现形式。
EBO
- EBO:Element Buffer Object,元素缓冲对象,它存储了顶点索引数据,可以理解为一个模型在内存中的表现形式。
这么讲还是太干巴巴了,完全体现不出VAO和VBO的作用,我们还是通过一个例子来理解一下:
比如我们要绘制一个三角形
当我们进行如下绑定操作时
// 申请缓冲区
GLuint VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// 绑定VAO,表示在这之后针对 VBO 的解析都会记录在 VAO 中
glBindVertexArray(VAO);
// 提交数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 使用Buffer中的数据在 VAO 生成 0 号顶点属性的指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
// 启用 0 号顶点属性
glEnableVertexAttribArray(0);
// 解绑VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 解绑VAO
glBindVertexArray(0);
在每次调用时只需要再调用vao而不需要再解析vbo
while (!glfwWindowShouldClose(window))
{
// 处理事件
glfwPollEvents();
// 清屏
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 指定着色器程序
glUseProgram(shaderProgram);
// 绑定VAO
glBindVertexArray(VAO);
// 绘制指令
glDrawArrays(GL_TRIANGLES, 0, 3);
// 解绑 VAO
glBindVertexArray(0);
// 双缓冲交换
glfwSwapBuffers(window);
}
其中可以看到,在循环中我们没再使用过VAO信息
而一个vao可以对应多个vbo数据,比如在有两组数据(顶点数据和颜色数据的时候)
// 申请缓冲区
GLuint VBO, VAO, VBO2;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &VBO2);
// 绑定VAO
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(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, VBO2);
glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
// 解绑VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 解绑VAO
glBindVertexArray(0);
绘制代码同样的不需要使用vbo
在绘制两个三角形的情况下,用vbo的情就需要定义六个顶点(每个三角形三个),传参也是用在调用glDrawArrays方法渲染6个顶点。
如果使用vao就可以保留三个顶点,分别使用两个vao保存两个三角形的解析数据进行绘制
但无论是上述哪种方法都会都有顶点数据的冗余,包括位置和颜色数据的冗余,6个顶点我们需要使用6个float3来表示位置和颜色,当需要渲染的顶点数时会造成更大的冗余,此时我们就需要使用更加优化的ebo来对我们的vao进行管理
EBO(Element Buffer Object)是索引缓冲对象,它解决上面两个三角形数据冗余问题的思路是这样的:只保存4个顶点数据,引入EBO来存储两个三角形对于顶点数据的索引。
// 四个顶点,有两个复用
GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f,
0.0f, -1.0f, 0.0f,
};
// 四个颜色,两个复用
GLfloat colors[] = {
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f
};
GLuint indices[] = {
0, 1, 2, // 第一个三角形使用的顶点下标
0, 1, 3 // 第二个三角形使用的顶点下标
};
简单来说就是,VBO 中存放的是去重后的顶点数据,当顶点复用数目较多时可以节省很多存储空间,另外单独开辟一个EBO缓冲区来存储每个顶点的实际数据在 VBO 中对应的下标值,在提交时将EBO信息也提交,计算结果得到 VAO,绘制时绑定 VAO 来访问顶点数据。
比如
GLuint colorIndices[] = {
0, 1, 2,
1, 2, 3
};
那只要在下面为 VAO 绑定属性时传递正确的数据就可以了
int draw_trangle()
{
init_opengl();
GLuint shaderProgram = compile_shader(vertexShaderSource, fragmentShaderSource);
// 四个顶点,有两个复用
GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f,
0.0f, -1.0f, 0.0f,
};
// 四个颜色,两个复用
GLfloat colors[] = {
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f
};
GLuint indices[] = {
0, 1, 2, // 第一个三角形使用的顶点下标
0, 1, 3 // 第二个三角形使用的顶点下标
};
// 缓冲区生成
GLuint VBO, ColorVBO, VAO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &ColorVBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
// 解析并提交位置属性
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// 解析并提交颜色属性
glBindBuffer(GL_ARRAY_BUFFER, ColorVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
// 解绑缓冲区
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 指定着色器
glUseProgram(shaderProgram);
// 绑定 VAO
glBindVertexArray(VAO);
// 根据索引绘制
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glfwSwapBuffers(window);
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glfwTerminate();
return 0;
}
此时需要特别注意的几点:
- 索引数组的类型必须用 GLuint,不要误用 GLfloat,否则将无法得到你想要的绘制结果
注意颜色数据的尺寸 - 绘制的 API 发生了变化,不再是 glDrawArrays 而是 glDrawElements,需要注意传参的顺序。
绘制阶段
在QOpenGLFunction情境下,绘制的函数一般是painGL()由update()触发
首先,我们要使用着色器程序
glUseProgram(shaderProgram);
然后,我们绑定VAO
glBindVertexArray(VAO);
在此之前,如果有不同的矩阵,比如mvp或者tsp,还需要向shader传入我们的矩阵
然后就需要根据不同的绘制方式来绘制了,比如绘制三角形,我们使用glDrawArrays来绘制,传入参数是GL_TRIANGLES,以及顶点的数量
glDrawArrays(GL_TRIANGLES, 0, 3);
此时就得了解一下不同的openGL的api的区别了
绘制方式
glDrawArrays
glDrawArrays 函数用于指定绘制的方式,它接受三个参数:
- 第一个参数是绘制的方式,比如 GL_TRIANGLES 表示绘制三角形,GL_TRIANGLE_STRIP 表示绘制三角形带,GL_TRIANGLE_FAN 表示绘制三角形扇。
- 第二个参数是绘制的起始顶点索引,从0开始。
- 第三个参数是绘制的顶点数量。
glDrawElements
glDrawElements 函数用于指定绘制的方式,它接受五个参数:
- 第一个参数是绘制的方式,比如 GL_TRIANGLES 表示绘制三角形,GL_TRIANGLE_STRIP 表示绘制三角形带,GL_TRIANGLE_FAN 表示绘制三角形扇。
- 第二个参数是绘制的顶点数量。
- 第三个参数是索引的类型,比如 GL_UNSIGNED_BYTE 表示索引是 8 位无符号整数,GL_UNSIGNED_SHORT 表示索引是 16 位无符号整数。
- 第四个参数是索引的数组,即顶点的索引数组。
- 第五个参数是索引的偏移量,即索引数组的起始位置。
参数绘制方式详解
- GL_POINTS 绘制一系列点。
- GL_LINES 绘制一系列线段。
- GL_LINE_STRIP 绘制一个线条,线条上的所有点都是相连的。
- GL_LINE_LOOP 绘制一个线条,线条上的所有点都是相连的。
- GL_TRIANGLES 绘制一系列三角形。
GL_TRIANGLES是最常见的绘制模式之一。
每三个顶点构成一个独立的三角形。
顶点之间没有连接关系,因此需要提供足够的顶点数据来描述所有的三角形。
- GL_TRIANGLE_STRIP 绘制一个三角带,三角带上的所有点都是相连的。
GL_TRIANGLE_STRIP绘制一个三角带。
三角带上的相邻三个顶点构成一个三角形。
从第二个顶点开始,每个新的顶点都与前两个顶点构成一个新的三角形。
这种连接方式可以减少顶点数据的数量,适用于绘制连续的表面。
- GL_TRIANGLE_FAN 绘制一个三角扇,三角扇上的所有点都是相连的。
GL_TRIANGLE_FAN绘制一个三角扇。
三角扇上的第一个顶点是中心点,其他顶点与中心点构成一个三角形。
每个新的顶点都与中心点和前一个顶点构成一个新的三角形。
适用于绘制放射状的图形,例如圆形、星形等。
- GL_QUADS 绘制一系列四边形。
- GL_QUAD_STRIP 绘制一个四边形带,四边形带上的所有点都是相连的。
- GL_POLYGON 绘制一个多边形(由很多个点连接而成)。
fbo和rendertarget
fbo
fbo(Frame Buffer Object)是OpenGL中的一个概念,它允许将渲染结果存储到帧缓冲区中,而不是直接显示到屏幕上。通过使用fbo,我们可以将渲染结果保存到纹理或渲染缓冲区中,以便后续处理或进一步渲染,而在QOpenGlFunction中我们可以使用下列代码获取默认输出fbo(渲染在屏幕中的fbo)
GLuint defaultFramebuffer;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, reinterpret_cast<GLint *>(&defaultFramebuffer));
这是一个离屏渲染相关的函数,在此篇中只做提及不做深入
rendertarget
rendertarget(渲染目标)是OpenGL中的一个概念,它允许我们将渲染结果存储到纹理或渲染缓冲区中,以便后续处理或进一步渲染。通过使用rendertarget,我们可以将渲染结果保存到纹理或渲染缓冲区中,而不是直接显示到屏幕上,更多的应用在游戏上,我们也可以通过创造rendertarget的方式更好地管理离屏渲染目标
标签:万字超,VAO,OpenGL,详解,顶点,GL,绘制,着色器 From: https://blog.csdn.net/particles/article/details/136837101