在渲染过程中可能产生的问题
- 在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的,或者是哪些部分是对观察者不可见的,对于不可见的部分,我们应该及早的丢弃。例如在一个不透明的墙壁后面,就不应该渲染,这种情况叫做
隐藏面消除
。甜甜圈的背面效果
解决办法隐藏面消除的办法:油画算法
油画算法
那么什么是油画算法呢?所谓的油画算法就是画家在画画的过程中,一般是先绘制场景中离观察者较远的物体,再绘制较近的物体。
例如下面的图例,先绘制红色的部分,再绘制黄色的部分,最后再绘制灰色的部分,即可解决隐藏消除的问题。
但是油画算法也有一定的弊端
,比如从上面我的已经知道了使用油画算法,只要将场景按照物理距离观察者的距离由远及近排序即可,那么使用油画算法会出现什么样的问题呢?例子:如果三个三角形是叠加的效果,优化算法将无法处理。

从上面的问题中,我们又有一种解决方案,那就是使用正背面剔除(Face Culling)
- 我们知道一个3d的图形,无论你从任何一个方向去观察,最多可看到不可能多余3个面,如果我们能以某种方式丢弃这部分的数据,OpenGL在渲染的性能即可提高超过50%。接下来面临着新的问题:如何知道某个面在观察者的视野中不会出现呢?
如何知道某个面在观察者的视野中不会出现呢
- 任何平面都是2个面,正面和背面。当某一个时刻观察时你只能看到一个面。
- OpenGL为我们提供了方法,能够直接检查所有的正面朝向观察者面,并渲染它们,从而丢弃背面朝向的面,这样可以节约片元着色器的性能。
OpenGL是如何知道哪个是正面哪个是背面的呢?
-
OpenGL通过分析顶点数据的顺序
顺时针(Clockwise)1->2->3
逆时针(Counter-clockwise)1->3->2
如何判断正面和背面
正面:按照逆时针顶点连接顺序的三角形面。
背面:按照顺时针顶点连接顺序的三角形面。
正面和背面判断相关的案例:分析立方体中的正背面

