案例
- 使用GLTool下的glMakeTorus() + 固定光源着色器,创建一个3D圆环
/// 创建一个3D圆环
/// @param torusBatch 批次类(容器类)
/// @param majorRadius 主(外)半径
/// @param minorRadius 次(内)半径
/// @param numMajor 主单元细分数量
/// @param numMinor 次单元细分数量
void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor)
//默认光源着色器
GLShaderManager::UserStockShader(GLT_SHADER_DEFAULT_LIGHT,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vColor[4]);
void SetupRC()
{
//1.设置背景颜色
glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
//2.初始化着色器管理器
shaderManager.InitializeStockShaders();
//3.将相机向后移动7个单元:肉眼到物体之间的距离
viewFrame.MoveForward(7.0);
//4.创建一个3D圆环
gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
//5.点的大小(方便点填充时,肉眼观察)
glPointSize(4.0f);
}
//渲染场景
void RenderScene()
{
//1.清除窗口和深度缓冲区
//可以给学员演示一下不清空颜色/深度缓冲区时.渲染会造成什么问题. 残留数据
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//2.把摄像机矩阵压入模型矩阵中
modelViewMatix.PushMatrix(viewFrame);
//3.设置绘图颜色
GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
//4.使用默认光源着色器
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
//5.绘制
torusBatch.Draw();
//6.出栈 绘制完成恢复
modelViewMatix.PopMatrix();
//7.交换缓存区
glutSwapBuffers();
}
-
效果图
00001.gif
问题抛出
- 在使用默认光源着色器后,渲染出来的画面会有一个明暗度的效果,当时当我们旋转视图时,明暗处理变的混乱,出现了大量的黑红交错显示;
- 视图被穿透了,即当物体旋转到正面时,可以透过正面看到后面;
解决方案
油画算法
定义:先绘制场景中离观察者较元的物体,再绘制较近的物体,比如:
弊端:使用油画算法,只能按照物理距离观察者的距离远近排序,或者只能是独立的压栈式绘制,当多个图层环环相扣时,该算法就无法完成绘制了,比如:
不是单纯的层级关系,就无法使用油画算法来完成绘制了,那怎么办呐?我们接着往下看。
正背面剔除
- 在现实生活中,我们在观察一个物体时,无论在任何角度都无法完全看到一个物体所有的面(或者全貌)。在计算机中的3D图形中也是这样的,既然看不到,我们就没必要计算并渲染看不到的部分,而且如果通过某种方式去丢弃这部分渲染,OpenGL的渲染效率会大大提高。
-
OpenGL中的正面与背面
正面与背面
正面:按照逆时针顶点连接顺序的三角形面
背面:按照顺时针顶点连接顺序的三角形面
在OpenGL中,可以通过检测所有朝向观察者的面,即正面,并渲染他们。看不到的面,即背面,丢弃且不进行渲染。 - 通过分析顶点数据的顺序,来告诉OpenGL哪个是正面,哪个是背面
在OpenGL中,提供了非常简单的API来开启顶点数据分析功能(表面剔除)
//开启表面剔除(默认是剔除背面)
void glEnable(GL_CULL_FACE);
//关闭表面剔除
void glDisable(GL_CULL_FACE);
//指定要剔除的面
//mode参数为: GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,默认GL_BACK
void glCullFace(GLenum mode);
//用户自定义哪个面为正面
//mode参数为: GL_CW,GL_CCW,默认值:GL_CCW
void glFrontFace(GLenum mode);
//例如,剔除正面实现(1)
glCullFace(GL_BACK);
glFrontFace(GL_CW);
//例如,剔除正⾯实现(2)
glCullFace(GL_FRONT);
开启正背面剔除后的效果:
这里我们通过开启正背面剔除,解决了物体在旋转时的明暗交替问题,但是在旋转过程中发现,在旋转到重叠部分时,发生了透视,这里引入下一个概念:深度。
深度
概念
深度:像素点在3D世界中距离观察者(Camra视角)的距离,z值。
深度缓存区:一块内存区域,专门存储每个像素点的深度值,z值越大,距离观察者越远(这里要考虑z轴的正/负方向,这里是负方向)。
意义:在不使用深度测试的时候,如果我们先绘制一个距离比较近的物体,再绘制距离较远的物体,此时距离远的位图因为后绘制,会把距离近的物体覆盖掉,有了深度缓冲区后,绘制物体的顺序就不不那么重要了(此时由z值来控制先后), 实际上,只要存在深度缓冲区,OpenGL 都会把像素的深度值写入到缓冲区中. 除非调⽤用 glDepthMask(GL_FALSE).来禁⽌写⼊。深度测试
深度缓冲区(DepthBuffer)和颜色缓存区(ColorBuffer)是一一对应的。颜色缓存区存储像素的颜色信息,而深度缓冲区存储像素的深度信息。 在决定是否绘制一个物体表面时,首先要将表面对应的像素的深度值与当前深度缓冲区中的值进行比较, 如果大于深度缓冲区中的值,则丢弃这部分,否则利用这个像素对应的深度值和颜色值,分别更新深度缓冲区和颜色缓存区,这个过程称为”深度测试”。使用深度测试
在OpenGL中同样给提供了简单的API来开启和关闭深度测试,以及一些相关的自定义API(至于内部如何实现我们并不需要关注)。
深度缓冲区,一般由窗口管理系统,GLFW创建.深度值一般由16位,24位,32位值表示. 通常是24位。数越高,深度精确度更好。
//开启深度测试
glEnable(GL_DEPTH_TEST);
//在绘制场景下,清除颜色缓存区和深度缓存区
glClear(0.0f,0.0f,0.0f,1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
清除深度缓冲区默认值为1.0,表示最大的深度值,深度值的范围为(0,1)之间. 值越小表示越靠近观察者,值越大表示越远离观察者。
看下开启深度测试后的效果:
总结
针对上面的两个问题,我们最终其实就是用了2行代码:
//开始深度测试
glEnable(GL_DEPTH_TEST);
//开启背面剔除
glEnable(GL_CULL_FACE);
3D效果的呈现,其实内部也是通过复杂的计算转换为了2D数据,并渲染出一个让人看起来像3D的效果;(比如透视投影的内部处理)。
在OpenGL中我们并不需要写复杂的代码来写对应的逻辑,只需要通过OpenGL开启相关的绘制能力即可。