美文网首页
OpenGL ES 入门之旅--OpenGL下的深度缓冲区,隐藏

OpenGL ES 入门之旅--OpenGL下的深度缓冲区,隐藏

作者: Henry_Jeannie | 来源:发表于2019-05-28 09:55 被阅读0次

    1.隐藏面消除

    首先我们用OpenGL代码渲染出一个类似于甜甜圈的场景。 甜甜圈.png 如果说当前这个图形不做任何移动,旋转,是没有任何问题的(也可以说是暂时看不到任何问题)实际上,我们在绘制3D场景的过程中,让图形旋转,此时OpenGL并不知道这个图形的哪些面是应该被隐藏的,那么在旋转图形的时候OpenGL在进行渲染的时候顺序就会发生混乱, 混乱.png 解决发生这种混乱的情况就叫做"隐藏面消除(Hidden surface elimination)";
    解决方法1:油画算法

    1先绘制场景中的离观察者较远的物体,再绘制较近的物体.
    例如下⾯的图例: 先绘制红⾊部分,再绘制黄色部分,最后再绘制灰色部分,即可解决隐藏面消除的问题

    油画算法.png 油画算法之所以能够解决"隐藏面"的问题,是因为我们在绘制的过程中去判断绘制的部分是否应该被看到,然后依次绘制。看到此处,似乎一切问题已经解决,但是,在上图中我们在依次绘制的三个颜色区域中包含了重复的部分,显然重复的区域是不需要绘制的(只能看到灰色部分,所以红色,黄色重叠的部分不需要绘制),此时就会带来另外一个问题:性能问题
    油画算法解决"隐藏面"问题,是根据图形绘制的顺序(使用油画算法,只要将场景按照物理距离观察者的距离远近排序,由远及近的绘制即可),倘若我们无法确定图形绘制的顺序, 无顺序三角形.png 如图这几个三角形依次叠加的时候,OpenGL就无法知道绘制图形的顺序,此时油画算法就无法彻底解决"隐藏面"问题。(并不是说油画算法不可取,只是油画算法不能够完美的解决隐藏面的问题)。
    下面我们来看一种彻底的解决方法:
    解决方法2.正背面剔除Face Culling

    首先我们知道,我们在任何一个视角观察图形的时候,只能看到一个面,在观察正方体的时候,最多可以看到正方体的三个面,那么我们还有必要去绘制那些根本看不到的多余的面吗?如果我们以某种方式丢弃掉这部分数据,那么OpenGL的性能就能得到提升。在OpenGL中,OpenGL 可以做到检查所有正面朝向观察者的面,并渲染它们从而丢弃背面朝向的面. 这样可以节约片元着⾊器的性能.我们也可以通过分析顶点数据的顺序,(逆时针画和顺时针画----正三角和反三角)来告诉OpenGL你所绘制的图形那个是正面那个是背面。
    所以正背面剔除法有两个很重要的依据:

    1.正背面区分:(区分顶点顺序) 正背面区分.png 右侧三角形(1->3->2):按照逆时针顶点连接顺序的三角形面为正面。
    左侧三角形(1->2->3):按照顺时针顶点连接顺序的三角形面为反面。
    2.观察者位置 观察者位置.png 左侧三⻆角形顶点顺序为: 1—> 2—> 3
    右侧三⻆角形的顶点顺序为: 1—> 2—>3
    当观察者在右侧时,则右边的三角形⽅方向为逆时针⽅向则为正面,而左侧的三⻆形为顺时针则为背面
    • 当观察者在左侧时,则左边的三角形为逆时针方向判定为正面,⽽右侧的三角形为顺时针判定为背面.
    正面和背面是由三⻆形的顶点定义顺序和观察者方向共同决定的.随着观察者的⻆度⽅向的改变,正⾯背面也会跟着改变。
    开启表⾯面剔除(默认背⾯面剔除)
    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 
    
    例如,剔除正面实现
    glCullFace(GL_BACK);
    glFrontFace(GL_CW);(可以不用写)
    
    正背面剔除后.png

    正背面剔除完成后,会看到绘制的甜甜圈没有出现混乱的情况,但是转动图形,会发现出现部分缺失的情况?正是因为我们没有进行深度测试。

    2.深度缓冲区

    深度:深度其实就是一个像素点在3D世界中距离摄像机(观察者)的距离,也就是Z值。
    深度缓冲区:深度缓冲区就是一块内存区域,专门用来存储每个像素点(绘制在屏幕上的)的深度值,深度值越大,则该像素点距离摄像机越远。
    在我们利用OpenGL绘制图形的时候,在不使用深度测试的情况下,如果我们先绘制一个距离比较远的物体,再绘制距离比较近的物体,那么距离远的物体位图是因为在后面绘制,会把距离近的物体覆盖掉。然而有了深度缓存区以后,绘制物体的远近顺序就不重要了,实际上只要在深度缓冲区,OpenGL都会把像素的深度值写入到深度缓冲区,除非调用glDepthMask(GL_FALSE)来禁止写入到深度缓冲区。
    深度测试:深度缓冲区(DepthBuffer)和颜色缓冲区(ColorBuffer)是一一对应的,颜色缓冲区存储着像素的颜色信息,⽽深度缓冲区存储每一个像素的深度信息. 在决定是否绘制一个物体表面时, 首先要将表面对应的像素的深度值与当前深度缓冲区中的值进行比较. 如果大于深度缓冲区中的值,则丢弃这部分。否则利⽤这个像素对应的深度值(深度值一般由16位,24位或者32位值表示,通常是24位。位数越高的话,深度的精确度越 好,深度值的范围在[0,1]之间,值越⼩小表示越靠近观察者,值越⼤表示远离观察者。)和颜⾊值,分别更新深度缓冲区和颜色缓存区. 这个过程称为”深度测试”。
    深度缓冲区一般由窗口系统管理,GLFW创建。

    **1.开启深度测试**
    glEnable(GL_DEPTH_TEST);
    在绘制场景前,清除颜色缓存区,深度缓冲 glClearColor(0.0f,0.0f,0.0f,1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    清除深度缓冲区默认值为1.0,表示最⼤大的深度值,深度值的范围为(0,1)之间. 值越小表示越靠近观察者,值越大表示 越远离观察者
    
    完整绘制.png

    那么我们怎么知道在什么时候开启正背面剔除,什么时候开启深度测试?其实我们在绘制立体图形的是最好把正背面剔除和深度测试都打开,以确保绘制的完美,事实上打开并不会影响OpenGL绘制的性能。但是记得打开正背面剔除和深度测试,也要记得关闭。

    2.深度测试判断式

    //指定深度测试判断模式
    void glDepthFunc(GLEnum mode);
    
    函数 描述说明
    GL_ALWAYS 总是通过测试
    GL_NEVER 总是不通过测试
    GL_ALWAYS 当前深度值 < 存储的深度值,通过测试
    GL_EQUAL 当前深度值 = 存储的深度值,通过测试
    GL_LEQUAL 当前深度值 <= 存储的深度值,通过测试
    GL_GREATER 当前深度值 > 存储的深度值,通过测试
    GL_NOTEQUAL 当前深度值 != 存储的深度值, 总是通过测试
    GL_GEQUAL 当前深度值 >= 存储的深度值,总是通过测试

    3.开启/阻断 深度缓冲区的写入

    void glDepthMask(GLBool value);
    value:
    GL_TURE 表示开启深度缓冲区写入
    GL_FALSE 表示关闭深度缓冲区写入
    

    4.ZFighting闪烁问题
    我们在绘制的过程中,因为开启深度测试后,OpenGL 就不会再去绘制模型被遮挡的部分,这样实现的绘制显示更加真实,但是由于深度缓冲区精度的限制对于深度相差非常小的情况下,(例如在同一个平面上进行2次绘制),OpenGL 就可能出现不能正确判断两者的深度值,从而会导致深度测试的结果不可预测,绘制显示出来的 现象时交错闪烁,会导致同一平面上的2个画面交错出现.

    1.png 2.png
    ZFighting闪烁问题解决:
    1.启⽤用 Polygon Offset 多边形偏移⽅式解决
    让深度值之间产⽣间隔,如果2个图形之间有间隔,是不是意味着就不会产⽣干涉,可以理解为在执⾏行深度测试前将⽴方体的深度值做一些细微的增加,于是就能将􏳎叠的2个图形深度值较之前有所区分。
    //启用Polygon Offset ⽅式 
    glEnable(GL_POLYGON_OFFSET_FILL)
    
    参数列列表: 
    GL_POLYGON_OFFSET_POINT   对应光栅化模式
    GL_POINTGL_POLYGON_OFFSET_LINE   对应光栅化模式: GL_LINE
    GL_POLYGON_OFFSET_FILL  对应光栅化模式: GL_FILL
    

    2.指定偏移量
    通过glPolygonOffset 来指定,glPolygonOffset 需要2个参数: factor , units 。
    每个Fragment 的深度值都会增加如下所示的偏移量:
    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值距离我们更远, 对于上节课的案例,我们设置factor和units设置为-1,-1
    

    3.关闭Polygon Offset

    glDisable(GL_POLYGON_OFFSET_FILL)
    
    5.裁剪

    裁剪是在OpenGL 中提高渲染的一种方式,只刷新屏幕上发⽣变化的部分,OpenGL 允许将要进行渲染的窗口只 去指定⼀个裁剪框。
    基本原理:用于渲染时限制绘制区域,通过此技术可以再屏幕(帧缓冲)指定⼀个矩形区域。启用剪裁测试之后,不在此矩形区域内的⽚元被丢弃,只有在此矩形区域内的片元才有可能进⼊帧缓冲。因此实际达到的效果就是在屏幕上开辟了一个⼩窗⼝,可以在其中进行指定内容的绘制。

    //开启裁剪测试
    glEnable(GL_SCISSOR_TEST);
    
    //关闭裁剪测试
    GLDisable(GL_SCISSOR_TEST);
    
    //裁剪窗口的指定
    void glScissor(Glint x, Glint y, GLSize width, GLSize height);
    

    窗口,视口,裁剪区域的区别:
    窗口:指的就是现实的界面
    视口:通过glViewport()函数设置大小,视口指的就是窗口中用来显示图形的一块区域,它可以和窗口等大,也可以比窗口大或者小,不过只有绘制在视口区域中的图形才会被显示,如果图形其中一部分超出了视口区域,那么超出的部分是看不到的。
    裁剪区域:指的就是视口矩形区域的最小最大X坐标(left,right)和最小最大Y坐标(bottom,top),而不是窗口的最小最大X坐标和Y坐标,通过glOrtho()函数设置,这个函数还需要指定最近最远Z坐标,形成一个立体的裁剪区域。

    6.混合

    我们把OpenGL 渲染时会把颜⾊值存在颜⾊缓存区中,每个片段的深度值也是放在深度缓冲区。当深度缓冲区被关闭时,新的颜色将简单的覆盖原来颜色缓存区存在的颜⾊值,当深度缓冲区再次打开时,新的颜色⽚段只是当它们比原来的值更接近邻近的裁剪平⾯才会替换原来的颜⾊片段。

    开启混合:
    glEnable(GL_BLEND);
    

    ⽬标颜色:已经存储在颜色缓存区的颜⾊值
    源颜色:作为当前渲染命令结果进⼊颜⾊缓存区的颜色值
    当混合功能被启动时,源颜⾊和目标颜⾊的组合方式是混合方程式控制的。在默认情况下,混合方程式如下所示:

    Cf = (Cs * S) + (Cd * D)
    Cf :最终计算参数的颜⾊
    Cs : 源颜⾊
    Cd :⽬标颜色 
    S: 源混合因子 
    D: 目标混合因子
    
    设置混合因子,需要用到glBlendFun函数
    glBlendFunc(GLenum S,GLenum D);
    

    设置混合因子:

    常量(函数) RGB混合因子 Alpha混合因子
    GL_ZERO (0, 0, 0) 0
    GL_ONE (1, 1, 1) 1
    GL_DST_COLOR (Rd, Gd, Bd) Ad
    GL_SRC_COLOR (Rs, Gs, Bs) As
    GL_ONE_MINUS_DST_COLOR (1, 1, 1) - (Rd, Gd, Bd) 1-Ad
    GL_ONE_MINUS_SRC_COLOR (1, 1, 1) - (Rs, Gs, Bs) 1-As
    GL_SRC_ALPHA (As,As,As) As
    GL_ONE_MINUS_SRC_ALPHA (1, 1, 1) - (As,As,As) 1-As
    GL_DST_ALPHA (Ad, Ad, Ad) Ad
    GL_ONE_MINUS_DST_ALPHA (1, 1, 1) - (Ad,Ad,Ad) 1-Ad
    GL_CONSTANT_COLOR (Rc, Gc Bc) Ac
    GL_ONE_MINUS_CONSTANT_COLOR (1, 1, 1) - (Rc, Gc Bc) 1-Ac
    GL_CONSTANT_ALPHA (Ac, Ac, Ac ) Ac
    GL_ONE_MINUS_CONSTANT_ALPHA (1, 1, 1) - (Ac, Ac Ac) 1-Ac
    GL_SRC_ALPHA_STATURATE (f, f, f, 1); f = min(As, 1 - Ad) 1

    表中R、G、B、A 分别代表 红、绿、蓝、alpha
    表中下标S、D,分别代表源、⽬标
    表中C 代表常量颜色(默认⿊色)

    默认混合⽅程式: Cf = (Cs * S)+(Cd * D)
    实际上远不止这⼀种混合⽅程式,我们可以从5个不同的方程式中进行选择:

    选择混合⽅方程式的函数:
    glbBlendEquation(GLenum mode);
    
    模式 函数
    GL_FUNC_ADD Cf = (Cs * S)+(Cd * D)
    GL_FUNC_SUBTRACT Cf = (Cs * S)-(Cd * D)
    GL_FUNC_RENERSE_SUBTRACT Cf = (Cd * D)-(Cs * S)
    GL_MIN Cf =min (Cs, Cd)
    GL_MAX Cf =max (Cs,Cd

    除了能使用glBlendFunc 来设置混合因子,还可以有更灵活的选择:

    void glBlendFuncSeparate(GLenum strRGB,GLenum dstRGB ,GLenum strAlpha,GLenum dstAlpha);
    strRGB: 源颜色的混合因子 
    dstRGB: ⽬标颜色的混合因⼦
    strAlpha: 源颜色的Alpha因子
    dstAlpha: ⽬标颜色的Alpha因⼦
    

    注意:
    glBlendFunc 指定源和目标 RGBA值的混合函数; 但是glBlendFuncSeparate函数则允许为RGB 和 Alpha 成分单独指定混合函数。

    在混合因⼦表中,GL_CONSTANT_COLOR,GL_ONE_MINUS_CONSTANT_COLOR,GL_CONSTANT_ALPHA,GL_ONE_MINUS_CONSTANT值允许混合⽅方程式中引⼊一个常量混合颜⾊。
    常量混合颜色,默认初始化为⿊色(0.0f,0.0f,0.0f,0.0f),但是还是可以修改这个常量混合颜⾊。

    void glBlendColor(GLclampf red ,GLclampf green ,GLclampf blue ,GLclampf alpha );
    

    相关文章

      网友评论

          本文标题:OpenGL ES 入门之旅--OpenGL下的深度缓冲区,隐藏

          本文链接:https://www.haomeiwen.com/subject/eoqbzqtx.html