- 顶点数组对象:Vertex Array Object,VAO
- 顶点缓冲对象:Vertex Buffer Object,VBO
- 元素缓冲对象:Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO
OpenGL的工作主要负责将3D空间坐标转换为屏幕的2D像素,3D坐标转换到2D的过程叫做PipeLine,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程
2D坐标和像素也是不同的,2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制。
渲染管线就是GPU执行的这个流水线,通过CPU将数据在应用阶段传送给GPU后,通过渲染管线的每个阶段,每个阶段都接收一个输入,产出一个输出,这些输入输出就是需要被渲染的顶点数据,输出数据给下一个阶段处理。
所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。OpenGL着色器是用OpenGL着色器语言
顶点数据
顶点数据是一系列顶点的集合。一个顶点(Vertex)是一个3D坐标的数据的集合。而这样一个顶点的数据是用顶点属性(Vertex Attribute)表示的,它可以包含任何我们想用的数据
为了让OpenGL知道我们的坐标和颜色值构成的到底是什么,OpenGL需要你去指定这些数据所表示的渲染类型。我们是希望把这些数据渲染成一系列的点?一系列的三角形?还是仅仅是一个长长的线?做出的这些提示叫做图元(Primitive),任何一个绘制指令的调用都将把图元传递给OpenGL。这是其中的几个:
- GL_POINTS
- GL_TRIANGLES
- GL_LINE_STRIP
渲染管线
怎么理解着色器,着色器就是渲染管线中的每个阶段运行的各自的小程序,下面简要介绍各个阶段的功能
图形渲染管线的第一个部分是**==顶点着色器==**(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。
顶点着色器阶段的输出可以选择性地传递给几何着色器(Geometry Shader)。几何着色器将一组顶点作为输入,这些顶点形成图元,并且能够通过发出新的顶点来形成新的(或其他)图元来生成其他形状。在这个例子中,它从给定的形状中生成第二个三角形。
图元装配(Primitive Assembly)阶段将顶点着色器(或几何着色器)输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并将所有的点装配成指定图元的形状;本节例子中是两个三角形。
图元装配阶段的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据。
**==片段着色器==**的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。
- 为每个像素/片段运行片段着色器并且他们决定了颜色输出,顶点着色器为每个顶点运行,他们决定了屏幕上的位置。
顶点缓冲区-VB
OpenGL仅当3D坐标在3个轴(x、y和z)上-1.0到1.0的范围内时才处理它。所有在这个范围内的坐标叫做标准化设备坐标(Normalized Device Coordinates),此范围内的坐标最终显示在屏幕上(在这个范围以外的坐标则不会显示)
-
指定坐标时,一般有三个点,
x,y,z这个z指的时深度,也就是在右手坐标系中,z轴指向屏幕外的方向,z就相当于离你的距离,如果离得远可能被别的像素遮挡,就会被丢弃/裁剪来节省资源。 -
顶点坐标已经在顶点着色器中处理过,它们就应该是标准化设备坐标了,标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。
光栅化阶段中,在使用glViewPort函数指定视口后,通过视口变换之后,NDC会变换为屏幕空间坐标。所得的屏幕空间坐标又会被变换为片段输入到片段着色器中。
float position[6] = {
-0.5f, 0.5f,
-0.5f, -0.5f,
0.5f, 0.5f
};
定义顶点数据后,顶点着色器接收这些数据,在GPU上创建内存用于存储顶点数据:
/*----用于将顶点数据上传GPU----*/
//GLenum -- 理解为slot
unsigned int buffer; //存储生成的缓冲区对象的ID
glGenBuffers(1, &buffer); //生成1个缓冲区,将ID存储在buffer变量中
glBindBuffer(GL_ARRAY_BUFFER,buffer); //绑定缓冲区对象到当前的OpenGL上下文中的GL_ARRAY_BUFFER上
glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW); //为绑定到GL_ARRAY_BUFFER的缓冲区对象分配内存并初始化数据
这里的buffer也就是所谓的VBO顶点缓冲对象,这四行就是VBO的初始化,通过Genbuffer生成带有ID的缓冲区,ID给Buffer,通过bind绑定为GL_ARRAY_BUFFER的缓存类型。
OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型.
这里就可以直接把缓存类型当作某种插槽,也就是说可以将VBO同时插在多个插槽里
从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)。然后我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中
三角形的位置数据不会改变,每次渲染调用时都保持原样,所以它的使用类型最好是GL_STATIC_DRAW。如果,比如说一个缓冲中的数据将频繁被改变,那么使用的类型就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,这样就能确保显卡把数据放在能够高速写入的内存部分。