OpenGL本身是一种规范,只是规定了一些应有的函数和参数,没有任何实现(实现由第三方库完成,如glfw、glew).
本文之所以放在[编程]->[框架]的分类,是因为本文主要内容是用OpenGL规范实现的库的使用,而非OpenGL规范本身
图形渲染管线是实时渲染的核心组件。渲染管线的功能是通过给定虚拟相机、3D场景物体以及光源等场景要素来产生或者渲染一副2D的图像。渲染管线是实时渲染的重要工具,主要包括两个功能:一是将物体3D坐标转变为屏幕空间2D坐标,二是为屏幕每个像素点进行着色。
渲染管线的一般流程分别是:顶点数据的输入、顶点着色器、曲面细分过程、几何着色器、图元组装、裁剪剔除、光栅化、片段着色器以及混合测试。
OpenGL中,所有事物都是在3D空间中的,而屏幕是2D,因此必须把三维的坐标转换成二维坐标。
图形渲染管线
图形渲染管线(Graphics Pipeline): 一个原始数据,经过一定变化和处理,最终显示在屏幕上。
主要有两个步骤:
- 把3D坐标转换为2D坐标
- 把2D坐标转变为实际的有颜色的像素
2D坐标和像素也是不同的,2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制。
可被划分为几个阶段,连接而成,都具有特定的函数,很容易执行(正因为容易执行,才可以在GPU上运行成千上万个各自阶段的小程序,这些小程序为着色器).
着色器运行在GPU上,节省了CPU的(宝贵的)时间.
OpenGL着色器是用OpenGL着色器语言(OpenGL Shading Language, GLSL)写成的
具体阶段:
- 输入数组,也即**顶点数据(Vertex Data),它是用顶点属性(Vertex Attribute)**表示的,如坐标、颜色等
- 使用图元(Primitive)可以告诉OpenGL把顶点渲染成什么样,如一系列点、一系列三角形或线,可用的**提示(Hint)**如
GL_POINTS
、GL_TRIANGLES
、GL_LINE_STRIP
- 图形渲染管线的第一个部分是顶点着色器(Vertex Shader),目的是把3D坐标数据转换成另一种3D坐标**,同时允许我们对顶点属性进行一些基本处理
- 图元装配是将顶点着色器输出的所有顶点作为输入,并所有的点装配成指定图元的形状(如果是GL_POINTS,那么就是一个顶点),如三角形
- 图元装配阶段的输出会传递给**几何着色器(Geometry Shader)**。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状;例子中,它生成了另一个三角形
- 几何着色器的输出会被传入**光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)**。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率
OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据
片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色
- 在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行**混合(Blend)**;所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同
第一个三角形
此处给出源码和详细注释,结构清晰:
|
概念与作用
第一个三角形涉及到一些概念和特性,最引人注目的是三个对象,即:
- 顶点数组对象:Vertex Array Object,VAO
- 顶点缓冲对象:Vertex Buffer Object,VBO
- 元素缓冲对象:Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO
标准化设备坐标
标准化设备坐标(Normalized Device Coordinates):OpenGL会将坐标转化为单位坐标,即所有轴上的大小范围为(-1, 1)
顶点缓冲对象
顶点缓冲对象(Vertex Buffer Objects, VBO):管理顶点的内存,在显存中储存大量顶点
这样可以一次性发送大量数据到显卡,而不用每次绘制都到cpu的内存中读取数据
- 创建
unsigned int VBO; //创建ID,类似套接字,可以通过此ID访问此对象(唯一绑定) |
glBufferData
函数的最后一个参数是指定显卡管理数据的方式:
- GL_STATIC_DRAW :数据不会或几乎不会改变,修改一次,使用多次
- GL_DYNAMIC_DRAW:数据会被改变很多,修改多次,使用多次
- GL_STREAM_DRAW :数据每次绘制时都会改变,每次都会修改和使用
- 使用
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), |
glVertexAttribPointer
函数参数:
- 第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用
layout(location = 0)
定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0
。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0
。 - 第二个参数指定顶点属性的大小。顶点属性是一个
vec3
,它由3个值组成,所以大小是3。 - 第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中
vec*
都是由浮点数值组成的)。 - 下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
- 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个
float
之后,我们把步长设置为3 * sizeof(float)
。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。 - 最后一个参数的类型是
void*
,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。
元素缓冲对象
元素缓冲对象(Element Buffer Object,EBO):当有重复的顶点需要绘制时,不需要定义出相同的顶点,而是使用索引来引用重复的顶点
这样只需要定义出不重复的所有顶点,需要哪个顶点时使用索引找到并使用它即可,这是EBO的工作方式
- 创建顶点数组和索引
// 绘制两个三角形组合成矩形,本来需要六个顶点,但是有两组重复的顶点 |
- 创建EBO(与VBO类似)
unsigned int VBO; |
- 使用
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), |
必须结合VBO才能使用EBO,因为EBO只储存了索引,而没有顶点数据
顶点数组对象
顶点数组对象(Vertex Array Object, VAO):主要用于管理 VBO 或 EBO ,减少glBindBuffer
、glEnableVertexAttribArray
、 glVertexAttribPointer
这些调用操作,高效地实现在顶点数组配置之间切换。
VAO的简单使用:
// 创建VAO |
其他
实践表明,无法使用同一个VAO绑定不同的VBO,画出两个不同的图形,一般是多个VAO分别对应多个VBO。一个EBO可以画出多个图形,但只是从预先设置好的数组中读取顶点数据,只不过可以重复使用顶点。
词汇表
- OpenGL: 一个定义了函数布局和输出的图形API的正式规范。
- GLAD: 一个扩展加载库,用来为我们加载并设定所有OpenGL函数指针,从而让我们能够使用所有(现代)OpenGL函数。
- **视口(Viewport)**: 我们需要渲染的窗口。
- **图形管线(Graphics Pipeline)**: 一个顶点在呈现为像素之前经过的全部过程。
- **着色器(Shader)**: 一个运行在显卡上的小型程序。很多阶段的图形管道都可以使用自定义的着色器来代替原有的功能。
- **标准化设备坐标(Normalized Device Coordinates, NDC)**: 顶点在通过在剪裁坐标系中剪裁与透视除法后最终呈现在的坐标系。所有位置在NDC下-1.0到1.0的顶点将不会被丢弃并且可见。
- **顶点缓冲对象(Vertex Buffer Object)**: 一个调用显存并存储所有顶点数据供显卡使用的缓冲对象。
- **顶点数组对象(Vertex Array Object)**: 存储缓冲区和顶点属性状态。
- **元素缓冲对象(Element Buffer Object,EBO),也叫索引缓冲对象(Index Buffer Object,IBO)**: 一个存储元素索引供索引化绘制使用的缓冲对象。
- Uniform: 一个特殊类型的GLSL变量。它是全局的(在一个着色器程序中每一个着色器都能够访问uniform变量),并且只需要被设定一次。
- **纹理(Texture)**: 一种包裹着物体的特殊类型图像,给物体精细的视觉效果。
- **纹理环绕(Texture Wrapping)**: 定义了一种当纹理顶点超出范围(0, 1)时指定OpenGL如何采样纹理的模式。
- **纹理过滤(Texture Filtering)**: 定义了一种当有多种纹素选择时指定OpenGL如何采样纹理的模式。这通常在纹理被放大情况下发生。
- **多级渐远纹理(Mipmaps)**: 被存储的材质的一些缩小版本,根据距观察者的距离会使用材质的合适大小。
- stb_image.h: 图像加载库。
- **纹理单元(Texture Units)**: 通过绑定纹理到不同纹理单元从而允许多个纹理在同一对象上渲染。
- **向量(Vector)**: 一个定义了在空间中方向和/或位置的数学实体。
- **矩阵(Matrix)**: 一个矩形阵列的数学表达式。
- GLM: 一个为OpenGL打造的数学库。
- **局部空间(Local Space)**: 一个物体的初始空间。所有的坐标都是相对于物体的原点的。
- **世界空间(World Space)**: 所有的坐标都相对于全局原点。
- **观察空间(View Space)**: 所有的坐标都是从摄像机的视角观察的。
- **裁剪空间(Clip Space)**: 所有的坐标都是从摄像机视角观察的,但是该空间应用了投影。这个空间应该是一个顶点坐标最终的空间,作为顶点着色器的输出。OpenGL负责处理剩下的事情(裁剪/透视除法)。
- **屏幕空间(Screen Space)**: 所有的坐标都由屏幕视角来观察。坐标的范围是从0到屏幕的宽/高。
- LookAt矩阵: 一种特殊类型的观察矩阵,它创建了一个坐标系,其中所有坐标都根据从一个位置正在观察目标的用户旋转或者平移。
- **欧拉角(Euler Angles)**: 被定义为偏航角(Yaw),俯仰角(Pitch),和滚转角(Roll)从而允许我们通过这三个值构造任何3D方向。
- 本文标题:OpenGL-1 初识
- 创建时间:2023-06-12 17:34:01
- 本文链接:2023/06/12/note/Framework/OpenGL/opengl-intro/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!