• OpenGL 四


    代码 or demo查看请滑动到最后。

    一、问题场景:

    在 3D 图形的渲染过程中,我们是需要来决定哪部分是要对观察者 可见/不可见 的,对于不可见的部分,我们就没有渲染的必要了,要及早丢弃掉他们。例子:一间草屋,我们站在门前的时候,草屋的背后我们是看不到的,那么就不要渲染它了。否则就会出现下图中的场景(我们绘制一个立体图形后 对其进行旋转操作查看时,背面我们本应是看不到的面也被看到了),叫做“隐藏面消除”(Hidden surface elimination)。 (图中的)

    背面为什么黑色呢?光源着色器,想象一下我们在太阳光下,光打下来,朝阳和背阳的两个场景下,是不是一面亮一面暗呢。

    二、解决方案

    1:油画算法(过时且浪费性能-重复渲染)

      先绘制画面中距离观察者远的物体,再绘制较*的物体。由远及*,如下图,远的会被*物遮住。

    但是如果是下面这种场景,油画算法就无法解决了:

     这种每个面都要看到部分的场景怎么办呢?

    2、正背面剔除(Face Culling)

    背景

      想象一个 3D 图形,从任何一个⽅向去观察,我们最多可以看到⼏个⾯? 最多3面。从⼀个⽴方体的任意位置和⽅向上看,最多不可能看到多于3个面。那么,为何还要多余的去绘制那些根本看不到的3个面呢? 如果能以某种⽅式去丢弃这部分数据, OpenGL 在渲染的性能就可以提高超过 50% 呢。

    问题分析:

      如何知道某个⾯在观察者的视口中不会出现? 任何*⾯都有2个面:正⾯/背面。这意味着我们在一个时刻只能看到一⾯。

      OpenGL 可以做到检查所有正面朝向观察者的面 并渲染它们,⽽丢弃背⾯朝向的面,这样可以节约片元着⾊器的性能。

      OpenGL 如何知道我们绘制的图形哪是正面呢?可通过分析顶点数据的顺序

    分析顶点数据:

    1)正背面区分:OpenGL 中 按逆时针进行顶点相连的三角形面为 正面,顺时针相连的三角形面为 背面 -- 规则如此

    2)立方体的正背面

    眼睛在右侧时,右边顶点按逆时针顺序,为正面,左侧为顺时针背面;眼睛在左侧时,则左侧为正面。

    归结:

    正面和背面是由三角形的顶点定义顺序鹅观察者方向共同决定的,随着观察者观察角度的改变,正背面也会跟着改变。

    解决操作:

      a、开启表面剔除(默认背面剔除)
      void glEnable(GL_CULL_FACE); 

      b、关闭表⾯剔除(默认背面剔除)
      void glDisable(GL_CULL_FACE); 

      c、用户选择剔除哪个面(正面/背面) 

      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); 

    我们对背面进行剔除后,图形好像正常了,但是,旋转到一定角度,发现:

    这又是怎么回事儿呢?

    下图中,当我们旋转到这个角度继续旋转时,此时A、B面都是正面,但继续旋转至A、B面重合,此时OpenGL无法区分谁才是正面应该也无法确定要显示A or B,因而出现了上图的场景。

    我们应该怎么在解决隐藏面消除的同时,告诉OpenGL我们要显示谁呢?

    3、Z-buffer 方法(深度缓冲区Depth-buffer)

    1)深度

    a、深度:像素点在3D世界中,距离观察者(眼睛)的距离 -- Z值(这个值是指物体的Z值)

    b、深度缓冲区:存储在显存值的一块区域,它主要就存储每个像素点的深度值,Z绝对值越大,说明距离观察者越远 -- 想象我们的现实世界

      Z值的绝对值:因为我们可能在Z轴的负轴。负值(负数)越小对应的绝对值(正数)越大。

    c、深度缓冲区用来做什么呢

      想象一个场景:当我们绘制一幅草屋图时,不按远*顺序而是随意绘制,若先画了*处场景(门前的栏杆)再画远处场景(大门),那么此时在我们的画中大门就在栏杆的前面了,这是不符合逻辑的。想象下在现实生活中,如果眼前远景和*景无法分辨产生了混乱,我们眼前的世界就光怪陆离了。

      有了深度,我们就可以知道每个点距离我们眼睛的远*,进而也就知道当2处重叠时我们应该看见的是哪一部分了 --> 对比到OpenGL上,有了深度缓冲区,知道了深度值,绘制时也就不担心绘制顺序和渲染谁了。 OpenGL中,只要存在深度缓存区(缓存==缓冲 名字而已),OpenGL都会把像素的深度值写入进去。除非手动禁止写入:glDepthMask(GL_FALSE)。(深度缓冲区中针对某个像素点 只存一个对应值,重叠时我们需要知道应该存哪个值)

    2)深度测试

    a、什么是深度测试?DepthBuffer(深度缓冲区)和ColorBuffer(颜色缓冲区)是一一对应的,我们知道颜色缓冲区是存储像素的颜色信息,深度缓冲区是存储像素的深度信息。在决定是否要绘制某个物体表面时,首先要将表面对应的像素深度(A)与深度缓冲区中的深度值(B)进行对比,A>B则丢弃掉A对应的表面不绘制;A<B,则拿出这个像素对应的 深度值和颜色值,对应的分别去更新深度缓冲区和颜色缓冲区。这个过程称为“深度测试”。

     

    b、深度测试的使用

    开启:glEnable(GL_DEPTH_TEST)  -- 深度测试的值默认1,范围[0,1]

    绘制开始前,首先清空缓冲区(颜色和深度都要清):glClear()GL_COLEO_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    深度测试的模式 mode

    关闭:glDisable(GL_DEPTH_TEST)  

    开启深度测试后:我的圈圈终于正常了,它是不是不会再出问题了呢?

    开启深度测试也是存在风险的

    4、ZFlighting 闪烁问题 - (深度测试的潜在风险)

    1)产生场景受限于设备的精度,当我们的绘制精度 大于设备所支持精度就会出现。(根本原因:精度。目前的设备上较少会出现)

    原因:开启深度测试后,OpenGL 就不会绘制被遮挡的部分了。此时绘制的图形已符合我们的现实场景了,但是,由于深度缓存区精度的限制,当出现两个像素的深度值相差很小(例子:0.0000005/0.0000007)时 --> OpenGL可能无法正确判断2个值大小 -->  深度测试的结果不确定性 --> 画面交错闪烁(AB的重叠绘制 可能出现随机绘制 - 一会儿A一会儿B)。如下图白框中的画面

    2)解决办法

    导致问题的原因既然是因为2个深度值相差太小了,那我们就想办法把2个值偏差的大点 --> 深度测试前,将两个深度值 手动做下细微的修改 使之可以区分 --> 手动复杂又不精确 --> OpenGL提供了办法:多边形偏移 -- 使产生 ZFlighting 的图层产生一个的偏移,即深度值间产生一个间隔。

    a、启用多边形偏移:

      glEnable(GL_POLYGON_OFFSET_FILL)

        GL_POLYGON_OFFSET_FILL:对应光栅化的 GL_FILL

        GL_POLYGON_OFFSET_LINE:对应光栅化的 GL_LINE

        GL_POLYGON_OFFSET_POINT:对应光栅化的 GL_POINT

    b、指定偏移量

      glPolygonOffset(Glfloat factor,Glfloat units) 

        深度:Depth offset = DZ * factor + r * units // 负值,使得模型距离观察者更*;正值,将使得模型距离观察者更远  

        DZ:多边形的深度斜率 最大值

        r:使深度缓冲区产生变化的最小值,即可分辨的最小差异值

      这里我们只需传 factor 和 units 两个值给 glPolygonOffset 即可:一般传入-1, -1

    c、关闭 

      glDisable(GL_POLYGON_OFFSET_FILL)      

    3)预防 

      a、不要将2个物体放太*,避免渲染时重叠。这种方式要求对场景中物体插入一个少量的偏移,自然这个操作是要付出一定代价的。

      b、将裁剪面设计的距离观察者远些,上⾯我们看到,在*裁剪*⾯附*,深度的精确度是很高的,因此尽可能让*裁剪⾯远一些的话,会使整个裁剪范围内的精确度变高一些。但是这种⽅式会使离观察者较*的物体被裁减掉,因此需要调试好裁剪⾯参数。

      c、使用高位数的深度缓存区。通常使用的深度缓存区是24位的,现在有一些使用32位的缓冲区的硬件设备,使精度提高。

      

    三、主要代码

    压栈:记录临时状态 保存一下下 - 状态的回滚

    出栈:恢复初始状态 -- 不需要了 记录的数据就可以扔掉,避免对后面的渲染产生异常影响  ---> 有点类似 Git 的 stash 命令

      1 GLBatch triangleBatch;
      2 
      3 GLShaderManager shaderManager;
      4 
      5 // 设置角色帧,作为相机
      6 GLFrame  viewFrame;// 观察者位置
      7 GLFrustum           viewFrustum;// 透视投影 - GLFrustum类
      8 GLTriangleBatch     torusBatch;
      9 // 两个矩阵
     10 GLMatrixStack       modelViewMatix;// 模型矩阵
     11 GLMatrixStack       projectionMatrix;// 透视矩阵
     12 //
     13 GLGeometryTransform transformPipeline;// 几何转换
     14 
     15 
     16 // 定义字段判断 正背面剔除/深度测试 开启与否
     17 int iCull = 0;
     18 int iDepth = 0;
     19 
     20 
     21 // 右键菜单栏选项 开启关闭深度测试
     22 void ProcessMenu(int value) {
     23     
     24     switch(value)
     25     {
     26         case 1:
     27             iCull = !iCull;
     28             break;
     29 
     30         case 2:
     31             iDepth = !iDepth;
     32             break;
     45     }
     46 
     47     glutPostRedisplay();
     48 }
     49 
     50 // 键盘上下左右 事件
     51 // 观察者移动 物体不动
     52 void SpecialKeys(int key, int x, int  y) {
     53     
     54     if(key == GLUT_KEY_UP)
     55         viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f);
     56     
     57     if(key == GLUT_KEY_DOWN)
     58         viewFrame.RotateWorld(m3dDegToRad(5.0), 1.0f, 0.0f, 0.0f);
     59     
     60     if(key == GLUT_KEY_LEFT)
     61         viewFrame.RotateWorld(m3dDegToRad(-5.0), 0.0f, 1.0f, 0.0f);
     62     
     63     if(key == GLUT_KEY_RIGHT)
     64         viewFrame.RotateWorld(m3dDegToRad(5.0), 0.0f, 1.0f, 0.0f);
     65     
     66     //重新刷新window
     67     glutPostRedisplay();
     68 }
     69 
     70 // 初始化 设置
     71 void SetupRC() {
     72     
     73     // ================ 简单绘制一个立体的圈圈 =================
     74     // 设置背景颜色
     75      glClearColor(0.0f, 0.3f, 0.3f, 1.0f );
     76      
     77      //初始化着色器管理器
     78      shaderManager.InitializeStockShaders();
     79      
     80      //将相机向后移动7个单元:肉眼到物体之间的距离
     81      viewFrame.MoveForward(7.0);
     82      
     83      //创建一个圈
     84      //void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
     85      //参数1:GLTriangleBatch 容器帮助类
     86      //参数2:外边缘半径
     87      //参数3:内边缘半径
     88      //参数4、5:主半径和从半径的细分单元数量
     89      gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
     90     //点的大小
     91      glPointSize(4.0f);
    117 }
    118 
    119 
    120 // 视口  窗口大小改变时接受新的宽度和高度,其中0,0代表窗口中视口的左下角坐标,w,h代表像素
    121 void ChangeSize(int w,int h) {
    122     // 防止h变为0
    123     if(h == 0)
    124         h = 1;
    125     
    126     // 设置视口窗口尺寸
    127     glViewport(0, 0, w, h);
    128     
    129     // setPerspective 函数的参数是一个从顶点方向看去的视场角度(用角度值表示)
    130     // 设置透视模式,初始化其透视矩阵
    131     viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 100.0f);
    132     
    133     //4.把 透视矩阵 加载到 透视矩阵对阵中
    134     projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    135     
    136     //5.初始化渲染管线
    137     transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
    138 }
    139 
    140 // 渲染
    141 void RenderScene(void) {
    142     
    143     //清除窗口和深度缓冲区
    144     //
    145     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    146     
    147     
    148     // iCull 判断是否开启背面剔除 GL_CULL_FACE
    149     if(iCull)
    150     {
    151         glEnable(GL_CULL_FACE);
    152         glFrontFace(GL_CCW);
    153         glCullFace(GL_BACK);
    154     }
    155     else
    156         glDisable(GL_CULL_FACE);
    157     
    158     // iDepth 判断是否开启深度测试 GL_DEPTH_TEST
    159     if(iDepth)
    160         glEnable(GL_DEPTH_TEST);
    161     else
    162         glDisable(GL_DEPTH_TEST);
    163     
    164     //把摄像机矩阵压入模型矩阵中 入栈
    165     modelViewMatix.PushMatrix(viewFrame);
    166     
    167     GLfloat vRed[] = { 0.0f, 0.5f, 0.5f, 1.0f };// 画笔颜色
    168     
    169     //使用默认光源着色器
    170     //通过光源、阴影效果跟提现立体效果
    171     //参数1:GLT_SHADER_DEFAULT_LIGHT 默认光源着色器
    172     //参数2:模型视图矩阵
    173     //参数3:投影矩阵
    174     //参数4:基本颜色值
    175     shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
    176     
    177     //绘制
    178     torusBatch.Draw();
    179 
    180     //出栈
    181     modelViewMatix.PopMatrix();
    182     glutSwapBuffers();
    205 }
    206 
    207 int main(int argc,char* argv[]) {
    208     
    209     //设置当前工作目录,针对MAC OS X
    210     
    211     gltSetWorkingDirectory(argv[0]);
    212     
    213     //初始化GLUT库
    214     
    215     glutInit(&argc, argv);
    216     /* 初始化双缓冲窗口,其中标志GLUT_DOUBLE、GLUT_RGBA、GLUT_DEPTH、GLUT_STENCIL分别指
    217         双缓冲窗口、RGBA颜色模式、深度测试、模板缓冲区
    218      */
    219     glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
    220     
    221     //GLUT窗口大小,标题窗口
    222     glutInitWindowSize(800,600);
    223     glutCreateWindow("Triangle");
    224     
    225     //注册回调函数
    226     glutReshapeFunc(ChangeSize);
    227     glutDisplayFunc(RenderScene);
    228     glutSpecialFunc(SpecialKeys);// 注册键盘特殊键位(上下左右键) 处理点击事件
    229     
    230     // Create the Menu
    231     glutCreateMenu(ProcessMenu);// 右键菜单栏选项 开启关闭深度测试
    232     glutAddMenuEntry("Toggle cull backface",1);// 正背面剔除
    233     glutAddMenuEntry("Toggle depth test",2);// 深度测试的开启与否
    234 
    235     glutAttachMenu(GLUT_RIGHT_BUTTON);
    236     
    239     //驱动程序的初始化中没有出现任何问题。
    240     GLenum err = glewInit();
    241     if(GLEW_OK != err) {
    242         
    243         fprintf(stderr,"glew error:%s
    ",glewGetErrorString(err));
    244         return 1;
    245     }
    246     
    247     //调用SetupRC
    248     SetupRC();
    249     
    250     glutMainLoop();
    251     
    252     return 0;
    253 }

    Demo

  • 相关阅读:
    C,LINUX,数据结构部分
    LINUX应用开发工程师职位(含答案)
    INT32 System_UserKeyFilter(NVTEVT evt, UINT32 paramNum, UINT32 *paramArray)
    屏幕调试
    1.ARM嵌入式体系结构与接口技术(Cortex-A8版)
    NT9666X调试log
    DemoKit编译过程错误
    selenium 代理设置
    pandas 轮询dataframe
    Spring 定时任务
  • 原文地址:https://www.cnblogs.com/zhangzhang-y/p/13276529.html
Copyright © 2020-2023  润新知