- 当观察者在右侧时,则右边的三⻆形⽅向为逆时针⽅向则为正⾯,⽽左侧的三⻆形为顺时针则为⾯
- 当观察者在左侧时,则左边的三⻆形为逆时针⽅向判定为正⾯,⽽右侧的三⻆形为顺时针判定为背⾯
总结:正面和背面是有三角形的顶点定义顺序和观察者方向共同决定的,随着观察者角度方向的改变,正背面也会跟着改变。
正背面剔除功能的代码示例
- 开启表面剔除(默认背面剔除)
void glEnable(GL_CULL_FACE);
- 关闭表面剔除(默认背面剔除)
void glDisable(GL_CULL_FACE);
- 用户选择剔除哪个面(正面/背面)
void glCullFace(GLenum mode); mode参数为: GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,默认GL_BACK
- 用户指定绕序哪个为正面
void glFrontFace(GLenum mode); mode参数为: GL_CW,GL_CCW,默认值:GL_CCW
- 例如剔除正面实现1
glCullFace(GL_BACK);
glFrontFace(GL_CW);
- 例如剔除正面实现2
glCullFace(GL_FRONT);
如何绘制一个甜甜圈,并使用正背面剔除功能
一、 窗口改变方法的实现过程核心:
- 1,设置透视模式,初始化其透视矩阵
viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 100.0f);
- 2, 把透视矩阵加载到透视矩阵对阵中
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
- 3,初始化渲染管线
transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
二、键位设置,通过不同的键位对其进行设置,控制camera的移动,从而改变视口,注意:观察者移动,物体不动
,在void SpecialKeys(int key, int x, int y)
方法中,
- 1,我们需要知道如何根据方向调整观察者的位置,这里以上键为例
viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f);
。 - 2,每次移动位置都需要重新渲染刷新,使用
glutPostRedisplay();
方法。
三、甜甜圈相关设置
- 1,使用
glClearColor()
设置背景颜色 - 2,初始化着色器管理器
shaderManager.InitializeStockShaders();
- 3, 使用
MoveForward(7.0);
设置肉眼到物体之间的距离,将相机向后移动7个单元。 - 4, 创建一个甜甜圈的方法:
-
void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
,该方法中 -
GLTriangleBatch& torusBatch
为容器帮助类; -
majorRadius
外边缘半径; -
minorRadius
内边缘半径; -
numMajor
主半径细分单元数量; -
numMinor
从半径的细分单元数量。 - 5,设置点的大小(方便点填充时,便于肉眼观察)
glPointSize(4.0f);
四、渲染场景
- 清除窗口和深度缓冲区
- 开启/关闭正背面剔除功能
glEnable(GL_CULL_FACE); glFrontFace(GL_CCW); glCullFace(GL_BACK);
- 把摄像机矩阵压入模型矩阵中
modelViewMatix.PushMatrix(viewFrame);
注意栈做什么用?记录当前的位置等状态,保存临时结果,回退就出栈
- 设置绘图颜色
- 使用默认光源着色器
GLT_SHADER_DEFAULT_LIGHT
- 开始使用
Draw
方法进行绘制 - 绘制完成,将压栈的状态进行
pop
操作,绘制完成恢复 - 使用
glutSwapBuffers();
交换缓存区
使用了正背面剔除和未使用正背面剔除的效果比较
-
未使用正背面剔除效果图
未使用正背面剔除.gif
-
使用了正背面剔除效果图
正背面剔除.gif
以下是详细代码
//演示了OpenGL背面剔除,深度测试,和多边形模式
#include "GLTools.h"
#include "GLMatrixStack.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLGeometryTransform.h"
#include <math.h>
#ifdef ____
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif
////设置角色帧,作为相机
GLFrame viewFrame;
//使用GLFrustum类来设置透视投影
GLFrustum viewFrustum;
GLTriangleBatch torusBatch;
GLMatrixStack modelViewMatix;
GLMatrixStack projectionMatrix;
GLGeometryTransform transformPipeline;
GLShaderManager shaderManager;
//标记:背面剔除、深度测试
int iCull = 0;
int iDepth = 0;
//渲染场景
void RenderScene()
{
//1.清除窗口和深度缓冲区
//可以给学员演示一下不清空颜色/深度缓冲区时.渲染会造成什么问题. 残留数据
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//开启/关闭正背面剔除功能
if (iCull) {
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glCullFace(GL_BACK);
}else
{
glDisable(GL_CULL_FACE);
}
//压栈:栈做什么用?记录当前的位置等状态,保存临时结果,回退就出栈
//2.把摄像机矩阵压入模型矩阵中
modelViewMatix.PushMatrix(viewFrame);
//3.设置绘图颜色
GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
//4.
//使用平面着色器
//参数1:平面着色器
//参数2:模型视图投影矩阵
//参数3:颜色
// shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed);
//使用默认光源着色器
//通过光源、阴影效果跟提现立体效果
//参数1:GLT_SHADER_DEFAULT_LIGHT 默认光源着色器
//参数2:模型视图矩阵
//参数3:投影矩阵
//参数4:基本颜色值
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
//5.绘制
torusBatch.Draw();
//6.出栈 绘制完成恢复
modelViewMatix.PopMatrix();
//7.交换缓存区
glutSwapBuffers();
}
void SetupRC()
{
//1.设置背景颜色
glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
//2.初始化着色器管理器
shaderManager.InitializeStockShaders();
//3.将相机向后移动7个单元:肉眼到物体之间的距离
viewFrame.MoveForward(7.0);
//4.创建一个甜甜圈
//void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
//参数1:GLTriangleBatch 容器帮助类
//参数2:外边缘半径
//参数3:内边缘半径
//参数4、5:主半径和从半径的细分单元数量
gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
//5.点的大小(方便点填充时,肉眼观察)
glPointSize(4.0f);
}
//键位设置,通过不同的键位对其进行设置
//控制Camera的移动,从而改变视口
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();
}
//窗口改变
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);
}
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();
}
int main(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
glutInitWindowSize(800, 600);
glutCreateWindow("Geometry Test Program");
glutReshapeFunc(ChangeSize);
glutSpecialFunc(SpecialKeys);
glutDisplayFunc(RenderScene);
//添加右击菜单栏
// Create the Menu
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);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
return 0;
}
网友评论