美文网首页
OpenGL 渲染常见问题

OpenGL 渲染常见问题

作者: 江海寄余生12138 | 来源:发表于2021-05-28 00:08 被阅读0次

    常见问题

    隐藏面消除

    在绘制3D场景的时候,我们需要决定哪些部分是对观察者可⻅的,或者哪些部分是对观察者不可⻅的.对于不可⻅的部分,应该及早丢弃.例如在⼀个不透明的墙壁后,就不应该渲染.这种情况叫做”隐藏⾯消除”(Hidden surface elimination).


    image.png
    解决方案
    • 油画法
      先绘制场景中的离观察者较远的物体,再绘制较近的物体
      如下图,先绘制红⾊部分,再绘制⻩⾊部分,最后再绘制灰⾊部分,即可解决隐藏⾯消除的问题


      image.png

      油画法弊端:如果三个三⻆形是叠加的情况,油画算法将⽆法处理


      image.png
    • 正背面剔除(Face Culling)
      任何平⾯都有2个⾯,正⾯/背⾯.意味着你⼀个时刻只能看到⼀⾯
      OpenGL 可以做到检查所有正⾯朝向观察者的⾯,并渲染它们.从⽽丢弃背⾯朝向的⾯. 这样可以节约⽚元着⾊器的性能.
      正⾯/背⾯区分:
      • 正⾯: 按照逆时针顶点连接顺序的三⻆形⾯
      • 背⾯: 按照顺时针顶点连接顺序的三⻆形⾯


      image.png
    // 开启表⾯剔除(默认背⾯剔除) 
    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);
    
    • 深度测试
      • 什么是深度?
      深度其实就是该像素点在3D世界中距离摄像机的距离,Z值
      • 什么是深度缓冲区?
      深度缓存区,就是⼀块内存区域,专⻔存储着每个像素点(绘制在屏幕上的)深度值.深度值(Z值)越⼤,则离摄像机就越远.
      • 为什么需要深度缓冲区?
      在不使⽤深度测试的时候,如果我们先绘制⼀个距离⽐较近的物理,再绘制距离较远的物理,则距离远的位图因为后绘制,会把距离近的物体覆盖掉. 有了深度缓冲区后,绘制物体的顺序就不那么重要的. 实际上,只要存在深度缓冲区,OpenGL 都会把像素的深度值写⼊到缓冲区中,除⾮调⽤glDepthMask(GL_FALSE).来禁⽌写⼊.
      • 深度测试:
      深度缓冲区(DepthBuffer)和颜⾊缓存区(ColorBuffer)是对应的.颜⾊缓存区存储像素的颜⾊信息,⽽深度缓冲区存储像素的深度信息. 在决定是否绘制⼀个物体表⾯时, ⾸先要将表⾯对应的像素的深度值与当前深度缓冲区中的值进⾏⽐较. 如果⼤于深度缓冲区中的值,则丢弃这部分.否则利⽤这个像素对应的深度值和颜⾊值.分别更新深度缓冲区和颜⾊缓存区. 这个过程称为”深度测试”
    • 使用深度测试
      深度缓冲区,⼀般由窗⼝管理系统,GLFW创建.深度值⼀般由16位,24位,32位值表示. 通常是24位.位数越⾼,深度精确度更好.
    // 开启深度测试
    glEnable(GL_DEPTH_TEST);
    // 在绘制场景前,清除颜⾊缓存区,深度缓冲
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // 打开或关闭深度缓冲区
    // GL_TURE 开启深度缓冲区写⼊; GL_FALSE 关闭深度缓冲区写⼊
    void glDepthMask(GLBool value);
    // 指定深度测试判断模式
    void glDepthFunc(GLEnum mode);
    

    判断模式glDepthFunc可以传如下表所示参数,默认为GL_LESS


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


      image.png

    解决:
    让深度值之间产⽣间隔.可以理解为在执⾏深度测试前将⽴⽅体的深度值做⼀些细微的增加.于是就能将重叠的2个图形深度值之前有所区分.

    //第⼀步: 启⽤ Polygon Offset ⽅式解决
    //参数列表: 
    //GL_POLYGON_OFFSET_POINT 对应光栅化模式: GL_POINT 
    //GL_POLYGON_OFFSET_LINE 对应光栅化模式: GL_LINE 
    //GL_POLYGON_OFFSET_FILL 对应光栅化模式: GL_FILL
    glEnable(GL_POLYGON_OFFSET_FILL)
    //第⼆步: 指定偏移量
    // ⼀般⽽⾔,只需要将-1.0 和 0.0 这样简单赋值给glPolygonOffset 基本可以满⾜需求.
    // Depth Offset = (DZ(深度) * factor) + (r(变化的最⼩值) * units);
    void glPolygonOffset(Glfloat factor,Glfloat units);
    //第三步: 关闭Polygon Offset
    glDisable(GL_POLYGON_OFFSET_FILL)
    

    ZFighting闪烁问题预防
    • 不要将两个物体靠的太近,避免渲染时三⻆形叠在⼀起。这种⽅式要求对场景中物体插⼊⼀个少量的偏移,那么就可能避免ZFighting现象。例如上⾯的⽴⽅体和平⾯问题中,将平⾯下移0.001f就可以解决这个问题。当然⼿动去插⼊这个⼩的偏移是要付出代价的。
    • 尽可能将近裁剪⾯设置得离观察者远⼀些。上⾯我们看到,在近裁剪平⾯附近,深度的精确度是很⾼的,因此尽可能让近裁剪⾯远⼀些的话,会使整个裁剪范围内的精确度变⾼⼀些。但是这种⽅式会使离观察者较近的物体被裁减掉,因此需要调试好裁剪⾯参数。
    • 使⽤更⾼位数的深度缓冲区,通常使⽤的深度缓冲区是24位的,现在有⼀些硬件使⽤使⽤32位的缓冲区,使精确度得到提⾼

    裁剪

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

    //1 开启裁剪测试
    glEnable(GL_SCISSOR_TEST);
    //2.关闭裁剪测试
    glDisable(GL_SCISSOR_TEST);
    //3.指定裁剪窗⼝
     void glScissor(Glint x,Glint y,GLSize width,GLSize height);
    // x,y:指定裁剪框左下⻆位置;
    // width , height:指定裁剪尺⼨
    
    • 理解窗⼝,视⼝,裁剪区域
      • 窗⼝: 就是显示界⾯
      • 视⼝: 就是窗⼝中⽤来显示图形的⼀块矩形区域,它可以和窗⼝等⼤,也可以⽐窗⼝⼤或者⼩。只有绘制在视⼝区域中的图形才能被显示,如果图形有⼀部分超出了视⼝区域,那么那⼀部分是看不到的。通过glViewport()函数设置。
      • 裁剪区域(平⾏投影):就是视⼝矩形区域的最⼩最⼤x坐(left,right)和最⼩最⼤y坐标(bottom,top),⽽不是窗⼝的最⼩最⼤x坐标和y坐标。通过glOrtho()函数设置,这个函数还需指定最近最远z坐标,形成⼀个⽴体的裁剪区域。

    混合(透明叠加时,颜色需要混合)

    混合函数经常⽤于实现在其他⼀些不透明的物体前⾯绘制⼀个透明物体的效果

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

    // 开启混合
    glEnable(GL_BlEND);
    
    • 组合颜⾊
      ⽬标颜⾊:已经存储在颜⾊缓存区的颜⾊值
      源颜⾊:作为当前渲染命令结果进⼊颜⾊缓存区的颜⾊值
      当混合功能被启动时,源颜⾊和⽬标颜⾊的组合⽅式是混合⽅程式控制的。在默认情况下,混合⽅程式如下所示:Cf = (Cs * S) + (Cd * D)
      Cf :最终计算参数的颜⾊
      Cs : 源颜⾊
      Cd :⽬标颜⾊
      S:源混合因⼦
      D:⽬标混合因⼦
    // 设置混合因子
    glBlendFunc(GLenum S,GLenum D);
    // 常见的组合
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    
    image.png

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

    • 可以选择混合方程式
    // 选择混合方程式
    glbBlendEquation(GLenum mode);
    
    image.png
    • 混合因子的灵活设置
      除了能使⽤glBlendFunc 来设置混合因⼦,还可以有更灵活的选择
    // strRGB: 源颜⾊的混合因⼦
    //dstRGB: ⽬标颜⾊的混合因⼦
    //strAlpha: 源颜⾊的Alpha因⼦
    //dstAlpha: ⽬标颜⾊的Alpha因⼦
    void glBlendFuncSeparate(GLenum strRGB,GLenum dstRGB,GLenum strAlpha,GLenum dstAlpha);
    

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

    在混合因⼦表中,
    GL_CONSTANT_COLOR,GL_ONE_MINUS_CONSTANT_COLOR,GL_CONSTANT_ALPHA,GL_ONE_MINUS_CONSTANT值允许混合⽅程式中引⼊⼀个常量混合颜⾊。

    //修改常量混合颜色
    void glBlendColor(GLclampf red ,GLclampf green ,GLclampf blue,GLclampf alpha );
    
    • 混合处理-抗锯齿
      出现锯齿的原因:每个像素都是一个一个的小正方形,当物体的边界出现颜色分明的变化时,正方形的像素就会出现在分割线的两边,形成锯齿。
    //开启混合处理,将锯齿进行颜色混合
    glEnable(GL_BLEND);
    //指定混合因⼦
    GLBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    //指定混合⽅程式
    glBlendEquation(GL_FUNC_ADD); 
    //对点进⾏抗锯⻮处理
    glEnable(GL_POINT_SMOOTH); 
    //对线进⾏抗锯⻮处理
    glEnable(GL_LINE_SMOOTH);
    //对多边形进⾏抗锯⻮处理
    glEnable(GL_POLYGON_SMOOTH);
    
    • 多重采用- 抗锯齿
    // 1.可以调⽤ glutInitDisplayMode 添加采样缓存区
    glutInitDisplayMode(GLUT_MULTISAMPLE);
    //2. 打开|关闭 多重采样。
    glEnable(GLUT_MULTISAMPLE); 
    glDisable(GLUT_MULTISAMPLE);
    
    // 混合,多重采样组合
     glDisable(GLUT_MULTISAMPLE); 
     glEnable(GL_POINT_SMOOTH); 
     //Draw some smooth point 
     glDisable(GL_POINT_SMOOTH); 
     glDisable(GL_LINE_SMOOTH); 
     glEnable(GLUT_MULTISAMPLE);
    //Draw some smooth polygon
    

    多重采样缓存区在默认情况下使⽤⽚段RGB值,并不包含颜⾊的alpha成分,我们可以通过调⽤glEnable来修改这个⾏为:

    • GL_SAMPLE_ALPHA_TO_COVERAGE 使⽤alpha值
    • GL_SAMPLE_ALPHA_TO_ON 使⽤alpha值并设为1,并使⽤它。
    • GL_SAMPLE_COVERAGE 使⽤glSampleCoverage所设置的值。
      当启⽤ GL_SAMPLE_COVERAGE 时,可以使⽤glSampleCoverage函数允许指定⼀个特定的值,它是与⽚段覆盖值进⾏按位与操作的结果。

    (本文为学习笔记,图片来自cc老师)

    相关文章

      网友评论

          本文标题:OpenGL 渲染常见问题

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