今天通过一个小案例,来讲解OpenGL的正背面剔除以及深度测试的应用。
圆环的绘制
我们使用GLTools
中的void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor)
方法,简单实现一下圆环的绘制。
代码实现
//
// main.cpp
// OpenGLDemo
//
// Created by SK on 2020/7/3.
// Copyright © 2020 SK. All rights reserved.
//
#include "GLShaderManager.h"
#include "GLTools.h"
#include <GLUT/GLUT.h>
#include "GLMatrixStack.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLGeometryTransform.h"
//定义一个,着色管理器
GLShaderManager shaderManager;
GLFrame viewFrame;
GLFrustum viewFrustum;
GLTriangleBatch torusBatch;
GLMatrixStack modelViewMatix;
GLMatrixStack projectionMatrix;
GLGeometryTransform transformPipeline;
void changeSize(int w, int h) {
glViewport(0, 0, w, h);
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 100.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
}
void renderScene(void) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
GLfloat vRed[] = {1.0f, 0.0f, 0.0f, 1.0f};
modelViewMatix.PushMatrix(viewFrame);
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
torusBatch.Draw();
modelViewMatix.PopMatrix();
glutSwapBuffers();
}
void specialKeys(int key, int x, int y) {
if (key == GLUT_KEY_UP)
viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f);
if (key == GLUT_KEY_DOWN)
viewFrame.RotateWorld(m3dDegToRad(5.0), 1.0f, 0.0f, 0.0f);
if (key == GLUT_KEY_LEFT)
viewFrame.RotateWorld(m3dDegToRad(-5.0), 0.0f, 1.0f, 0.0f);
if (key == GLUT_KEY_RIGHT)
viewFrame.RotateWorld(m3dDegToRad(5.0), 0.0f, 1.0f, 0.0f);
glutPostRedisplay();
}
void setupRC() {
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
shaderManager.InitializeStockShaders();
viewFrame.MoveForward(7.0);
gltMakeTorus(torusBatch, 1.0f, 0.3f, 60, 30);
glPointSize(4.0f);
}
int main(int argc, char *argv[]) {
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
glutInitWindowSize(500, 500);
glutCreateWindow("OpenGL Demo");
glutReshapeFunc(changeSize);
glutDisplayFunc(renderScene);
glutSpecialFunc(specialKeys);
GLenum err = glewInit();
if (err != GLEW_OK) {
fprintf(stderr, "glew error: %s\n", glewGetErrorString(err));
return 1;
}
setupRC();
glutMainLoop();
return 0;
}
实现的效果如下图所示:
看起来是一个很漂亮的圆环,当我们缓慢的转到圆环,我们会看到下面的情况:
No,为什么会有这些黑色的出行?为什么开始显示的好好的,一转动就出现了这样的情况?
其实在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的,哪些部分是对观察者不可见的。对于不可见的部分,应该及早丢弃。例如在一个不透明的墙壁后,就不应该渲染。这种情况叫做隐藏面消除(Hidden surface elimination)
。
消除隐藏面的解决方案
油画算法
先绘制场景中的离观察者较远的物体,在绘制较近的物体。
例如下面的图例:先绘制红色部分,在绘制黄色部门,最后绘制灰色部门,即可解决隐藏面消除的问题。
油画算法的弊端
如果场景里的物体出现了交叉叠加的情况,油画算法将无法处理。
正背面剔除(Face Culling)
当我们从任意一个方向去观察一个3D图形,最多可以看到几个面?答案是最多3个面,从一个立方体的任意位置和方向上看,不可能看到多余3个面,那么我们在绘制的过程中,为何要多余的绘制那些看不到的面呢?如果我们能以某种方式去丢弃这部分数据,OpenGL在渲染的性能上可以提高50%。
OpenGL可以做到检查所有正面朝向观察者的面,并渲染它们,从而丢弃背面朝向的面。这样可以节约片元着色器的性能。
正面和背面是有三角形的顶点定义顺序和观察者⽅向共同决定的。随着观察者的⻆度方向的改变,正面背面也会跟着改变。
在OpenGL中开启正背面剔除的相关方法如下:
//开启表面剔除(默认背面剔除)
void glEnable(GL_CULL_FACE);
//关闭表面剔除(默认背面剔除)
void glDisable(GL_CULL_FACE);
//用户选择剔除那个面(正面/背面)
//mode参数为: GL_CW,GL_CCW,默认值:GL_CCW
void glCullFace(GLenum mode);
//mode参数为: GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,默认GL_BACK 用户指定绕序那个为正面
void glFrontFace(GLenum mode);
//剔除正面实现例1
glCullFace(GL_BACK);
glFrontFace(GL_CW);
//剔除正⾯实现例2
glCullFace(GL_FRONT);
我们开启正背面剔除功能,来看一下实现效果:
开启了正背面剔除功能,我们已经基本的达到了预期的展示效果,但是当我们继续旋转时发现,出现了新的问题,如何解决这样的问题呢?这就引出了一个新的概念 深度测试
深度测试
那么什么是深度?
深度 其实就是该像素点在3D世界中距离摄像机的距离,也就是Z
值。
深度缓存区就是一块内存区域,专门存储着每个像素点(绘制在屏幕上的)深度值。深度值(Z值)越⼤, 则离摄像机就越远。
在不使⽤深度测试的时候,如果我们先绘制⼀个距离比较近的物体,再绘制距离较远的物体,则距离远的位图因为后绘制,会把距离近的物体覆盖掉。有了深度缓冲区后,绘制物体的顺序就不那么要了。实际上,只要存在深度缓冲区,OpenGL 都会把像素的深度值写入到缓冲区中。除⾮调⽤ glDepthMask(GL_FALSE)
来禁⽌写⼊。