绘制
我们这次来绘制一个甜甜圈,整体的绘制流程,和上一篇图元绘制雷同. 因为OpenGL已经提供了甜甜圈的批次类torusBatch
,所以我们不用担心说,画一个甜甜圈需要多少个顶点的问题了. 上一篇图元绘制,设定为观察者不动,图元动
的方式,这次我们改变下策略,使用观察者动,图元不动
,并添加一个默认的光源.
SetupRC
- 同样首先初始化背景颜色,着色器
- 移动观察者MoveForward到合适位置
- 创建甜甜圈
//void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
//参数1:GLTriangleBatch 容器帮助类
//参数2:外边缘半径
//参数3:内边缘半径
//参数4、5:主半径和从半径的细分单元数量
gltMakeTorus(torusBatch, 1.0, 0.4, 52, 26);
gltMakeTorus方法,将GLBatch的Begin,CopyVertexData3f,END方法合在了一起.
- 初始化点的大小,方便点填充时,肉眼可以看得到
- 设置变换管道
//变换管线
transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
ChangeSize
- 设置视口
- 设置透视投影,并加载到透视投影矩阵栈中
- 模型视图矩阵栈记载一个单元矩阵
RenderScene
- 清除颜色缓冲区和深度缓冲区,设置绘图的颜色
- 将观察者矩阵压入模型视图矩阵栈
- 使用默认光源着色器,体现立体效果
//使用默认光源着色器
//参数1:GLT_SHADER_DEFAULT_LIGHT 默认光源着色器
//参数2:模型视图矩阵
//参数3:投影矩阵
//参数4:基本颜色值
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
辅助函数只用了SpecialKeys
大功告成,我们来看一下结果.
貌似看起来很完美,我们利用SpecialKeys调整下再看
探究原因
默认情况下,OpenGL渲染点、线、三角形都会在屏幕上进行光栅化,并按照组合图元批次时指定的顺序排列,这就会在某些情况下产生问题.例如上图,甜甜圈绘制的三角形其中一部分在背面,本应被丢弃但是并没有,而当我们旋转时,正面和背面的三角形该如何显示,OpenGL就蒙圈了.那么就需要我们告诉它,哪些该显示,哪些要隐藏丢弃,这样也可以提交OpenGL的渲染性能,毕竟它少了一半的工作量.这样的方法即隐藏面消除
.
黑色是因为使用了光源着色器,背面就是阴面-黑色
油画法
油画法,会首先渲染距离最远的图形,再依次渲染较劲的图形直到结束.
但是油画法在计算机图形处理中是非常低效,且也有着很大的问题
- 必须对任何发生几何图形重叠的地方每个像素进行2次写操作,而在存储中进行写操作会使速度变慢
- 对独立三角形进行排序的开销过高
- 当绘制图像重叠,没有明确的先后顺序时,无法分得清图形的远近,就无法处理
正背面剔除
我们可以给平面定义正面和背面,OpenGL可以做到检查所有正面朝向观察者的面,并渲染它们,从而丢弃背面朝向的面,OpenGL渲染的性能即可提高50%.
那么如何告诉OpenGL,哪个面是正面?哪个是背面?
答案是通过分析顶点数据的顺序.
- 正面:按照逆时针顶点顺序连接的三角形面
- 背面:按照顺时针顶点顺序连接的三角形面
立方体中的正背面其实也有观察者所在位置
的影响
- 当观察者在右侧时,右边的三角形方向为逆时针
- 当观察者在左侧时,左边的三角形方向为逆时针
正面和背面是由三角形的顶点定义顺序和观察者所在方向共同决定的
//开启表面剔除(默认背面剔除)
glEnable(GL_CULL_FACE);
//关闭表面剔除(默认背面剔除)
glDisable(GL_CULL_FACE);
//选择剔除那个面(正面/背面)
// mode参数为: GL_FRONT, GL_BACK, GL_FRONT_AND_BACK,默认GL_BACK
glCullFace(GLenum mode);
//用户指定绕序那个为正面,一般不做修改,默认逆序为正面
//mode参数为: GL_CW, GL_CCW,默认值:GL_CCW
glFrontFace(GL enum mode);
所以我们需要在RenderScene方法中,glEnable开启背面剔除,就可以解决上边的问题了.
同时又出现了新的问题.
深度测试
开启正背面剔除后,甜甜圈外环的正面和内环的正面,OpenGL不知道该如何进行处理哪个是正面,导致渲染后是这样的结果
我们先了解下深度的相关内容
- 深度其实就是3D世界中,距离观察者的距离z值.当观察者在z轴的
正方向
,z值越大则越靠近观察者,当观察者在z轴的负方向
,z值越小则越靠近观察者 - 深度缓冲区,是一块显存区域,专门存储每个像素点(绘制在屏幕上的)深度值
- 在不使用深度测试的时候,如果我们先绘制一个距离比较近的物体,再绘制距离较远的物体,较远的图形会被较劲的图形所覆盖,当使用深度测试后,绘制物体的顺序就不那么重要了,只要通过开启了深度缓冲区,并允许深度值的写入,OpenGL都会把像素的深度值写入到缓冲区中进行渲染。除非调用glDepthMask(GL_FALSE)来禁止写入
深度缓冲区(DepthBuffer)和颜⾊缓存区(ColorBuffer)是对应的.颜⾊缓存区存储像素的颜⾊信息,⽽深度缓冲区存储像素的深度信息. 在决定是否绘制⼀个物体表⾯时, ⾸先要将表⾯对应的像素的深度值与当前深度缓冲区中的值进⾏⽐较. 如果⼤于深度缓冲区中的值,则丢弃这部分.否则利⽤这个像素对应的深度值和颜 ⾊值.分别更新深度缓冲区和颜⾊缓存区. 这个过程称为”深度测试”
在开启深度测试,在绘制每个像素之前,OpenGL会把它的深度值和当前像素点对应存储在缓冲区的深度值,进行比较,如果新对应的深度值,距离观察者更近,会更新深度缓冲区中的深度值,也要更新颜色缓冲区中对应的颜色值,否则会被丢弃.
深度缓冲区的默认值为1.0,表示最大的深度值,深度值的范围是[0,1]之间.
我们也可以通过glDepthFunc(GLenum func)来修改深度测试的测试规则(一般不会对规则进行修改)
// 开启深度测试
glEnable(GL_DEPTH_TEST);
// 关闭深度测试
glDisable(GL_DEPTH_TEST);
开启后的效果
Z-fighting(Z冲突、闪烁)
开启深度测试,仍旧潜在有风险。 原因是深度缓冲区的精度有极限,当两个图层之间深度差非常小,由于精度问题,导致OpenGL无法正确判断两者的深度值,深度测试的结果变得不可预测,无法确定谁在前谁在后,显示存在歧义,图像交错闪烁。
解决方法
现在的设备,一般不会出现Z-fighting的问题
1.启用多边形偏移Polygon Offset
开启多边形偏移,让深度值之间产生间隔,这在深度测试中就能够正确判断,谁在前谁在后的问题。
// 开启
glEnable(GL_POLYGON_OFFSET_FILL)
// 关闭
glDisable(GL_POLYGON_OFFSET_FILL)
参数 | 对应模式 |
---|---|
GL_POLYGON_OFFSET_FILL | GL_FILL |
GL_POLYGON_OFFSET_LINE | GL_LINE |
GL_POLYGON_OFFSET_POINT | GL_POINT |
2.指定偏移量
- 通过glPolygonOffset 来指定2个参数: factor , units
- 每个Fragment 的深度值都会增加如下所示的偏移量: Offset = ( m * factor ) + ( r * units);
m : 多边形的深度的斜率的最⼤值,理解⼀个多边形越是与近裁剪⾯平⾏,m 就越接近于0.
r : 能产⽣于窗⼝坐标系的深度值中可分辨的差异最⼩值.r 是由具体是由具体OpenGL 平台指定的 ⼀个常量.
- ⼀个⼤于0的Offset 会把模型推到离你(摄像机)更远的位置,相应的⼀个⼩于0的Offset 会把模型拉近
⼀般⽽⾔,只需要将-1 和 -1 这样简单赋值给glPolygonOffset 基本可以满⾜需求.
预防Z-fighting
- 不要将两个物体靠的太近,避免渲染时三角形叠在一起。可以在绘制时插入少量的偏移,但是手动插入偏移也要付出代价
- 尽可能将近裁剪面
(设置透视投影矩阵时设置)
设置的离观察者远一些,这样可以使裁剪范围内的精确度变高,但也可能使离观察者近的物体被裁剪掉,因此需要调试好裁剪面参数 - 使用更高位数的深度缓冲区的硬件设备,提高精确度,常用的是24位,
推荐: