OpenGL(六)-- 渲染技巧:正背面剔除、深度测试、多边形偏移、颜色混合
通过一个基础案例来了解这些渲染技巧:正背面剔除、深度测试、多边形偏移。应该更容易理解。
案例
通过使用系统几何图形,绘制并移动图形。下面放出核心代码。
代码语言:javascript复制void RenderScene(){
...
//把摄像机矩阵压入模型矩阵中
modelViewMatix.PushMatrix(cameraFrame);
//使用默认光源着色器
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
}
void SetupRC(){
...
//观察者向后移动
objectFrame.MoveForward(-10.0);
//创建圆环
gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
}
void ChangeSize(int w, int h){
...
//创建透视矩阵
viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 100.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//创建渲染管线
transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
//绘制
torusBatch.Draw();
//出栈 绘制完成恢复
modelViewMatix.PopMatrix();
//交换缓存区
glutSwapBuffers();
}
void SpecialKeys(int key, int x, int y){
...
//根据方向调整观察者位置
if(key == GLUT_KEY_UP)
cameraFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f);
...
}
int main(int argc, char* argv[]){
...
glutReshapeFunc(ChangeSize); //创建窗口回调
glutSpecialFunc(SpecialKeys); //创建键盘事件
glutDisplayFunc(RenderScene); //创建渲染回调
SetupRC()
}
在你认为大功告成的时候,发现问题并不简单。
正背面剔除
移动后发现会有没有上色的部分,代码并没有问题。仔细观察后发现黑色部分是因为OpenGL认为那是你看不到的地方是隐藏⾯
所以没有绘制。
举例: 一个苹果放在桌上,你不可能一眼看到苹果的所有面,你看到的相当于图中的有色一面,你看不到的部分就相当于黑色一面。
放到OpenGL里虽然作为观察者已经移动到隐藏⾯了,但是OpenGL还是认为它还是隐藏⾯
也就是背面
,不需要绘制,这就造成了我们看到的一幕。这就是OpenGL中的隐藏⾯消除
。
移动后的黑色是因为使用的光源着色器
,使隐藏面可以观察到。即使不使用光源着色器,虽然察觉不到隐藏⾯,但是隐藏⾯消除问题依旧存在。如果小伙伴有更好的观察方式也可以私信我。
OpenGL中的正面、背面
上文中提到了一个概念背面
。
正面
背面
都是OpenGl人为定义的概念。
- 正面:点的绘制顺序是:
逆时针
- 背面:点的绘制顺序是:
顺时针
就像图中右侧三角形(图元)代表的是正面,左侧代表的是背面。 有一个很容易记忆的方式: 右手握拳后,如果绘制方向与手指方向一致则为正面,反之
正背面剔除
在了解正背面剔除
之前,先了解一下OpenGL是如何绘制3D图形的,我们所知的油画算法
在绘制下图这种情况时就派不上用场了,因为相互叠加无法区分图层的先后,所以OpenGl选择了正背面剔除
的渲染方式。
- 正背面剔除:只绘制我们可以观察到的面,这样做及解决了优化算法的问题,而且在渲染的性能即可提⾼高超过50%
使用正背面剔除的方式:
代码语言:javascript复制void RenderScene(){
...
//开启表⾯面剔除(默认剔除背面)
void glEnable(GL_CULL_FACE);
//关闭表⾯面剔除(默认背⾯面剔除)
void glDisable(GL_CULL_FACE);
}
很显然影藏面的问题已经解决,但是却发现了新问题,在某个角度下少了一块。
深度测试
在解决了隐藏面问题的同时,却引来了一个新的问题,先分析一下问题的成因。
从现在这个角度观察,图中的A、B面都是正面
,而我们有开启了正背面剔除。导致OpenGl又不知道要绘制哪个面了,所以在某个角度下出现了绘制的错乱。
首先我们通过生活经验来思考,如果出现2个正面重叠的情况时,应该显示的是距离我们更近的那一部分,因为远的那一部分被遮挡了,相当更远的那一部分成了“隐藏面”绘制时应该被放弃。
- 在3D模型中,距离观察者的距离表示为:
深度
。其实就是该像素点在3D世界中距离摄像机的距离,Z值。。 - 所以在绘制之前需要知道每个点距离观察者的距离,而存放计算结果的区域叫做:
深度缓冲区
。
当然这个深度可以简单总结为: 观察者在Z轴正方向, 图形的Z值越大。表示距离观察者越近 观察者在Z轴负方向, 图形的Z值越大。表示距离观察者越远 但是如果只是这样简答表示,当图形的Z值相同时又会有问题出现。
所以在OpenGL中深度值是这样计算的:
far、near是提供投影矩阵设置时使用的可见视图截锥的远近值。公式中的Z值也是矩阵变换后的值。 从公式中可以发现2点:
- 深度值在【0,1】之间
- 值越⼩小表示越靠近观察者,值越⼤大表示远离观察者
整个过程就叫做深度测试(Z-buffer)
。
- 相对应的颜色缓冲区和深度缓存区是一一对应的。在进行
深度测试
的时候,深度值比较大的会被丢弃,相同的颜色缓冲区也会跟着进行修改。以保证深度缓存区和颜色缓存区中是同一个点的信息。 - 通过测试利用深度测试在解决深度问题时还同时解决了隐藏面消除的问题,因为隐藏面一定是远离观察者的。
开启深度测试的方式:
代码语言:javascript复制void RenderScene(){
...
//开启前进行重操作,重置后默认值为1.0,表示最大深度
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//开启表⾯面剔除(默认剔除背面)
void glEnable(GL_DEPTH_TEST);
//关闭表⾯面剔除(默认背⾯面剔除)
void glDisable(GL_DEPTH_TEST);
}
也可以通过glDepthFunc(GLenum func)
来修改深度测试规则,但是一般情况下很少进行修改.
多边形偏移
如果仔细思考深度问题的解决方案就会发现,既然是进行数值的比较,就会有相同、两者非常接近的情况出现。就会导致下图中的问题。
这个现象在游戏中非常常见,这种问题就叫做Z-fighting
Z-fighting出现情况
- 数值相同:这个很好理解
- 两数非常接近:如果在一个8位系统下一个值为:0.12345671,一个值为0.12345679,而系统中会表示为:0.1234567,这样就会造成相等的情况出现。
解决Z-fighting
当然OpenGL也帮我们想到了,并给出了解决方案多边形偏移
,顾名思义就是对深度相同的物体进行微妙的移动。
//1,开启多边形偏移
glEnable(GL_POLYGON_OFFSET_FILL)
绘制模式对应的参数列表:
绘制模式 | 参数 |
---|---|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) | GL_POLYGON_OFFSET_FILL |
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); | GL_POLYGON_OFFSET_LINE |
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); | GL_POLYGON_OFFSET_POINT |
//2,指定偏移量
glPolygonOffset(GLfloat factor, GLfloat units)
//一般设置为glPolygonOffset(-1,-1)
此API代表了公式:OffSet = (m * factor) (r * units)
- m代表了:多边形的深度的斜率最大值,理解为一个多边形越是与近裁剪面平行,m就越接近于0
- r代表了:能产生空口坐标系的省渎职可分辨的差异最小值,r是一个OpenGl定义的常量,可以理解为当前系统的最小精度。
一般设置为(-1,-1)即可解决大部分问题。该参数为负值时表示Z轴离我们越近,反正。
代码语言:javascript复制//3,记得关闭
glDisable(GL_POLYGON_OFFSET_FILL)
如何预防
可以在开发初期进行以下3中手段来预防:
- 避免两物体靠的过近,毕竟开启多边形便宜是需要消耗性能的。
- 让观察者尽量远近裁剪面,这个位置可以回头看看深度测试的那个公式,也就是将公式中的near变大,近异步提高计算精度。
- 使用更高位数的深度缓存区。现在的计算机精度更高,所以这一条无形的帮我们避免大部分同类问题。
扩展
当然还可以利用多边形偏移
来对我们绘制的多边形增加边框,具体实现的核心代码。
// 偏移深度
glPolygonOffset(-1.0f, -1.0f);
glEnable(GL_POLYGON_OFFSET_LINE);
// 开启抗锯齿锯齿,让黑边更顺滑
glEnable(GL_LINE_SMOOTH);
//绘制线
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//绘制
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
pBatch->Draw();
//关闭
glDisable(GL_POLYGON_OFFSET_LINE);
glDisable(GL_LINE_SMOOTH);