1. OpenGL中的正面、背面(隐藏面)

八、隐藏面消除解决方案 - 图1

  • 在上图右侧三角形中,线条是从 V0 ->V1 -> V2 -> V0 沿着顶点顺时针方向形成⼀个闭合三角形。 这种顺序与方向结合来指定顶点的⽅式称为环绕
  • 环绕方式由两种:一种是顺时针环绕,一种是逆时针环绕
  • 在 OpenGL 中默认具有逆时针⽅向环绕的多边形为正面。当我们从正面看屏幕上的这个图时,左侧三角形展示的是正面,右侧三角形展示的是背面。假设我们绕到这个图的背面去看时,顺时针和逆时针的方向就变了。 所以如555555t是否为正面,是由环绕方向及观察者方向共同决定的。
  • 这种默认可以更改,更改代码如下。但是由于大家都习惯逆时针方向为正面,所以不建议更改。
  1. glFrontFace(GL_CW);
  2. GL_CW: 设置OpenGL 顺时针环绕的多边形为正面;
  3. GL_CCW: 设置OpenGL 逆时针环绕的多边形为正面

2. 为什么要区分正面、背面

在图形渲染过程中,我们能看到的正面会渲染出颜色,背面黑色。区分了正背面后,可以将背面剔除,以某种方式丢弃这部分数据,不渲染背面,可以提高至少 50% 的渲染性能。

3. 隐藏面消除解决方案

3.1 油画算法

  • 先渲染场景中离观察者较远的图层,再渲染较近的图层。
  • 下图中有多个图层依次叠加在一起,遵循油画算法,先渲染红色图层、再绘制黄色图层、最后渲染灰色图层。这样叠加在一起,可以自然的将背面藏起来,让大家看不到。

八、隐藏面消除解决方案 - 图2

3.1.1 油画算法存在的问题

  • 这种方法是非常低效的,因为对于图层重叠的每个像素都要进行多次渲染。
  • 如果图层之间,不是平行的一层一层的叠加,而是交叉叠放时, 没有明确的先后顺序,油画算法就不能解决问题了。如下图这样:
    八、隐藏面消除解决方案 - 图3

3.2 使用正面、背面剔除

  1. //1.开启正背面剔除
  2. glEnable(GL_CULL_FACE);
  3. //2.关闭正背面剔除
  4. glDisable(GL_CULL_FACE);
  5. //3. 指定剔除面:mode 参数的可用值为GL_FRONT、GL_BACK、GL_FRONT_AND_BACK
  6. glCullFace(GLenum mode);

3.2.1 代码实践

我们通过一个 3D 的甜甜圈案例,来看看正背面剔除的效果。

1. 首先画一个 3D 甜甜圈.

OpenGL有一个甜甜圈的批次类,可以直接供给我们使用。

  1. // 4.创建一个甜甜圈批次类
  2. //void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
  3. //参数1:GLTriangleBatch 容器帮助类
  4. //参数2:外边缘半径
  5. //参数3:内边缘半径
  6. //参数4、5:主半径和从半径的细分单元数量
  7. gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);

2. 通过上下左右键位,控制甜甜圈旋转

  1. //键位设置,通过不同的键位对其进行设置
  2. //控制Camera的移动,从而改变视口
  3. void SpecialKeys(int key, int x, int y)
  4. {
  5. //1.判断方向
  6. if(key == GLUT_KEY_UP)
  7. //2.根据方向调整观察者位置
  8. viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f);
  9. if(key == GLUT_KEY_DOWN)
  10. viewFrame.RotateWorld(m3dDegToRad(5.0), 1.0f, 0.0f, 0.0f);
  11. if(key == GLUT_KEY_LEFT)
  12. viewFrame.RotateWorld(m3dDegToRad(-5.0), 0.0f, 1.0f, 0.0f);
  13. if(key == GLUT_KEY_RIGHT)
  14. viewFrame.RotateWorld(m3dDegToRad(5.0), 0.0f, 1.0f, 0.0f);
  15. //3.重新刷新
  16. glutPostRedisplay();
  17. }

3. 旋转后重绘甜甜圈

  1. //渲染场景
  2. void RenderScene()
  3. {
  4. //1.清除窗口和深度缓冲区
  5. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  6. //2.把摄像机矩阵压入模型矩阵中
  7. modelViewMatix.PushMatrix(viewFrame);
  8. //3.设置绘图颜色
  9. GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
  10. //使用默认光源着色器
  11. //通过光源、阴影效果跟提现立体效果
  12. //参数1:GLT_SHADER_DEFAULT_LIGHT 默认光源着色器
  13. //参数2:模型视图矩阵
  14. //参数3:投影矩阵
  15. //参数4:基本颜色值
  16. shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
  17. //5.绘制
  18. torusBatch.Draw();
  19. //6.出栈 绘制完成恢复
  20. modelViewMatix.PopMatrix();
  21. //7.交换缓存区
  22. glutSwapBuffers();
  23. }

4. 运行效果

  • 我们绘制的是红色的甜甜圈,在OpenGL中,正面绘制的是我们设置的颜色,背面绘制的黑色。
  • 最初展示时,我们看到的是正面红色,这是正确的。但是随着观察者旋转,展示出红黑相间的样子,最终转到背面时,展示全黑色。
  • OpenGL 正面背面都绘制时,显示的效果就错乱了。
    八、隐藏面消除解决方案 - 图4

5. 对甜甜圈进行背面剔除

对甜甜圈进行背面剔除,背面看不到就不绘制。

  1. // 开启背面剔除
  2. glEnable(GL_CULL_FACE);
  3. //5.绘制
  4. torusBatch.Draw();
  5. // 关闭背面剔除
  6. glDisable(GL_CULL_FACE);

八、隐藏面消除解决方案 - 图5

不过看起来甜甜圈还有些问题,旋转到侧面时,看起来在上半部分或者下半部分缺少了一个块。为什么呢?

  • 这是因为整个甜甜圈,我们能看到的都是正面。如下图中的ABCD四个区域,都是正面。当甜甜圈旋转到侧面对着我们时,AB区域就重合了。但是这两个都是正面,哪个该显示,哪个不该显呢?
  • 我们直观的看上去,知道应该显示的A区,因为A区离我们更近,所以在重叠时,A区应该挡住B区。
  • 但是OpenGL它不知道哪个离我们近呀。应该怎么让它知道呢?这个时候,我们就需要借助深度测试了。下篇文章中讲解

八、隐藏面消除解决方案 - 图6