LearnOpenGL笔记——四、高级OpenGL:“几何着色器”、“实例化”和“抗锯齿”

四、高级OpenGL:“几何着色器”、“实例化”和“抗锯齿”

4.9 几何着色器

  • 在顶点和片段着色器之间有一个可选的几何着色器(Geometry Shader),几何着色器的输入是一个图元(如点或三角形)的一组顶点
  • 几何着色器可以在顶点发送到下一着色器阶段之前对它们随意变换
  • 然而,几何着色器最有趣的地方在于,它能够将(这一组)顶点变换为完全不同的图元,并且还能生成比原来更多的顶点
  • 废话不多说,我们直接先看一个几何着色器的例子:
    #version 330 core
    layout (points) in;
    layout (line_strip, max_vertices = 2) out;
    
    void main() {    
        gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0); 
        EmitVertex();
    
        gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0);
        EmitVertex();
    
        EndPrimitive();
    }
    
  • 在几何着色器的顶部,我们需要声明从顶点着色器输入的图元类型。这需要在in关键字前声明一个布局修饰符(Layout Qualifier)。
  • 这个输入布局修饰符可以从顶点着色器接收下列任何一个图元值:
    在这里插入图片描述
  • 接下来,我们还需要指定几何着色器输出的图元类型,这需要在out关键字前面加一个布局修饰符。
  • 和输入布局修饰符一样,输出布局修饰符也可以接受几个图元值:
    • points
    • line_strip
    • triangle_strip
  • 有了这3个输出修饰符,我们就可以使用输入图元创建几乎任意的形状了。要生成一个三角形的话,我们将输出定义为triangle_strip,并输出3个顶点。
  • 几何着色器同时希望我们设置一个它最大能够输出的顶点数量(如果你超过了这个值,OpenGL将不会绘制多出的顶点),这个也可以在out关键字的布局修饰符中设置。
  • 在这个例子中,我们将输出一个line_strip,并将最大顶点数设置为2个。
    在这里插入图片描述
  • 如果使用的是上面定义的着色器,那么这将只能输出一条线段,因为最大顶点数等于2。
  • 为了生成更有意义的结果,我们需要某种方式来获取前一着色器阶段的输出。GLSL提供给我们一个内建(Built-in)变量,在内部看起来(可能)是这样的:
    in gl_Vertex
    {
        vec4  gl_Position;
        float gl_PointSize;
        float gl_ClipDistance[];
    } gl_in[];
    
  • 这里,它被声明为一个接口块(Interface Block,我们在上一节已经讨论过),它包含了几个很有意思的变量,其中最有趣的一个是gl_Position,它是和顶点着色器输出非常相似的一个向量。
  • 要注意的是,它被声明为一个数组,因为大多数的渲染图元包含多于1个的顶点,而几何着色器的输入是一个图元的所有顶点
  • 有了之前顶点着色器阶段的顶点数据,我们就可以使用2个几何着色器函数,EmitVertex和EndPrimitive,来生成新的数据了。
    void main() {
        gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0); 
        EmitVertex();
    
        gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0);
        EmitVertex();
    
        EndPrimitive();
    }
    
  • 每次我们调用EmitVertex时,gl_Position中的向量会被添加到图元中来。
  • 当EndPrimitive被调用时,所有发射出的(Emitted)顶点都会合成为指定的输出渲染图元。
  • 在一个或多个EmitVertex调用之后重复调用EndPrimitive能够生成多个图元。
  • 在这个例子中,我们发射了两个顶点,它们从原始顶点位置平移了一段距离,之后调用了EndPrimitive,将这两个顶点合成为一个包含两个顶点的线条。
  • 另外的进一步实践,详见代码:
    • 造几个房子
    • 爆破物体
    • 法向量可视化

