2.1.5 fragment shading
这一阶段,就是我们所熟知的片源着色器阶段。在这一阶段,我们使用自定义的程序,对每一个片源进行处理。在可编程管线时代,纹理的采样,光照的处理,都是在这一阶段执行。
2.1.6 Pre-Fragment Operations
在执行完上一阶段后,管线还会做一些额外的操作,如图2.3所示。
图2.3 Pre-Fragment Operations
在这里,我们常用用到的是这几个,剪裁测试,Alpha测试,深度测试,蒙版测试,颜色混合。
剪裁测试,是通过调用glScissorXXX相关函数,设定一个矩形区域,当片源不在矩形区域中,管线将自动阻止他进入下一阶段。
Alpha测试,我们可以通过调用glAlphaFunc,设定只有alpha满足一定条件后,才能通过这个测试。
深度测试,当将要绘制的片源Z值大于原先帧缓冲片源的Z值时,才会通过这一阶段。
蒙版测试,我们可以调用glStencil 相关函数,设置蒙版区域,只有蒙版区域被标记为某些值得时候,才能通过测试。
通常,深度缓冲和蒙版缓冲是放在一起的,深度缓冲占24位,蒙版缓冲占8位。
另外纠正一点,我们平常爱说的,给某一个层添加一个蒙版,添加了一个半透明的层,蒙到场景上面。准确的说,这种方法叫Blending,而不是蒙版。是管线根据alpha值,与之前的帧缓冲经行叠加。
2.2 一个简单的着色器程序
了解了OpenGL管线,小伙伴们是不是已经按捺不住,想要做点什么呢。下面,我们将写第一个应用shader程序,如图2.4,2.5,2.6,2.7所示。
图2.4 第一个三角形代码-1
图2.5 第一个三角形代码-2
图2.6 第一个三角形代码-3
图2.7 第一个三角形
Vertex Shader:
attribute vec2 position; void main() { }
gl_Position = vec4(position,0.0,1.0);
Fragment Shader:
void main() { }
gl_FragColor = vec4(0.1,0.1,0.5,1.0);
下面,我将来逐一讲解这些程序。
首先是初始化shader,这些都是固定的代码,以后可以封装到基类中,以供统一调用。在这里,我不得不吐槽一下新版本的GLProgramState。这个类是对于GLProgram的一个封装,提供了对uniform和attribute的统一管理。但当我查看他的源码的时候发现,他的实现方法用了共用体,用了std::function
的callback,令人很难驾驭,而且在apply函数中,没有检测是否需要更新。总之,还没有小编以前写的UniformManager好用。所以建议大家不要用他。
2.3 VAO与VBO
下面就是创建顶点,并把顶点传入显存中。
有些同学看到这里就要问了,为什么要把数据传入显存中,内存中一份数据,显存中一份数据,这不是浪费存储么?但问题在于,CPU和显卡的GPU并不是放一起的,在硬件中是两个独立的原件。CPU更善于随机访问的逻辑运算,GPU善于做批量处理的数学运算。我们的手机、电脑之所以要装显卡,是为了把CPU从繁重的图形学运算中解脱出来,专职处理逻辑运算。而让更善于做并行批量数学运算的显卡去做图形学计算。这样一来,CPU和GPU就成了一个CS架构。在硬件高度发展的今天,图形学程序的效率损失,往往会发生在CPU和GPU之间的数据传输以及同步上,如图2.8所示。
图2.8 GPU和CPU的数据传递
正因如此,OpenGL提供了一个叫Vertex Buffer Object的解决方案,也