双缓冲设计(Double Buffering)
void glfwSwapBuffers(GLFWwindow * window)
/* This function swaps the front and back buffers of the specified window. If the swap interval is greater than zero, the GPU driver waits the specified number of screen updates before swapping the buffers.
When an application draws in a single buffer the resulting image may display flickering issues. This is because the resulting output image is not drawn in an instant, but drawn pixel by pixel and usually from left to right and top to bottom. Because this image is not displayed at an instant to the user while still being rendered to, the result may contain artifacts. To circumvent these issues, windowing applications apply a double buffer for rendering. The front buffer contains the final output image that is shown at the screen, while all the rendering commands draw to the back buffer. As soon as all the rendering commands are finished we swap the back buffer to the front buffer so the image can be displayed without still being rendered to, removing all the aforementioned artifacts.
*/
glfwSwapBuffers
采用两个缓冲设计,因为绘制不是立即完成的(由上到下,由左到右),所以绘制在幕后进行,当绘制完成后,在切换到前台给用户看已经画好的,这样增强用户体验
垂直同步(V-SYNC)
void glfwSwapInterval(int interval)
/*
This function sets the swap interval for the current context, This is sometimes called 'vertical synchronization', 'vertical retrace synchronization' or 'vsync',
interval = The minimum number of screen updates to wait for until the buffers are swapped by glfwSwapBuffers
*/
设置要等待多少次屏幕刷新,才会将后台缓冲区切换到前台。
如果设置为0,GPU无需等待显示器刷新信号,可能导致画面渲染速度过快,显示器来不及显示,出现画面丢失。
如果设置为1,GPU会等待显示器的V-Blank信号进行缓冲区交换,若GPU性能较差,可能导致帧率降低,画面更新不及时。
栅格化(Rasterisation)
栅格化: 指的是将3D模型中的图形,尤其是三角形等几何图形,通过投影映射到2D屏幕上的过程
完美的几何图形可以比作无限高清图片,当矢量绘制到低像素屏幕上,就会出现一个现象,就是信息会丢失。
需要决定哪些像素块需要绘制,哪些不需要,这个过程叫做栅格化。
信息丢失的结果是弧线看起来有锯齿,可以打开抗锯齿(当然是由性能消耗的)。
抗锯齿并不能分割像素,抗锯齿做的是在黑白世界,增加了灰度,使得曲线看起来比较平滑
绘图流水线
Application => Geometry => Rasterization => Screen
应用 几何 栅格化 屏幕
OpenGL世界是3D, 但屏幕是2D的,所以很大一部分绘制工作,就是将3D坐标转换成2D坐标。
Graphics Pipeline的另一部分工作是,将转换的2D坐标,绘制成颜色像素点
流水线上的操作处理程序,称作shaders,随着时间推移,shaders一词已经进化成为处理图形渲染的专门程序
上图是流水线处理过程,具体可通过代码了解,蓝色部分的shaders可以通过GLSL语言控制
VBO
屏幕坐标范围[-1, 1], 每一组三维坐标(x, y, z), 其中 z=0
定义坐标数组,然后将数据拷贝到显卡存储单元VBO(vertex buffer objects)中,GPU中的显存要比内存快得多
从CPU到显卡推送数据比较慢,所以OpenGL期待传送一个array包含所有vetices
- 在显卡创建一个Buffer
- 通过Bind改变OpenGL状态机,将其设定为当前的Buffer
- 拷贝数据到Buffer中(注意使用GL_ARRAY_BUFFER而不是id)
拷贝数据最后一个参数决定,GPU内存使用类型
- GL_STATIC_DRAW = 上传一次, 绘制多次
- GL_DYNAMIC_DRAW = 多次上传,绘制多次
- GL_STREAM_DRAW = 流式,上传一次,绘制一次,不断重复
float vertices[] = {
-0.5f, -0.5f, 0.0f, //left
0.5f, -0.5f, 0.0f, //right
0.0f, 0.5f, 0.0f // top
};
unsigned int VBO; // vertex buffer object
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
VAP
Vertex Shader允许传入不同的Buffer,我们还需要告诉OpenGL数据结构,才能解析 以三角形坐标为例,
// index = 0 (GLSL的location, glEnableVertexAttribArray)中需一致
// 数据维度 = 3
// 数据类型 = GL_FLOAT
// 是否需要normalized,如果是无符号转换成[0, 1], 有符号数据转换成[-1, 1]
// 下一组数据的offset
// 第一组数据起始offset
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
VAO
通常需要数十个VBO来维持不同坐标,纹理,命令等等,一个个绑定十分麻烦。 新版OpenGL提供了VAO(vertex array object),能够存储所有的vertex.
- 创建一个Buffer Array
- 通过Bind绑定,这里要先绑定VAO,让后续的VBO绑定和属性设置记录到VAO中
- 绑定后
glVertexAttribPointer glEnableVertexAttribArray glDisableVertexAttribArray
操作都会存储到VAO中 - 需要enable属性
unsigned int VAO;
// create VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// agttributes
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
GLSL
GLSL和C语言很像,需要编译后才能使用,OpenGL程序至少需要两个shader才能工作,若有任何错误OpenGL选择躺平不画:
- vertex shader处理坐标
- fragment shader上色
vertex shader
GLSL最大是纬度是4,也就是vec4, 分别为(x, y, z, w), 前三个是3维坐标,最后一个w和视角有关系
330对应版本3.3(从3.3开始版本才开始对应),使用core mode,一个输入变量in vec3, gl_Position为输出
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
frament shader
一个输出out vec4,代表RGBA颜色,数值范围[0.0, 1.0]
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
最后,需要编译,链接才能使用,可以修改DLSL源码,但是要重新链接才能生效
绘制三角形
// commonly point, line or triangle
glDrawArrays(GL_TRIANGLES, 0, 3);