4.10 实例化

  • 渲染几乎是瞬间完成的,但上千个渲染函数调用却会极大地影响性能。
  • 如果我们需要渲染大量物体时,代码看起来会像这样:
    for(unsigned int i = 0; i < amount_of_models_to_draw; i++)
    {
        DoSomePreparations(); // 绑定VAO,绑定纹理,设置uniform等
        glDrawArrays(GL_TRIANGLES, 0, amount_of_vertices);
    }
    
  • 如果像这样绘制模型的大量实例(Instance),你很快就会因为绘制调用过多而达到性能瓶颈。
  • 与绘制顶点本身相比,使用glDrawArrays或glDrawElements函数告诉GPU去绘制你的顶点数据会消耗更多的性能
  • 如果我们能够将数据一次性发送给GPU,然后使用一个绘制函数让OpenGL利用这些数据绘制多个物体,就会更方便了。这就是实例化(Instancing)
  • 实例化这项技术能够让我们使用一个渲染调用来绘制多个物体,来节省每次绘制物体时CPU -> GPU的通信,它只需要一次即可。
  • 如果想使用实例化渲染,我们只需要将glDrawArraysglDrawElements的渲染调用分别改为glDrawArraysInstancedglDrawElementsInstanced就可以了。
  • 这些渲染函数的实例化版本需要一个额外的参数,叫做实例数量(Instance Count),它能够设置我们需要渲染的实例个数。
  • 这样我们只需要将必须的数据发送到GPU一次,然后使用一次函数调用告诉GPU它应该如何绘制这些实例。GPU将会直接渲染这些实例,而不用不断地与CPU进行通信。
  • 这个函数本身并没有什么用。渲染同一个物体一千次对我们并没有什么用处,每个物体都是完全相同的,而且还在同一个位置。我们只能看见一个物体!出于这个原因,GLSL在顶点着色器中嵌入了另一个内建变量,gl_InstanceID
  • 在使用实例化渲染调用时,gl_InstanceID会从0开始,在每个实例被渲染时递增1。比如说,我们正在渲染第43个实例,那么顶点着色器中它的gl_InstanceID将会是42。因为每个实例都有唯一的ID,我们可以建立一个数组,将ID与位置值对应起来,将每个实例放置在世界的不同位置。

一组例子:非实例化与实例化

  • 具体看官网代码即可
  • glVertexAttribDivisor
    • 这个函数告诉了OpenGL该什么时候更新顶点属性的内容至新一组数据。它的第一个参数是需要的顶点属性,第二个参数是属性除数(Attribute Divisor)
    • 默认情况下,属性除数是0,告诉OpenGL我们需要在顶点着色器的每次迭代时更新顶点属性。
    • 将它设置为1时,我们告诉OpenGL我们希望在渲染一个新实例的时候更新顶点属性。
    • 而设置为2时,我们希望每2个实例更新一次属性,以此类推。
      -c 我们将属性除数设置为1,是在告诉OpenGL,处于位置值2的顶点属性是一个实例化数组。

另一组例子:小行星带

  • 再次理解glVertexAttribPointer与顶点着色器location的关系!
  • 用四个vec4存储mat4
  • 具体看官网代码即可
  • 可以看到,在合适的环境下,实例化渲染能够大大增加显卡的渲染能力。正是出于这个原因,实例化渲染通常会用于渲染草、植被、粒子,以及上面这样的场景,基本上只要场景中有很多重复的形状,都能够使用实例化渲染来提高性能。

4.11 抗锯齿

  • 关于Aliasing产生的原因,可参考GAMES101
  • 超采样抗锯齿(Super Sample Anti-aliasing, SSAA)
    • 使用比正常分辨率更高的分辨率(即超采样)来渲染场景,当图像输出在帧缓冲中更新时,分辨率会被下采样(Downsample)至正常的分辨率。这些额外的分辨率会被用来防止锯齿边缘的产生。
    • 虽然它确实能够解决走样的问题,但是由于这样比平时要绘制更多的片段,它也会带来很大的性能开销。所以这项技术只拥有了短暂的辉煌。

多重采样

  • 多重采样抗锯齿(Multisample Anti-aliasing, MSAA)
  • 关于多重采样的原理,可参考GAMES101

OpenGL中的MSAA

  • 大多数的窗口系统都应该提供了一个多重采样缓冲,用以代替默认的颜色缓冲。
  • GLFW同样给了我们这个功能,我们所要做的只是提示(Hint) GLFW,我们希望使用一个包含N个样本的多重采样缓冲。
  • 这可以在创建窗口之前调用glfwWindowHint来完成。
    glfwWindowHint(GLFW_SAMPLES, 4);
    
  • 现在再调用glfwCreateWindow创建渲染窗口时,每个屏幕坐标就会使用一个包含4个子采样点的颜色缓冲了。GLFW会自动创建一个每像素4个子采样点的深度和样本缓冲。这也意味着所有缓冲的大小都增长了4倍
  • 我们还需要调用glEnable并启用GL_MULTISAMPLE,来启用多重采样。在大多数OpenGL的驱动上,多重采样都是默认启用的,所以这个调用可能会有点多余,但显式地调用一下会更保险一点。
    glEnable(GL_MULTISAMPLE);
    

离屏MSAA

  • 具体操作过程见官网

自定义抗锯齿算法

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值