第一章 OpenGL概述
1.1 什么是OpenGL
- 一个用来渲染图像的OpenGL程序需要执行的主要操作:
- 从opengl的几何图元中设置数据
- 使用不同的着色器对输入的图元数据执行计算操作,判断他们的位置、颜色,以及其他渲染属性
- 将输入图元的数学描述转换为与屏幕位置对应的像素片元(光栅化)
- 针对光栅化过程产生的每个片元,执行片元着色器,从而决定这个片元最终的位置和颜色
- 如果有必要,还要对每个片元执行一些额外的操作,例如判断片元对应的对象是否可见,或者将片元的颜色与当前屏幕位置的颜色进行融合
1.2 初始OpenGL程序
- OpenGL程序的基本结构
- 初始化物体渲染所对应的状态
- 设置需要渲染的物体
- OpenGL是基于光栅化的系统
- 着色器–图形硬件设备所执行的函数
- 顶点着色器–用于处理顶点数据
- 片元着色器–用于处理光栅化后的片元数据
- 帧缓存–由图形硬件设备管理的一块独立内存区域,可以直接映射到最终的显示设备上。计算机系统将所有的像素保存到这里
1.3 OpenGL语法
| 后缀 |
数据类型 |
C语言类型 |
OpenGL类型 |
| b |
8位整型 |
signed char |
GLbyte |
| s |
16位整型 |
signed short |
GLshort |
| i |
32位整型 |
int |
GLint、GLsizei |
| f |
32位浮点型 |
float |
GLfloat、GLclampf |
| d |
64位浮点型 |
double |
GLdouble、GLclampd |
| ub |
8位无符号整型 |
unsigned char |
GLubyte |
| us |
16位无符号整型 |
unsigned short |
GLushort |
| ui |
32位无符号整型 |
unsigned int |
GLuint、GLenum、GLbitfield |
1.4 OpenGL渲染管线
- 渲染管线–他是一系列数据处理过程,并且将应用程序的数据转换到最终渲染的图形
- OpenGL首先接收用户的几何数据(顶点和几何图元),并将他们输入到一系列着色器阶段中进行处理,包括:顶点着色、细分着色、以及最后的几何着色,然后他们将被送入光栅化单元
- 光栅化单元负责对所有裁剪区域内的图元生成片元数据,然后对每个生成的片元都执行一次片元着色器
1.4.1 准备向OpenGL传输数据
- OpenGL需要将所有的数据都保存到缓存对象(buffer object)中,它相当于OpenGL服务端维护的一块内存区域。 我们可以使用多种方式来创建这样的数据缓存,不过最常用的方法就是glBufferData()命令。
1.4.2 将数据传输到OpenGL
- 调用OpenGL的绘制命令(比如glDrawArrays()),通常就是将顶点数据传输到OpenGL的服务端。(我们可以将一个顶点视为一个需要统一处理的数据包,这个包中的数据可以是我们需要的任何数据)
1.4.3 顶点着色
- 对于绘制命令传输的每个顶点,OpenGL都会调用一次顶点着色器来处理顶点相关的数据
1.4.4 细分着色
- 细分着色器一般使几何图元的数量增加,使模型外观变得更为平顺
1.4.5 几何着色
1.4.6 图元装配
- 图元装配阶段将顶点和相关的几何图元之间组织起来,准备下一步的剪切和光栅化工作
1.4.7 剪切
1.4.8 光栅化
- 剪切之后马上进行的工作,就是将更新后的图元传递到光栅化单元,生成对应的片元。我们可以将一个片元视为一个“候选的像素”,也就是可以放置在帧缓存中的像素,但是它也可能被最终剔除,不再更新对应的像素位置
1.4.9 片元着色
-
控制屏幕上显示颜色的阶段。 在这个阶段中,我们可以计算片元的最终颜色和他的深度值。
- 顶点着色器决定一个图元应该位于屏幕的什么位置;片元着色器使用这些信息决定某个片元的颜色是什么
1.4.10 逐片元的操作
- 在这个阶段里会使用深度测试和模板测试的方式来决定一个片元是否是可见的
1.5 第一个程序的深入分析
1.5.1 main函数
- glutInit() 负责初始化GLUT库
- glutInitDIsplayMode() 设置应用程序使用的窗口类型
- glutInitWindowSize() 设置所需的窗口大小
- glutInitContextVersion() 设置opengl环境的类型
- glutInitContextProfile() 使用opengl4.3版本的核心模式创建环境
- glewInit() 初始化GLEW库,该库简化获取函数地址的过程
- glutMainLoop() 无限循环,处理窗口和操作系统的用户输入等
1.5.2 OpenGL的初始化过程
初始化顶点数组对象
-
顶点数组对象–负责保存一系列顶点的数据。这些数据保存到缓存对象当中,并且由当前顶点数组对象管理
- glGenVertexArrays(GLsizei n, GLuint* arrays) 返回n个未使用的顶点数组对象名到数据arrays中
- glGen* 负责分配不同类型的OpenGL对象的名称
- 这里的名称类似C语言的一个指针变量,我们必须分配内存并且用名称引用它之后,名称才有意义。
- 在OpenGL中,给名称分配内存的机制叫做绑定对象,它通过一系列glBind*形式的函数去实现。
- glBindVertexArray(GLuint array) 如果array非0并且是glGenVertexArrays()所返回的,那么它将创建一个新的顶点数组对象并且与array关联起来;如果绑定到一个已经创建的顶点数组对象中,那么会**这个顶点数组对象(把该顶点数组对象设置为当前顶点数组对象),并且直接影响对象中所保存的顶点数组状态
- 第一次绑定对象(即调用glBind*(a)),OpenGL内部会分配a对象需要的内存,并且将它做为当前对象,即后续的操作都会作用于这个被绑定的对象
-
有两种情况需要绑定对象:
- 创建对象并初始化它所对应的数据时
- 每次准备使用某个对象,而它又不是当前对象时
- 当我们完成对顶点数组对象的操作之后,可以调用glDeleteVertexArrays(GLsizei n, GLuint* arrays)将它释放
- glIsVertexArray(GLuint array) 检查某个名称是否已经被保留位一个顶点数组对象了
分配顶点缓存对象
- 缓存对象–就是OpenGL服务端分配和管理的一块内存区域,并且几乎所有传入OpenGL的数据都是存储在缓存对象当中的
- 缓存对象的类型由八种
- glGenBuffers(GLsizei n, GLuint* buffers)
- glBindBuffer(GLenum target, GLuint buffer)
- glDeleteBuffers(GLsizei n, const GLuint* buffers)
- glIsBuffer(GLuint buffer)
将数据载入缓存对象
- 初始化顶点缓存对象之后,我们要把顶点数据从对象传输到缓存对象中。
- glBufferData(…) 有两个任务
- 分贝顶点数据需要的存储空间
- 将数据从应用程序的数组中拷贝到OpenGL服务端的内存中
- glBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage)
- 在OpenGL服务端内存中分配size个存储单元,用于存储数据或索引
- target的值
- 顶点属性数据–GL_ARRAY_BUFFER
- 索引数据–GL_ELEMENT_ARRAY_BUFFER
- OpenGL的像素数据–GL_PIXEL_UNPACK_BUFFER
- 从OpenGL中获取的像素数据–GL_PIXEL_PACK_BUFFER
- 缓存之间的复制数据–GL_COPY_READ_BUFFER和GL_COPY_WRITE_BUFFER
- 纹理缓存中存储的纹理数据–GL_TEXTURE_BUFFER
- 通过transform feedbeak着色器获得的结果–GL_TRANSFORM_FEEDBACK_BUFFER
- 一致变量–GL_UNIFORM_BUFFER
- size表示存储数据的总数据量(data中存储的元素的总数乘以单位元素存储空间的结果)
- data要么是客户端内存的指针,以便初始化缓存对象;要么是NULL。如果传入的指针合法,那么将会有size大小的数据从客户端拷贝到服务端。如果传入NULL,那么将保留size大小的未初始化的数据,以备后用
- usage用于设置分配数据之后的读取和写入方式。可用的方式包括
- GL_STREAM_DRAW
- GL_STREAM_READ
- GL_STREAM_COPY
- GL_STATIC_DRAW
- GL_STATIC_READ
- GL_STATIC_COPY
- GL_DYNAMIC_DRAW
- GL_DYNAMIC_READ
- GL_DYNAMIC_COPY
- 规格化设备坐标系统,Normalized Device Coordinate,NDC。坐标被限制在[-1,1]的范围内。OpenGL只能绘制坐标空间内的几何体图元。
初始化顶点与片元着色器
- “#version 430 core” 指定所用GLSL的版本,“430”表示OpenGL 4.3 对应的GLSL; "core"表示使用OpenGL核心模式
- 着色器变量是着色器与外部世界的联系所在
- “v”作为顶点属性名称的前缀
- layout(…) 布局限定符 目的是为变量提供元数据(location指变量的位置值或者说是索引值)
- “f”作为片元输出的变量的前缀
- 片元颜色的取值范围是[0,1]
- 为了给顶点着色器输入数据,需要在着色器中声明一个in变量,然后使用glVertexAttribPointer(…)将变量关联到一个顶点属性数组(这个数组应该是帧缓存中的当前顶点数组对象指向的数组)
- glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer)
- index–表示着色器中的属性的位置
- size–表示每个顶点需要更新的顶点数目,可以是1、2、3、4或者GL_BGRA
- type–指定数组中每个元素的数据类型
- GL_BYTE
- GL_UNSIGNED_BYTE
- GL_SHORT
- GL_UNSIGNED_SHORT
- GL_INT
- GL_UNSIGNED_INT
- GL_FIXED
- GL_HALF_FLOAT
- GL_FLOAT
- GL_DOUBLE
- normalized–设置顶点数据在存储前是否需要归一化
- stride–数组中每两个元素之间的大小偏移值(单位byte)
- pointer–表示缓存对象中,从起始位置开始计算的数组数据的偏移值(单位byte)
- glEnableVertexAttribArray(GLuint index) 启用顶点着色器中变量对应的顶点数组,index表示着色器中的变量的位置或者索引
- 在顶点着色器中声明一个输入型的顶点变量,有两个步骤:第一指定该变量的为位置或者索引,并关联帧缓存中的一个顶点数组;第二启用该变量(用位置或者索引标识该变量)
1.5.3 第一次使用OpenGL进行渲染
- 首先清除帧缓存的数据再进行渲染。由glClear(…)完成
- glClear(GLbitfield mask) 清除指定的缓存数据,并重设为当前的清除值。mask的取值由三个
- GL_COLOR_BUFFER_BIT
- GL_DEPTH_BUFFER_BIT
- GL_STENCIL_BUFFER_BIT
- glClearColor(…) 设置“清除颜色”的数值。(0,0,0,0)黑色;(1,1,1,1)白色。“清除颜色”本身也是OpenGL状态机的一个状态,它的数值会一直保留在当前OpenGL环境的状态机中
使用OpenGL进行绘制
- 首先调用glBindVertexArray(…)来选择作为顶点数据使用的顶点数组
- 其次调用glDrawArrays(…)来实现使用当前绑定的顶点数据来建立一系列几何图元
- glDrawArrays(GLenum mode, GLint first, GLsizei count)
- first–起始位置
- count–要绘制点的个数
- mode–图元类型
- GL_POINTS
- GL_LINES
- GL_LINE_STRIP
- GL_LINE_LOOP
- GL_TRIANGLES
- GL_TRIANGLE_STRIP
- GL_TRIANGLE_FAN
- GL_PATCHES
- glFlush() 强制所有进行中的OpenGL命令立即运行并传输到OpenGL服务端处理,自己立即返回。保证它们在一定时间内全部完成。
- glFinsh() 它与glFlush()的不同之处在于:它会一直等待所有当前的OpenGL操作完成后再返回
- glEnable(GLenum capability) 开启模式
- glDisable(GLenum capability) 关闭模式
- glIsEnabled(GLenum capability) 判断是否启用某个模式
相关文章: