在OpenGL渲染中, 我们会碰到各种各样的问题,所以也会对应的产生各种各样的渲染技巧,接下来就介绍我们最经常使用的渲染技巧。
在绘制3D
场景的时候,我们需要决定哪些部分是对观察者可⻅的,或者哪些部分是对观察者不可⻅的,否则就会出现甜甜圈变黑的问题。对于不可⻅的部分,应该及早丢弃。例如在⼀个不透明的墙壁后,就不应该渲染.这种情况叫做”隐藏⾯消除”(Hidden surface elimination
)。为了解决这种问题,我们引入一个概念:油画算法。
油画算法
即先绘制场景中的离观察者较远的物体,再绘制较近的物体。如下图中,我们先绘制红色,然后黄色,最后蓝色,这样就不会产生某些我们看不到的面绘制不到的问题。
但是油画算法也有自己的问题。使⽤油画算法,只要将场景按照物理距离观察者的距离远近排序,由远及近的绘制即可.那么会出现什么问题? 如果三个三⻆形是叠加的情况,油画算法将⽆法处理。所以此时引入一个新的概念:正背面剔除。 叠加
正背面剔除
因为我们观察一个立体图形的时候,最多只能看到这个立体图形的三个面,所以我们不需要绘制看不到的面,等观察者可以看到背面的时候,我们出发重绘,这样可以大大的提高OpenGL
的渲染效率,节约片元着色器的性能。需要知道,OpenGL
在判断正面反面的时候,通过传入的顶点数组顺序来分析。
正面:按照逆时针顶点连接顺序的三⻆形⾯。
背面:按照顺时针顶点连接顺序的三⻆形⾯。
左侧三⻆形顶点顺序为: 1—> 2—> 3 , 右侧三⻆形的顶点顺序为: 1—> 2—> 3。当观察者在右侧时,则右边的三⻆形⽅向为逆时针⽅向则为正⾯,⽽左侧的三⻆形为顺时针则为正⾯ 。当观察者在左侧时,则左边的三⻆形为逆时针⽅向判定为正⾯,⽽右侧的三⻆形为顺时针判定为背⾯.。正⾯和背⾯是有三⻆形的顶点定义顺序和观察者⽅向共同决定的.随着观察者的⻆度⽅向的改变,正⾯背⾯也会跟着改变。
正背面
//开启正背面剔除功能
glEnable(GL_CULL_FACE);
//关闭正背面剔除功能
glDisable(GL_CULL_FACE);
此时绘制的甜甜圈,我们经过旋转可以看到依然有问题。仿佛缺少了一块(如下图)。因为在重合部分的像素点,计算机无法判断观察者究竟看到的是那一面,所以会出现错乱。而这时候就会引入一个新的概念:深度测试。深度其实就是该像素点在3D世界中距离摄像机的距离,Z值。深度缓存区,就是⼀块内存区域,专⻔存储着每个像素点(绘制在屏幕上的)深度值.深度值(Z值)越⼤,则离摄像机就越远。
为什么我们需要深度缓冲区?
在不使⽤深度测试的时候,如果我们先绘制⼀个距离⽐较近的物理,再绘制距离较远的物理,则距离远的位图因为后绘制,会把距离近的物体覆盖掉. 有了深度缓冲区后,绘制物体的顺序就不那么᯿要的. 实际上,只要存在深度缓冲区,OpenGL 都会把像素的深度值写⼊到缓冲区中. 除⾮调⽤glDepthMask(GL_FALSE)
,来禁⽌写⼊。
深度测试
深度缓冲区(DepthBuffer)和颜⾊缓存区(ColorBuffer)是对应的.颜⾊缓存区存储像素的颜⾊信
息,⽽深度缓冲区存储像素的深度信息. 在决定是否绘制⼀个物体表⾯时, ⾸先要将表⾯对应的像
素的深度值与当前深度缓冲区中的值进⾏⽐较. 如果⼤于深度缓冲区中的值,则丢弃这部分.否则
利⽤这个像素对应的深度值和颜⾊值.分别更新深度缓冲区和颜⾊缓存区. 这个过程称为”深度测
试” 。
深度计算值
深度值⼀般由16位,24位或者32位值表示,通常是24位。位数越⾼的话,深度的精确度越好。深度值的范围在[0,1]之间,值越⼩表示越靠近观察者,值越⼤表示远离观察者。
⾮线性深度缓存
在实践中是可以减少使⽤这样的线性深度缓冲区。正确的投影特性的⾮线性深度⽅程是和1/z成正⽐的 ,由于⾮线性函数是和 1/z 成正⽐,例如1.0 和 2.0 之间的 z 值,将变为 1.0 到 0.5之间, 这样在z⾮常⼩的时候给了我们很⾼的精度。⽅程如下所示:
上面的两个概念,只需要了解就好,我们更多的是如何简单的使用深度测试。深度缓冲区一般由窗口管理系统,
GLFW
创建,深度值一般由16位,24位,32位表示,通常是24位,位数越高,精度越大。
//开启深度测试
glEnable(GL_DEPTH_TEST);
//关闭深度测试
glDisable(GL_DEPTH_TEST);
深度测试判断模式
void glDepthFunc(GLEnum mode);
函数 | 说明 |
---|---|
GL_ALWAYS | 总是通过测试 |
GL_NEVER | 总是不通过测试 |
GL_LESS | 在当前深度值 < 存储的深度值时通过 |
GL_EQUAL | 在当前深度值 = 存储的深度值时通过 |
GL_LEQUAL | 在当前深度值 <= 存储的深度值时通过 |
GL_GREATER | 在当前深度值 > 存储的深度值时通过 |
GL_NOTEQUAL | 在当前深度值 != 存储的深度值时通过 |
GL_GEQUAL | 在当前深度值 >= 存储的深度值时通过 |
打开/阻断 深度缓存区写⼊
//value : GL_TURE 开启深度缓冲区写⼊; GL_FALSE 关闭深度缓冲区写⼊
void glDepthMask(GLBool value);
深度缓冲区在使用过程中,也会有问题,当深度Z
值的差距在非常小的范围内的时候,计算机的精度无法判断两个深度值得差异,这时候在绘制的时候,会产生不可预测的结果,显示现象就是交错闪烁。即:ZFighting闪烁问题。
解决ZFighting闪烁问题:多边形偏移
既然闪烁问题,是由于Z
值精度无法判断的问题,那么我们可以人为的使两个图形之间深度有一些细微的增加。这样就能使两个图形的深度值有所区分。使用起来比较简单。
//GL_POLYGON_OFFSET_POINT 对应光栅化模式: GL_POINT
//GL_POLYGON_OFFSET_LINE 对应光栅化模式: GL_LINE
//GL_POLYGON_OFFSET_FILL 对应光栅化模式: GL_FILL
//开启
glEnable(GL_POLYGON_OFFSET_FILL)
/*
通过glPolygonOffset 来指定.glPolygonOffset 需要2个参数: factor , units
Offset = ( m * factor ) + ( r * units);
m : 多边形的深度的斜率的最⼤值,理解⼀个多边形越是与近裁剪⾯平⾏,m 就越接近于0.
r : 能产⽣于窗⼝坐标系的深度值中可分辨的差异最⼩值.r 是由具体是由具体OpenGL 平台指定的⼀个常量。
⼀个⼤于0的Offset 会把模型推到离你(摄像机)更远的位置,相应的⼀个⼩于0的Offset 会把模型拉近⼀般⽽⾔,
只需要将-1.0 和 -1 这样简单赋值给glPolygonOffset 基本可以满⾜需求。
*/
void glPolygonOffset(Glfloat factor,Glfloat units);
/*
应⽤到⽚段上总偏移计算⽅程式:
Depth Offset = (DZ * factor) + (r * units);
DZ:深度值(Z值)
r:使得深度缓冲区产⽣变化的最⼩值,假设观察者在原点,负值,将使得z值距离我们更近,⽽正值,将使得z值距离我们更远。
*/
//关闭
glDisable(GL_POLYGON_OFFSET_FILL)
ZFighting
虽然可以解决,但是我们在使用中可以尽量避免,因为插入小的偏移量当然是有代价的。所以我们应该尽量避免渲染时候,两个三角形叠加在一起,也不要靠的太近。或者尽可能将近裁剪面设置的离观察者远一些。因为在近裁剪面附近,深度的精确度是很高的,因此我们尽可能的让近裁剪面远一些的话,会使整个裁剪范围内的精度变高。但是这种方式会使离观察者较近的物体被裁减掉,所以需要调试好裁剪面参数。或者使用高位数的深度缓冲区,通常使用的是24位的深度缓冲区,你换成32位的就好了。当然这种方式,说了跟没说一样。想要查看效果,可以详细的查看Demo,鼠标右键就可以查看正背面剔除和深度测试的效果。
裁剪
在OpenGL 中提⾼渲染的⼀种⽅式.只刷新屏幕上发⽣变化的部分.OpenGL 允许将要进⾏渲染的窗⼝只去指定⼀个裁剪框。 基本原理:⽤于渲染时限制绘制区域,通过此技术可以再屏幕(帧缓冲)指定⼀个矩形区域。启⽤剪裁测试之后,不在此矩形区域内的⽚元被丢弃,只有在此矩形区域内的⽚元才有可能进⼊帧缓冲。因此实际达到的效果就是在屏幕上开辟了⼀个⼩窗⼝,可以再其中进⾏指定内容的绘制。
之前的文章中,我们了解过视口和窗口的概念,这里的裁剪区域就是视口矩形区域的最小最大x
坐标和最小最大y
坐标,而不是窗口的x
,y
坐标。使用glOrtho
函数,还可以指定最近最远z
坐标,行程立体裁剪区域。
//1 开启裁剪测试
glEnable(GL_SCISSOR_TEST);
//2.关闭裁剪测试
glDisable(GL_SCISSOR_TEST);
//3.指定裁剪窗⼝
//x,y:指定裁剪框左下⻆位置;
//width , height:指定裁剪尺⼨
void glScissor(Glint x,Glint y,GLSize width,GLSize height);
混合
OpenGL
渲染时会把颜⾊值存在颜⾊缓存区中,每个⽚段的深度值也是放在深度缓冲区。当深度缓冲区被关闭时,新的颜⾊将简单的覆盖原来颜⾊缓存区存在的颜⾊值,当深度缓冲区再次打开时,新的颜⾊⽚段只是当它们⽐原来的值更接近邻近的裁剪平⾯才会替换原来的颜⾊⽚段。
//开启混合
glEnable(GL_BIEND);
//设置混合因子
glBlendFunc(GLenum S,GLenum D);
//选择混合⽅程式的函数:
glbBlendEquation(GLenum mode);
//关闭混合
glDisable(GL_BIEND);
组合颜色
⽬标颜⾊:已经存储在颜⾊缓存区的颜⾊值
源颜⾊:作为当前渲染命令结果进⼊颜⾊缓存区的颜⾊值
当混合功能被启动时,源颜⾊和⽬标颜⾊的组合⽅式是混合⽅程式控制的。在默认情况
下,混合⽅程式如下所示:
Cf = (Cs * S) + (Cd * D)
Cf
:最终计算参数的颜⾊
Cs
: 源颜⾊
Cd
:⽬标颜⾊
S
:源混合因⼦
D
:⽬标混合因⼦
设置混合因子
混合因子设置混合因⼦,需要⽤到
glBlendFun(GLenum S,GLenum D)
函数
S
:源混合因⼦
D
:⽬标混合因⼦
表中R
、G
、B
、A
分别代表 红、绿、蓝、alpha
。
表中下标S
、D
,分别代表源、⽬标
表中C
代表常量颜⾊(默认⿊⾊)
我们不仅可以改变混合因子,还可以改变组合方程式Cf = (Cs * S) + (Cd * D),这是默认的组合方程式,OpenGL
用函数glbBlendEquation
给我们提供了5中不同的方程式从中选择。不仅如此,我们还可以用glBlendFuncSeparate
函数来更灵活的选择混合方式,但是这里不一一介绍了,以为这些都不是常用的东西,有兴趣的可以自己去查看资料了解。在Demo里我提供了可以动的方块,来实现颜色的混合。
网友评论