- 什么是OpenGL
- OpenGL 命名规律
- OpenGL 渲染管线
- 准备向 OpenGL 传输数据
- 将数据传输到 OpenGL
- 顶点着色
- 细分着色
- 几何着色
- 图元装配
- 剪切
- 光栅化
- 片元着色
- 逐片元的操作
- Resources
- References
LearnGL - 学习笔记目录
本人才疏学浅,如有什么错误,望不吝指出。
下面部分内容引用《OpenGL 编程指南》 第9版的内容。
什么是OpenGLOpenGL 是一种应用程序编程接口(Application Programming Interface, API),它是一种可以对图形硬件设备特性进行访问的软件库。OpenGL 4.5 版本包含 500+ 个命令。
OpenGL 被设计被一个现代化的、硬件无关的接口,因此我们可以在不考虑计算机操作系统或窗口系统的前提下,在多种不同的图形硬件系统上,或者完全通过软件的方式(如果当前系统没有图形硬件)实现 OpenGL 的接口。
Open 只是一中渲染系统,当然还有其他的渲染系统。OpenGL 是基于光栅化的系统,但是也有别的方法用于生成图像。例如:光线追踪(ray tracing),就算是光线追踪技术的系统,同样可能需要用到 OpenGL 来显示图像,或是计算图像生成需要的信息。并且,OpenGL 的最新版本如今已经变得更加灵活而强大,因此诸如光线追踪、光子映射(photon mapping)、路径跟踪(path tracing)以及基于图像的渲染(image-based rendering)这样的技术都可以相对简单地在可编程图形硬件端实现了。
OpenGL 命名规律你可以会看到使用 OpenGL 的函数都会 gl 开头的,例如:glGenBuffers
。
而我们使用的第三方 GLFW 库的函数都是以 glfw 开头的,这个注意区别。
除了 OpenGL 的函数外,OpenGL 的常量值都是以 GL_ 开头的,例如:GL_COLOR
,这些常量都是用#define
宏定义来定义的,他们基本上都可以在 OpenGL 的头文件 glcorearb.h
和 glext.h
中找到。
还有一些函数,如:glUniform1f
,glUniform2i,glUniform3fv
,的这类函数。
很明显的规律是:glUniform[N][T][V]
- N 可以是1~4的数字,代表的是要输入的分量数量。
- T 可以是
i,f,ui
等,具体可以看下面的个表,i
是int
,f
是float
,ui
是unsigned int
,代表的是分量的类型。 - V 代表vector,就是数组,意思每个元素都是:N个T类型分量的。
T 的后缀类型与参数数据类型
后缀数据类型通常对应的C语言数据类型对应的OpenGL类型b8位整型signed charGLBytes16位整型signed shortGLshorti32位整型intGLint、GLsizeif32位浮点型floatGLfloat、GLclampfd64位浮点型doubleGLdouble、GLclampdub8位无符号整型unsigned charGLubyteus16位无符号整型unsigned shortGLushortui32位无符号整型unsigned intGLuint、GLenum、GLbitfield为何OpenGL 有对应的C/C++类型了,还要重新定义一份,这都是为了可移植性考虑的,因为每个操作系统数据类型区别、或是硬件制造产商编写的 OpenGL 实现都可以不一致,如果我们直接使用C/C++的基础数据类型,有可能在不同这些平台会有差异,导致移植性变差,所以尽量用OpenGL提供的类型。
OpenGL 渲染管线OpenGL 实现了我们通常所说的渲染管线(rendering pipeline),它是一些列数据处理过程,并且将应用程序的数据转换到最终渲染的图像。
OpenGL 渲染管线:顶点数据 -> 顶点着色器 -> 细分控制着色器 -> 细分计算着色器 -> 几何着色器 -> 图元设置 -> 裁剪和剪切 -> 光栅化 -> 片元着色器
每个阶段都会将处理好的数据传递到下一个阶段。
截面截一张:OpenGL 4.5 References Card中的截图:
OpenGL 需要将所有的数据都保存到缓存对象(buffer object)中,它相当于由 OpenGL 维护的一块内存区域。我们可以使用多种方式来穿件这样的数据缓存,不过最常用的方法就是使用 glNamedBufferStorage() 命令同时这时缓存的大小及内容。我们可能还需要对缓存做一些额外的设置。
将数据传输到 OpenGL当将缓存初始化完毕之后,我们可以通过调用 OpenGL 的一个绘制命令来请求渲染几何图元,glDrawArrays()就是一个常用的绘制命令。
OpenGL 的绘制通常就是将顶点数据传输到 OpenGL 服务端。我们可以将一个顶点视为一个需要统一处理的数据包。这个包中的数据可以是我们需要的任何数据(也就是说,我们自己负责定义构成顶点的所有数据),通常其中几乎始终会包含位置数据。其他的数据可能用来决定一个像素的最终颜色。
顶点着色顶点着色器会对传进来的所有的顶点数据进行处理,通常会用到变换矩阵(transformation matrix)。或是基于顶点的光照计算、等。
细分着色顶点着色器处理每个顶点的关联数据之后,如果同时激活了细分着色器(tessellation shader),那么它将进一步处理这些数据。细分着色器会使用面片(patch)来描述一个物体的形状,并且使用相对简单的面片几何体连接来完成细分的工作,其结果是几何图元的数量增加,并且模型的外观会变得更为平顺。细分着色阶段会用到两个着色器来分别管理面片数据并生成最终的形状。
几何着色下一个着色阶段(几何着色)允许在光栅化之前对每个几何图元做更进一步的处理,例如创建新的图元。这个着色阶段也是可选的。
图元装配前面介绍的着色阶段所处理的都是顶点数据,此外,这些顶点构成几何图元的所有信息也会被传递到 OpenGL 当中。图元装配阶段将这些顶点与相关的几何图元之间组织起来,准备下一步的剪切和光栅化工作。
剪切顶点可能会落在视口(viewport)之外(也就是我们可以进行绘制的窗口区域),此时与顶点相关的图元会做出改动,以保证相关的像素不会再视口外绘制。这一过程叫做剪切(clipping),它是由 OpenGL 自动完成的。
光栅化剪切之后马上要执行的工作,就是将更新后的图元传递到光栅化(rasterizer)单元,生成对应的片元。光栅化的工作是判断某一部分几何体(点、线或者三角形)所覆盖的屏幕空间。得到了屏幕空间信息以及输入的顶点数据之后,光栅化单元就可以直接对片元着色器中的每个可变变量进行线性插值,然后将结果传递给用于的片元着色器。我们可以将一个片元视为一个“候选的像素”,也就是可以放置在帧缓存中的像素,但是它也可能被最终剔除,不在更新对应的像素位置。之后的两个阶段将会执行片段的处理,即片段着色和逐片元的操作。
OpenGL 实现光栅化和数据插值的方法是与具体平台相关的。我们无法保证在不同平台上的插值结果总是相同。
片元着色会将光栅化生成的所有片元都执行一次片元着色器。可以进行光照计算,或是片元丢弃(discard)。
逐片元的操作除了我们在片元着色器里做的工作之外,片元操作的下一步就是最后的独立片元处理过程。在这个阶段里会使用深度测试(depth test,深度测试)和模板测试(stencil test)的方式来决定一个片元是否可见。
如果一个片元成功通过了所有激活的测试,那么它就可以被直接绘制到帧缓存中,它对应的像素的颜色值或是深度值都可能被更新,如果开启了融混(blending)模式,那么片元的颜色会与该像素当前位置对应的帧缓存的颜色进行混合后再更新到帧缓存。
Resources- OpenGL pipeline.xlsx
- 《OpenGL 编程指南》第9版