混合面剔除
混合
目前我们所有的操作都是建立在物体是完全不透明的前提下的,但是对于透明材质的物体,例如玻璃,我们应该怎么样去渲染呢?
我们在片段着色器输出颜色的时候会输出一个vec4类型的向量,而这个向量的第四个向量便是控制物体透明度的通道,又因为之前我们渲染的都是不透明的物体,所以这个通道都设置为了1,也即是完全不透明,相反的,完全透明的物体也就是0,这将使物体不会被渲染出来。而问题最大的便是那种半透明的物体,也即是物体本身带有一点颜色,但是可以透光,例如有色玻璃等等。所以我们需要将两种颜色进行混合从而达到这种半透明的效果。
如果对颜色熟悉的话可能以及知道如何混合了,其实只需要将两种颜色按一定比例相加在一起即可,例如将50%的红加上50%的绿将会得到黄色,这也便是OpenGL颜色混合方程的由来:
混合一般要等到所有的测试完成(模板测试、深度测试)后,才会将颜色缓冲中的颜色与纹理中的颜色进行混合,而且源颜色与目标颜色一般会由openGL自己设定,但是我们可以设定目源因子值和目标因子值。可以通过函数glBlendFunc(GLenum sfactor, GLenum dfactor)
来分别设定源因子值和目标因子值。一般会将源因子值设置为源颜色向量中的ALPHA值,将目标因子值设定为1-ALPHA,这样做是因为如果源颜色向量是不透明的,那么ALPHA将会是1,那么目标因子就会变为0,这样如果源颜色向量通过了深度测试(即在原来像素的前面),那么源颜色向量会直接覆盖掉之前的颜色缓冲,这与实际逻辑也是吻合的。源因子与目标因子的取值为下表:
这些准备好之后便可以开启混合了,和之前开启深度测试或是模板测试一样,可以用glEnable(GL_BLEND)
来开启混合,然后再设定好混合函数glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
,这样OpenGL便会在完成深度测试后帮我们混合颜色了。
但这其实并没有结束,在加入了透明物体后会给我们的渲染造成很多额外的麻烦,其中最大的因素便是渲染顺序。之前我们并没有对渲染顺序有太多的考究,逼近都是不透明物体,只需要通过深度测试便可以知道其该不该被渲染到屏幕上。但是有了透明物体后,我们需要最后再渲染透明物体,试想一下:如果我们先渲染了透明物体,当我们渲染非透明物体的时候,由于非透明物体的ALPHA值是1,还记得我们将源因子设定为源颜色向量的ALPHA值吗?这将会导致目标因子标为0,也即是原来渲染出来的透明物体直接便消失了,即是非透明物体没有通过深度测试,但是由于混合的存在会直接将挡在不透明物体前面的透明物体忽略掉。
然而这还没有结束,在弄清楚非透明物体和透明物体的渲染顺序后,透明物体之间的渲染顺序也有考究,我们需要按远近顺序渲染透明物体,先渲染远处再渲染近处。再试想一下:我们先渲染了一个A物体,然后我们想着渲染A物体后面的一个B物体,此时A物体已经在颜色缓冲中,也即是A物体是作为目标颜色的,那么B物体就是源颜色,还记得我们设定的源因子是源颜色向量的ALPHA吗?由于是透明物体,所以一般ALPHA会很小,那么目标因子1-ALPHA便会很大,这将直接导致目标颜色占比很大,而源颜色占比很小,所以混合后的效果便是在A后的物体B消失了,就像没被渲染出来一样。所以为了防止这种情况的发生,我们需要先将透明物体根据物体到摄像机的距离从远到近排序,然后先渲染出远处物体,再渲染出近处物体。
最后渲染出来的图像就是这样的:
面剔除
一个非透明的立体模型是不能被我们看到所有的面,例如一个立方体我们最多只能看到它的三个面,另外三个面会在深度测试的时候被忽略掉。但是我们知道,在深度测试之前,那些看不见的面也会经过光栅化然后再经过片段着色器,这个过程实际上是很浪费性能且没有意义的,因为最终都会在深度测试的时候被忽略掉。那么我们是否可以直接不去管这些我们看不到的面以达到节省性能的目的?这就是面剔除。
但是如何确定哪些面我们看的到呢?实际上我们只需先确定一个正面即可。如果我们把逆时针顶点环绕的方向定义为正,顺时针为反面
那么我们从某个方向观察的时候,可以看到的面环绕顺序还是逆时针,但是看不到的面将会变为顺时针,就像这样
这里还要补充的是,openGL是如何判断顺时针和逆时针的呢?其实方法也很简单,就是利用了向量的叉乘(crossing),例如上图,我们只要把向量12和向量23做叉乘,然后规定向外或向内为正,这样就可以判断是顺时针还是逆时针了。
开启面剔除也很简单,和开启深度测试,模板测试,混合都是一样的,就是利用glEnable()
,面剔除对应的关键词是GL_CULL_FACE
。面剔除不仅可以剔除背面还可以剔除正面,或者是正反两面都剔除,这里用glCullFace()
来修改,参数GL_BACK
、GL_FRONT
、GL_FRONT_AND_BACK
分别对应着剔除背面、剔除正面和正反都剔除。除此我们还可以定义顺时针还是逆时针为正面,使用glFrontFace()
来修改,参数GL_CCW
和GL_CW
分别对应着逆时针(Counter Clockwise)和顺时针(Clockwise)。