004 --- 正背面剔除、深度测试、多边形模式
前言
默认情况下,我们渲染的每一个
点,线或者三角形
都会在屏幕上进行光栅化,并且按照组合图元批次指定的顺序进行排列.
这个在某些情况下会产生问题,其中可能出现的一个问题就是:
- 如果我们绘制一个由很多个三角形组成的实体,那么第一个绘制的三角形有可能被后面绘制的三角形覆盖.
- 例如,如果绘制一个类似于甜甜圈的图形,其中一些三角形在甜甜圈的背面,一些在正面.
对于上面问题,一个可能解决的办法:油画法
.首先渲染那些较远的三角形,再在他们的的上方渲染那些较近的三角形.但是这个办是很低效的.
- 我们必须对任何发生几何图形重叠的地方每个像素进行
两次操作
,而且储存其中进行读写操作会使速度变慢
. - 对独立的三角形进行排序的开销会过高.
绘制甜甜圈
- 常用类
GLFrame viewFrame; //参考帧
//使用GLFrustum类来设置透视投影
GLFrustum viewFrustum; //透视投影
GLTriangleBatch torusBatch; //批次容器
GLMatrixStack modelViewMatix; // 模型栈
GLMatrixStack projectionMatrix; //投影栈
GLGeometryTransform transformPipeline; //管线
GLShaderManager shaderManager; //着色管理器
- RenderScene
- 清屏
- 将viewFrame压栈
- 设置图形颜色
- 默认光源着色器
- 绘制
- 出栈
- 交换缓存区
void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
modelViewMatix.PushMatrix(viewFrame);
GLfloat vRed[] = { 1.0f, 0.0f, 1.0f, 1.0f };
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
torusBatch.Draw();
modelViewMatix.PopMatrix();
glutSwapBuffers();
}
- SetupRC
- 设置屏幕颜色
- 初始化着色管理器
- 参考帧往后移动
- 绘制甜甜圈
gltMakeTorus
系统`给出绘制API
void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
- 参数1:GLTriangleBatch 容器帮助类
- 参数2:外边缘半径
- 参数3:内边缘半径
- 参数4、5:主半径和从半径的细分单元数量
- 设置线宽
void SetupRC()
{
glClearColor(0.0f, 0.3f, 0.3f, 1.0f );
shaderManager.InitializeStockShaders();
viewFrame.MoveForward(7.0);
gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
glPointSize(4.0f);
}
- SpecialKeys
objectFrame,观察动,物体不动
void SpecialKeys(int key, int x, int y)
{
//1.判断方向
if(key == GLUT_KEY_UP)
//2.根据方向调整观察者位置
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);
//3.重新刷新
glutPostRedisplay();
}
- ChangeSize
void ChangeSize(int w, int h)
{
//1.防止h变为0
if(h == 0)
h = 1;
//2.设置视口窗口尺寸
glViewport(0, 0, w, h);
//3.setPerspective函数的参数是一个从顶点方向看去的视场角度(用角度值表示)
// 设置透视模式,初始化其透视矩阵
viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 100.0f);
//4.把透视矩阵加载到透视矩阵对阵中
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//5.初始化渲染管线
transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
}
- 绘制后的甜甜圈
正面和背面剔除
如果,甜甜圈出现互相遮挡的问题.
image.png如果以我们看见的角度为正面
,看不见的为背面
,也就是说我们只要渲染看见的那部分就可以了。
对于正面和背面进行区分的原因之一就是为了进行剔除.
OpenGL
可以做到检查所有正面朝向观察者的面
,并渲染它们
,背后朝向的面则不再渲染,从而节约片元着色器的性能.
1 .添加一个右键单击的示例程序
- 定义两个int型变量用来记录一些操作状态,深度测试用于解决其他问题
//标记:背面剔除、深度测试
int iCull = 0;
int iDepth = 0;
- 在main函数添加如下代码
glutCreateMenu(ProcessMenu);
glutAddMenuEntry("Toggle depth test",1); //深度测试
glutAddMenuEntry("Toggle cull backface",2); //正背面剔除
glutAddMenuEntry("Set Fill Mode", 3);
glutAddMenuEntry("Set Line Mode", 4);
glutAddMenuEntry("Set Point Mode", 5);
glutAttachMenu(GLUT_RIGHT_BUTTON);
- ProcessMenu 根据iCull,做出响应操作
void ProcessMenu(int value)
{
switch(value)
{
case 1:
iDepth = !iDepth;
break;
case 2:
iCull = !iCull;
break;
case 3:
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
break;
case 4:
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
break;
case 5:
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
break;
}
glutPostRedisplay();
}
-
iCull
来判断开启正背面剔除还是关闭
if (iCull) {
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glCullFace(GL_BACK);
}else
{
glDisable(GL_CULL_FACE);
}
正背面剔除相关代码
- 开启表面剔除(默认背面剔除)
void glEnable(GL_CULL_FACE);
- 关闭表面剔除(默认背面剔除)
void glDisable(GL_CULL_FACE);
- 用户选择剔除哪个面(正面/背面)
mode参数为: GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,默 认GL_BACK
-
GL_FRONT
: 剔除正面 -
GL_BACK
: 剔除背面,是默认值 -
GL_FRONT_AND_BACK
: 剔除正背面
void glCullFace(GLenum mode);
- 用户指定绕序哪个为正⾯
mode参数为: GL_CW,GL_CCW,默认值:GL_CCW
-
GL_CW
: 顺时针 -
GL_CCW
: 逆时针,是默认值
glFrontFace(GL_CW);
- 例如,剔除正面实现(1)
glCullFace(GL_BACK);
glFrontFace(GL_CW);
- 例如,剔除正⾯面实现(2)
glCullFace(GL_FRONT);
设置背面剔除之后效果
image.png |
|
image.png
深度测试
深度测试是另外一种高效消除隐藏表面的技术
.
在绘制一个像素时,讲一个值(
z值
)分配它,这个值表示它到观察者的距离
.然后,另外一个像素需要在屏幕上的同样位置进行绘制
时,新像素的z值
将与已经存储的像素的z值
进行比较.如果新像素的z值
比较大,那么它的距离观察者就比较近,这就就在原来的像素上面,所以原来的像素就会被新的像素覆盖
.如果新的像素z值
更低,那么它就必须位域原来的像素后面
,不能遮住原来的像素,在内部,这个任务是通过深度缓冲区实现的,它存储了屏幕上每个像素的深度值.
- 注意点
观察者可以放在坐标系的任意位置,所以,不能简单的说z数值越大或越小,观察者就越靠近或物体.
- 如果观察者在
z轴
的正方形,z
值越大则靠近观察者. - 如果观察者在
z轴
的负方向,z
值越小则靠近观察者.
- 如图,自身重叠的甜甜圈在不使用深度测试情况下,OpenGL不能分清哪个图层在前,哪个图层在后,会出现天天被啃了一口的现象.
启用深度测试
在使用GLUT设置OpenGL窗口时,应该请求一个深度缓冲区,按如下方式申请一个颜色缓冲区和一个深度缓冲区.
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
要启用深度测试:
glEnable(GL_DEPTH_TEST);
如果没有深度缓冲区,那么启用深度测试的命令将被忽略.
关闭深度测试:
glDisable(GL_DEPTH_TEST);
- 在右键单击窗口增加选择
Toggle depth test
(开启深度测试),这样会调用glEnable
,开启深度测试.
深度缓冲区
总结
深度测试在绘制多个对象是能进一步解决性能问题,油画法
的绘制形式对于图形硬件来说,会导致同一片区域重复进行绘制,而且每次绘制都产生性能开销.如果开销过大则导致光栅化过程变慢,这种方式被称为填充受限
.所以可以将油画法
颠倒过来使用,会加速填充性能,首先绘制那些较近的对象,然后在绘制那些较远的对象. 深度测试将消除那些对那些应该被已存在的像素覆盖的像素,这将节省可观的存储器带宽.
多边形模式
多边形(含三角形)不一定是实心的.在默认情况下,多边形作为实心图形绘制的,但是我们可以通过将多边形指定显示为显示轮廓或只有点(值显示顶点)来改变这种行为.函数glPolygonMode
允许将多边形渲染成实体,轮廓或只有点.
- glPolygonMode
void glPolygonMode(GLenum face,GLenum mode);
-
face
这个参数确定显示模式将适用于物体的哪些部分,控制多边形的正面和背面的绘图模式:
GL_FRONT
:表示显示模式将适用于物体的前向面(也就是物体能看到的面)
GL_BACK
:表示显示模式将适用于物体的后向面(也就是物体上不能看到的面)
GL_FRONT_AND_BACK
:表示显示模式将适用于物体的所有面 -
mode
这个参数确定选中的物体的面以何种方式显示(显示模式):
GL_POINT
:表示只显示顶点,多边形用顶点显示
GL_LINE
:表示显示线段,多边形用轮廓显示
GL_FILL
:表示显示面,多边形采用填充形式
- 将多边形正面和背面设为线框模式,就能实现这种线框渲染.
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
image.png
- 将多边形绘制为点的模式.
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
image.png