OpenGL 阐述隐藏面消除解决方案
在OpenGL中如果没有设置隐藏背面,就会出现下面这种情况,正面红色和背面黑色同时被绘制了出来,OpenGL并不知道先绘制背面还是正面。这明显不符合我们的预期。
我们需要对背面进行消除,这叫做“隐藏面消除(Hidden surface elimination)”
目前有两种解决方案:
1. 先绘制背面后绘制正面(油画算法)
2. 只画正面(正背⾯面剔除(Face Culling))
第一种(油画算法)
油画算法有一条规则: 先绘制场景中距离观察者远的物体,再绘制近的物体。
图中绘制顺序:红色->换色->灰色
有顺序的绘制就能解决同时绘制背面和正面的问题。
油画法的弊端
如果出现图形叠加的情况,油画法就显得力不从心。
例如下图:到底先画哪个图形呢?
接下来考虑第二种方法。
第二种(背面剔除)
正背面剔除有一条规则:看不到的不画!
想要做到看不到的不画,需要解决以下问题:
- OpenGL如何知道哪个面看得到,哪个面看不到?
答案是:通过分析顶点数据的顺序
顶点绘制的顺序不同就决定了是顺时针绘制还是逆时针绘制(下图)
GLfloat vertices[] = {
//顺时针
vertices[0], // vertex 1
vertices[1], // vertex 2
vertices[2], // vertex 3
// 逆时针
vertices[0], // vertex 1
vertices[2], // vertex 3
vertices[1] // vertex 2
};
有了顺时针和逆时针,我们就可以通过观察者的所在位置,确定正面与背面:
- 当观察者在右侧时,右边的三角形方向为逆时针方向则为正⾯面,而左侧三⻆形为顺时针则为背⾯。
- 当观察者在左侧时,左边的三⻆角形为逆时针⽅方向,判定为正⾯,⽽右侧的三角形为顺时针判定为背⾯面
这样就能轻而易举的知道正面和背面了。
代码中如何实现
// 开启表面剔除(默认背面剔除)
void glEnable(GL_CULL_FACE);
// 关闭表面剔除(默认背面剔除)
void glDisable(GL_CULL_FACE);
/*
* 用户选择剔除那个面(正⾯/背面)
* mode : GL_FRONT(正⾯),GL_BACK(背面),GL_FRONT_AND_BACK (一起),默认GL_BACK
*/
void glCullFace(GLenum mode);
/*
* 用户指定绕序那个为正面
* mode: GL_CW(顺时针),GL_CCW(逆时针),默认值:GL_CCW
*/
void glFrontFace(GLenum mode);
例如:开启背面剔除
void SetupRC(){
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
}
成功了!💐💐💐
我们在这里解决了背面绘制的问题,但于此同时又出现了另外一个问题: 出现了一个窟窿
为了解决这个问题,我们需要了解“深度”
这是个什么问题
深度
-
什么是深度(z值)?
深度是物体的像素点在3d世界中,距离摄像机的距离,z值越大距离越大。 -
什么是深度缓冲区(DEPTH_BUFFER)?
深度缓冲区是一块内存区域,用于缓存每个像素点的深度值。 -
为什么需要深度(DEPTH_BUFFER)?
在不使用深度测试的时候,如果我们先绘制一个距离较近的物体,再绘制距离较远的物体,则距离远的物体因为后绘制,会把距离近的物体覆盖掉,这样的效果并不是我们所希望的。而有了深度缓冲以后,绘制物体的顺序就不那么重要了,都能按照远近(Z值)正常显示,这很关键。 -
深度缓冲区原理
- 深度缓冲区原理就是把一个距离观察平面(近裁剪面)的深度值(或距离)与窗口中的每个像素关联起来。
- 首先,使用glClear(GL_DEPTH_BUFFER_BIT),把所有像素的深度值设置为最大值(一般是远裁剪面)。
- 然后,在场景中以任意次序绘制所有物体。硬件或者软件所执行的图形计算把每一个绘制表面转换为窗口上一些像素的集合,此时并不考虑是否被其他物体遮挡。
- 其次,OpenGL会计算这些表面和观察平面的距离。如果启用了深度缓冲区,在绘制每个像素之前,OpenGL会把它的深度值和已经存储在这个像素的深度值进行比较。新像素深度值<原先像素深度值,则新像素值会取代原先的;反之,新像素值被遮挡,他颜色值和深度将被丢弃。
- 为了启动深度缓冲区,必须先启动它,即glEnable(GL_DEPTH_TEST)。每次绘制场景之前,需要先清除深度缓冲区,即glClear(GL_DEPTH_BUFFER_BIT),然后以任意次序绘制场景中的物体。
-
深度测试
深度缓冲区的作用就是区分颜色所在的层次,防止把被遮挡住的颜色显示出来。
当一个像素第二次被绘制时, 例如当一个物体在另一个物体之后被绘制,深度缓冲要么保留前面的深度值,要么使用第二个像素的深度值替换当前深度值。保留或抛弃哪个深度取决于你选择的深度函数。例如,如果当前深度函数是CompareFunction.LessEqual时,只有小于等于当前深度值的值才会被保留,而大于当前深度值的值会被抛弃。这叫做深度测试,每次绘制像素时都会进行深度测试。当对一个像素进行深度测试时,它的颜色会被写入渲染目标,而深度被写入深度缓冲。
上面我们出现了一个窟窿,原因就是物体的深度未知的,后面的像素由于是后面才绘制出来的,前面绘制的像素背后面的像素所覆盖,所以导致我们看到了后面的窟窿。我们开启深度测试修复这个问题:
- 启用深度测试
// 在默认情况是将需要绘制的新像素的z值与深度缓冲区中对应位置的z值进行比较,
// 如果比深度缓存中的值小,那么用新像素的颜色值更新帧缓存中对应像素的颜色值。
glEnable(GL_DEPTH_TEST);
/*
* mode :GL_NEVER(用不处理)、GL_ALWAYS(处理所有)、GL_LESS(小于)、GL_LEQUAL(小于等于)、GL_EQUAL(等于)、GL_GEQUAL(大于等于)、GL_GREATER(大于)或GL_NOTEQUAL(不等于),其中默认值是GL_LESS。
* 普遍使用GL_LEQUAL 来表达一般物体之间的遮挡关系。
*/
glDepthFunc(GLEnum mode);
// 在绘制场景前,清除颜色缓存区,深度缓冲
// 清除深度缓冲区默认值为1.0,表示最⼤大的深度值,深度值的范围为(0,1)之间,
// 值越⼩小表示越靠近观察者,值越⼤大表示 越远离观察者
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
例如:
void RenderScene(void){
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
}
开启深度测试后,甜甜圈的窟窿没有了。💐💐💐
结束
到此甜甜圈的效果已经和我们想看到的样子非常像了。谢谢阅文。