美文网首页OpenGL相关
OpenGL基础渲染

OpenGL基础渲染

作者: 沙克阿拉卡 | 来源:发表于2020-08-16 22:19 被阅读0次

    第三章 基础渲染

    本章内容

    • OpenGL渲染基础架构
    • 如何使用7种OpenGL几何图元
    • 如何使用存储着色器
    • 如何使用uniform值和属性
    • 如何使用GLBatch帮助类传递几何图形
    • 如何执行深度测试和背面消除
    • 如何绘制透明或混合几何图形
    • 如何绘制抗锯齿点、线和多边形

    一、基础图形管线:

           OpenGL中的图元只不过是顶点的集合以预定义的方式结合在一起了。基础渲染管线接受3个顶点并将它们转换成一个三角形。


           客户机-服务器
           将管线分成两部分,上部分是客户机端,下部分是服务器端。
           对于OpenGL,客户端是存储在CPU存储器中的,在应用程序中执行,或在主系统内存的驱动程序中执行;程序将渲染命令和数据组合起来,发送到服务器执行。服务器会跨域一些系统总线,实际上,它就是图形加速卡上的硬件和内存。
    客户机不断地讲数据块和命令组合在一起并送入缓冲区,然后缓冲区会发送到服务器执行。服务器将执行缓冲区中的内容,于此同时客户端又做好了发送下一个用于渲染的数据或信息的准备。
    若服务器停止工作等待客户机或客户机停止工作等待服务器做好解释更多指令和数据的准备,这种情况成为管线停滞。是注重性能者的噩梦。
           着色器:
           着色器是用GLSL编写的。
           准备就绪的着色器程序在第一阶段构成顶点着色器,在第二阶段构成片段着色器。这是简化方式的理解。实际上还有几何着色器(选择性的)安排在两者之间,还有一些片段的处理如混合、模板和深度测试等。有三种向OpenGL着色器传递渲染数据方法可供选择,属性,uniform和纹理。
           属性:
           所谓属性就是一个对每个顶点都要作改变的数据元素。实际顶点位置本身就是一个属性。
    属性会从本地客户机内存中复制,存储在图形硬件中的一个缓冲区上。这些属性只供顶点着色器使用,对于片段着色器来说没有意义。
           Uniform:
           通常设置完Uniform变量就紧跟着发出渲染一个图元批次的命令。Uniform变量实际上可以无次数限制的使用,可以设置一个应用于整个表面的单个颜色值,还可以设置一个时间值,在每次渲染某种类型的顶点时修改它(Uniform变量在每个批次改变一次,而不是每个顶点改变一次)。顶点着色器和片段着色器多可以有Uniform变量。
    Uniform变量一个常见的应用是在顶点渲染中设置变换矩阵。
           纹理:
           可以传递到着色器的第三种数据类型是纹理数据。从顶点着色器和片段着色器中都可以对纹理值进行采样和筛选。
           输出:
           输出数据(Out)是作为一个阶段着色器的输出定义的,而在后续阶段的着色器则是作为输入定义的。客户端接触不到这内部变量。

    二、创建坐标系:

           正投影(正方体)
           在正投影中,所有在指定空间范围内的东西都会被显示在屏幕上,而不存在照相机或视点坐标系的概念。
           GLFrustum::Setorthographic(GLfloat xMix, GLfloat xMax, GLfloat yMix, GLfloat yMax, GLfloat zMix, GLfloat zMax,));
           透视投影(平截头体)
           平截头体:一个金字塔被截短之后的形状。GLFrustum类通过调用SetPerspective方法构建一个平截头体。
           GLFrustum::SetPerspective(GLfloat fFov, GLfloat fAspect, GLfloat fNear, GLfloat fFar);

    三、使用存储着色器:

           属性,存储着色器为每个变量都使用一致的变量命名规则和相同的属性槽。


           Uniform值,UseStockShader函数会选择一个存储着色器并提供一个着色器的Uniform值:
           GLShaderManager::UseStockShader(GLenum shader, ……);

    单元着⾊器

    //参数1: 存储着⾊器种类-单元着⾊器
    //参数2: 颜⾊值
    GLShaderManager::UserStockShader(GLT_SHADER_IDENTITY, GLfloat vColor[4]);
    

           使⽤场景:绘制默认OpenGL 坐标系(-1,1)下图形。 图形所有片段都会以⼀种颜⾊填充。

    平⾯着⾊器

    //参数1: 存储着⾊器种类-平⾯着⾊器
    //参数2: 允许变化的4*4矩阵
    //参数3: 颜⾊色值
    GLShaderManager::UserStockShader(GLT_SHADER_FLAT, GLfloat mvp[16], GLfloat vColor[4]);
    

           使⽤场景:在绘制图形时, 可以应⽤变换(模型/投影变化)。

    上⾊着⾊器

    //参数1: 存储着⾊器种类-上⾊着⾊器
    //参数2: 允许变化的4*4矩阵
    GLShaderManager::UserStockShader(GLT_SHADER_SHADED, GLfloat mvp[16]);
    

           使⽤场景:在绘制图形时, 可以应⽤变换(模型/投影变化)。颜色将会平滑地插入到顶点之间,称为平滑着色。

    默认光源着⾊器

    //参数1: 存储着⾊器种类-默认光源着⾊器
    //参数2: 模型4*4矩阵
    //参数3: 投影4*4矩阵
    //参数4: 颜⾊值
    GLShaderManager::UserStockShader(GLT_SHADER_DEFAULT_LIGHT, GLfloat mvMatrix[16], GLfloat pMatrix[16], GLfloat vColor[4]);
    

           使⽤场景:在绘制图形时, 可以应⽤变换(模型/投影变化)。这种着⾊器会使绘制的图形产生阴影和光照的效果。

    点光源着⾊器

    //参数1: 存储着⾊器种类-点光源着⾊器
    //参数2: 模型4*4矩阵
    //参数3: 投影4*4矩阵
    //参数4: 点光源的位置
    //参数5: 漫反射颜⾊值
    GLShaderManager::UserStockShader(GLT_SHADER_POINT_LIGHT_DIEF, GLfloat mvMatrix[16], GLfloat pMatrix[16], GLfloat vLightPos[3],GLfloat vColor[4]);
    

           使⽤场景:在绘制图形时, 可以应用变换(模型/投影变化)。这种着⾊器会使绘制的图形产⽣阴影和光照的效果。它与默认光源着⾊器⾮常类似,区别只是光源位置可能是特定的。

    纹理替换矩阵着⾊器

    //参数1: 存储着⾊器种类-纹理替换矩阵着⾊器
    //参数2: 模型4*4矩阵
    //参数3: 纹理单元
    GLShaderManager::UserStockShader(GLT_SHADER_TEXTURE_REPLACE, GLfloat mvMatrix[16], GLint nTextureUnit);
    

           使⽤场景:在绘制图形时, 可以应⽤变换(模型/投影变化)。这种着⾊器通过给定的模型视图投影矩阵,使⽤纹理单元来进⾏颜⾊填充。其中每个像素点的颜⾊是从纹理中获取。

    纹理调整着⾊器

    //参数1: 存储着⾊器种类-纹理调整着⾊器
    //参数2: 模型4*4矩阵
    //参数3: 颜⾊值
    //参数4: 纹理单元
    GLShaderManager::UserStockShader(GLT_SHADER_TEXTURE_MODULATE, GLfloat mvMatrix[16], GLfloat vColor[4], GLint nTextureUnit);
    

           使⽤场景:在绘制图形时, 可以应⽤变换(模型/投影变化)。这种着⾊器通过给定的模型视图投影矩阵。着⾊器将⼀个基本⾊乘以⼀个取⾃纹理单元nTextureUnit 的纹理,将颜⾊与纹理进⾏颜⾊混合后才填充到⽚段中。

    纹理光源着⾊器

    //参数1: 存储着⾊器种类-纹理光源着⾊器
    //参数2: 模型4*4矩阵
    //参数3: 投影4*4矩阵
    //参数4: 点光源位置
    //参数5: 颜⾊值
    //参数6: 纹理单元
    GLShaderManager::UserStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIEF, GLfloat mvMatrix[16], GLfloat pMatrix[16], GLfloat vLightPos[3], GLfloat vBaseColor[4],GLint nTextureUnit);
    

           使⽤用场景:在绘制图形时, 可以应⽤变换(模型/投影变化)。这种着⾊器通过给定的模型视图投影矩阵,着⾊器将⼀个纹理通过漫反射照明计算进⾏调整(相乘)。
           GL_TRIANGLE_STRIP优点:
           • 用前3个顶点指定第1个三角形之后,接下来的每⼀个三⻆形,只需要再指定1个顶点。需要绘制⼤量的三⻆形时,采⽤这种⽅法可以节省⼤大量的程序代码和数据存储空间。
           • 提供运算性能和节省带宽。更少的顶点意味着数据从内存传输到图形卡的速度更快,并且顶点着⾊器需要处理的次数也更少。

    四、将点连接起来

    基本图元:
    7种基本图元


    图元说明

    一个简单批次容器
           GLTools库中包含一个简单的容器类,GLBatch。这个类可以作为表中列出的7种图元简单批次使用,而且它知道在使用GLShaderManager支持的任意存储着色器时如何对图元进行渲染。
    不希望出现的几何图形----正面和背面剔除:
           从任何⼀个⽅向去观察一个立方体,最多可以看到3个⾯。如果我们能以某种⽅式去丢弃这部分数据。OpenGL在渲染的性能即可提高超过50%。
           任何平⾯都有2个⾯:正⾯和背面,⼀个时刻我们只能看到一面。通过分析顶点数据的顺序,OpenGL可以做到检查所有正面朝向观察者的面,并渲染它们;从⽽丢弃背面朝向的面。
           正/背面的区分:
           • 正⾯:按照逆时针顶点连接顺序的三⻆形⾯
           • 背⾯:按照顺时针顶点连接顺序的三角形⾯
           当观察者在右侧时:右边的三角形为逆时针方向,则为正面;而左侧的三⻆形为顺时针,则为背⾯。当观察者在左侧时:左边的三⻆形为逆时针⽅向,则为正⾯;而右侧的三⻆形为顺时针,则为背⾯。
           弊端:如果前后两个点都是正面或是背面,这时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);
    
    //剔除正面实现一
    glCullFace(GL_BACK);
    glFrontFace(GL_CW); 
    
    //剔除正面实现二
    glCullFace(GL_FRONT);
    glFrontFace(GL_CCW);
    

    深度测试
           深度:就是像素点在3D世界中距离摄像机的距离,即Z值。
    深度缓存区,就是⼀块内存区域,专门存储每个像素点的深度值。深度值(Z值)越⼤,则离摄像机就越远。在不使⽤深度测试的时候,如果先绘制⼀个⽐较近的物体,再绘制较远的物体。较远的图像就会像油画一样覆盖掉之前的图像有了深度缓冲区后,绘制物体的顺序就不那么重要了。只要通过开启了深度缓冲区,并允许深度值的写入,OpenGL都会把像素的深度值写入到缓冲区中。
           深度测试:深度缓冲区和颜⾊缓存区是对应的。颜⾊缓存区存储像素的颜⾊信息,而深度缓冲区存储像素的深度信息。
    在决定是否绘制⼀个物体表⾯时,首先要将表面对应的像素的深度值与当前深度缓冲区中的值进⾏⽐较。如果大于深度缓冲区中的值,则丢弃这部分;否则利⽤这个像素对应的深度值和颜⾊值,分别更新深度缓冲区和颜色缓存区。这个过程称为深度测试。
           深度值的计算:深度值,⼀般由16位、24位或者32位值表示,通常是24位。位数越高,深度的精确度越好。深度值的范围在[0,1]之间,值越⼩表示越靠近观察者,值越大表示远离观察者。
           深度值的使用:

    //开启深度测试
    glEnable(GL_DEPTH_TEST);
    //在绘制场景前,清除颜⾊缓存区,深度缓冲。清除深度缓冲区默认值为1.0。
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //指定深度测试判断模式
    void glDepthFunc(GLEnum mode);
    //深度缓冲区写入开关
    //value: GL_TURE,开启深度缓冲区写入; GL_FALSE,关闭深度缓冲区写⼊
    void glDepthMask(GLBool value);
    

           使⽤正⾯/背面剔除法和深度测试法解决了OpenGL的渲染效率问题。

    ZFighting闪烁问题
           由于精度的限制,对于相差非常小的深度值(比如在同一个深度进行2次渲染),就可能出现不能正确区分两个深度值的问题,导致测试的结果随机出现。所以,显示时2个画⾯交错出现,就会出现闪烁问题。
           多边形偏移:
           解决z-fighting问题可用多边形偏移
           多边形模式:多边形(含三角形)不一定是实心的。默认情况下。多边形是作为实心图形绘制的,但我们可以通过将多边形指定为显示轮廓或只有顶点。函数glPolygonMode允许将多边形渲染成实体、轮廓或只有点。
           多边形偏移实现:

    //1:启用Polygon Offset
    //增大重叠或深度值接近的2个图形的深度值差距,使得OpenGL可以区分两个深度值。
    glEnable(GL_POLYGON_OFFSET_FILL);
    //2:指定偏移量
    //通过glPolygonOffset来指定2个参数 factor和units
    //应⽤到⽚段上总偏移计算⽅程式
    //Depth Offset = (DZ * factor) + (r * units);
    //DZ:深度值(Z值)
    //r:使得深度缓冲区产⽣变化的最⼩值,是由具体OpenGL平台指定的⼀个常量
    void glPolygonOffset(Glfloat factor, Glfloat units);
    //offset为负值,将使得z值距离摄像机更近;⽽正值,将使得z值距离摄像机更远。 一般而言,我们设置factor和units设置为-1.0和-1.0。
    //3:关闭Polygon Offset
    // 参数和开启的参数相同
    glDisable(GL_POLYGON_OFFSET_FILL);
    

    五、裁剪

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

    //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);** 理解窗口、视口、裁剪区域**
    

           窗口:显示界面。就相当于iOS里面的window。
           视口:窗口中⽤来显示图形的一块矩形区域,它可以和窗⼝等⼤,也可以⽐窗口⼤或者小。只有绘制在视口区域中的图形才能被显示,如果图形有一部分超出了视口区域,那么那一部分是看不到的。通过glViewport()函数设置。就相当于View。
           裁剪区域(平⾏投影):视口矩形区域的最小最大x坐标(left,right)和最小最⼤y坐标 (bottom,top),⽽不是窗口的最小最大x坐标和y坐标。通过glOrtho()函数设置,这个函数还需指定最近最远z坐标,形成一个立体的裁剪区域。 就相当于设置一个frame。

    六、混合

           OpenGL渲染时会把颜色值存在颜⾊缓存区中,每个⽚段的深度值也是放在深度缓冲区。当深度缓冲区被关闭时,新的颜色将简单地覆盖原来颜色缓存区存在的颜色值。当深度缓冲区再次打开时,新的颜⾊片段只是当它们比原来的值更接近邻近的裁剪平⾯才会替换原来的颜⾊片段。
    开启混合
           gl_Enable(GL_BIEND);
    颜色混合
           • ⽬标颜色:已经存储在颜色缓存区的颜色值 (已经存在的颜色,旧颜色)
           • 源颜色:作为当前渲染命令结果进入颜色缓存区的颜⾊值 (新进来的颜色 ,新颜色)
           当混合功能被开启时,源颜色和⽬标颜色的组合方式是混合方程式控制的。在默认情况下,混合方程式如下所示:

    //Cf: 最终计算参数的颜⾊
    //Cs: 源颜⾊
    //Cd: 目标颜⾊
    //S: 源混合因⼦,源Alpha混合因子
    //D: ⽬标混合因⼦,⽬标Alpha混合因子
    Cf = (Cs * S) + (Cd * D);
    

           混合函数经常用于实现在其他一些不透明的物体前面绘制一个透明物体的效果。
    混合方程式
           实际上不止一种颜色混合方程式,OpenGL有5个不同的方程式进行选择。
           glbBlendEquation(GLenum mode);


    设置混合因⼦
           //S:源混合因⼦
           //D:⽬标混合因子
           glBlendFunc(GLenum S, GLenum D);

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

    常量混合颜色

    //默认初始化为⿊色(0, 0, 0, 0),通过下面的函数可以修改这个颜色。
    void glBlendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha );
    
    //glBlendFuncSeparate函数
    //除了能使⽤OpenGL内置的混合因⼦,还可以有更灵活的选择。
    //strRGB: 源颜色的混合因⼦
    //dstRGB: 目标颜⾊的混合因⼦
    //strAlpha: 源颜⾊的Alpha因⼦
    //dstAlpha: 目标颜⾊的Alpha因⼦
    void glBlendFuncSeparate(GLenum strRGB, GLenum dstRGB , GLenum strAlpha, GLenum dstAlpha);
    

    抗锯齿
           OpenGL混合功能的另一个用途是抗锯齿,为了消除图元之间的锯齿状边缘,OpenGL使用混合片段的颜色,也就是把像素的目标颜色和周围像素的颜色进行混合。从本质上说,在任何图元的边缘上,像素颜色会稍微延伸到相邻的像素。

    glEnable(GL_POINT_SMOOTH);
    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_POLYGON_SMOOTH);
    

    多重采样
           抗锯齿处理的最大优点之一就是能够使多边形的边缘更为平滑,使渲染效果显得更为自然和逼真。点和线的平滑处理是得到广泛支持的,但多边形的平滑处理并没有在所有的平台都实现。即是在可以使用GL_POLYGON_SMOOTH的时候,对整个场景进行抗锯齿处理并没有想象中那么方便。
           OpenGL新增了一个特性,称为多重采样,可以来解决这个问题。OpenGL1.3特性,已经包含了颜色、深度和模板值得帧缓冲区会添加一个额外的缓冲区,所有图元在每一个像素上进行多次采样,其结果就存储在这个缓冲区中。

    //请求一个多重采样、完成颜色、带深度的双缓冲帧缓冲区,可以调用:
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE);
    //可以使用glEnable/glDisable组合(使用GL_MULTISAMPLE)打开或关闭多重采样:
    glEnable(L_MULTISAMPLE);
    glDisable(L_MULTISAMPLE).
    

    相关文章

      网友评论

        本文标题:OpenGL基础渲染

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