这篇文章介绍片段着色器相关的两个部分,离屏渲染和反混叠。离屏渲染将内容渲染到用户自定义格式的缓存中。反混叠可以使渲染的物体更加平滑。
离屏渲染
到目前位置,我们程序中的窗体创建工作都是程序框架做的,其中封装了很多细节。虽然针对特定操作系统如何绑定窗体缓存的步骤,我们不得而知。但是我们可以通过这节内容,了解如何自行设置帧缓存,进而侧面感受一下窗体缓存的概念。
首先,我们需要创建一个帧缓存对象。可以通过 glCreateFramebuffers() 函数创建,其原型为:
- void glCreateFramebuffers(GLsizei n, GLuint *framebuffers);
可以看到创建帧缓存对象的参数和我们之前创建缓冲对象是一样的。同样,后续我们也需要将帧缓存对象绑定到目标点,需要使用 glBindFramebuffer() 函数。其原型为:
- void glBindFramebuffer(GLenum target, GLuint framebuffer);
其中,目标点可以是 GL_DRAW_FRAMEBUFFER、GL_READ_FRAMEBUFFER 或 GL_FRAMEBUFFER。我们一般选 GL_FRAMEBUFFER,表示将对象同时绑定到读取和绘制目标点。
需要注意的是,将 target 参数设置为 0,可以恢复到默认帧缓存,通常就是应用程序窗口对应的帧缓存。
target 参数设置为 0,可以恢复到默认帧缓存。
当我们创建和绑定好帧缓存对象后,我们需要将纹理对象附加到帧缓存对象,以作为要进行渲染的存储空间。帧缓存支持 3 种附加类型 —— 深度、模板和颜色附加,分别作为深度、模板和颜色缓存。附加可以通过 glFramebufferTexture() 函数完成,其原型为:
- void glFramebufferTexture(GLenum target,
- GLenum attachment,
- GLuint texture,
- GLint level);
其中 attachment 需要说明一下,就是用它指定附加类型。GL_DEPTH_ATTACHMENT 表示深度缓存;GL_STENCIL_ATTACHMENT 表示模板缓存;GL_COLOR_ATTACHMENTi 表示颜色缓存,因为颜色输出可以有多个,我们在 《片段处理与帧缓冲 - 片段着色器、单片段测试、颜色输出》 中已经了解过。
参数 level 指的是 mipmap 的 level。
因为颜色缓冲会有多个,所以还需要特别指定片段着色器需要输出值到哪些颜色缓冲。需要使用 glDrawBuffers() 函数指定,其原型为:
- void glDrawBuffers(GLsizei n, const GLenum *bufs);
其中 bufs 参数是包含指定缓冲的列表。常用的值为 GL_COLOR_ATTACHMENTn,含义和 glFramebufferTexture 中的颜色缓存是对应上的。值还可能是 GL_BACK_LEFT、GL_BACK_RIGHT 等,用于立体渲染。
立体渲染一节的实验没有做成功,窗体创建失败。应该是本地环境不支持立体渲染这种模式。
至此,我们目前需要用到的函数就都介绍完毕了。我们再看到代码清单 1.1 的帧缓存设置代码,就容易理解了:首先我们创建并绑定了帧缓存对象。接着我们创建了两个纹理对象,之后将这两个纹理对象分别当作颜色缓存和深度缓存,绑定到帧缓存对象。最后通过 glDrawBuffers() 指定颜色缓存输出。
我们接下来做的实验操作是,先渲染上述自定义的帧缓存对象,然后我们将渲染的输出结果当作另一个程序的纹理输入。
代码清单 1.2 是自定义帧缓存的片段着色器,它输出条纹状的颜色。代码清单 1.3 是需要渲染到窗体上的片段着色器,它的颜色从纹理中获取。
渲染过程如代码清单 1.4 所示。第 1 行绑定到自定义的帧缓存。第 3 至 10 行执行完就可以在帧缓存中得到渲染结果。接着第 12 行切换回默认窗体缓冲。注意第 17 行将绑定到帧缓存的颜色纹理作为下一个渲染程序的纹理输入。
图 1 是渲染的结果,可以看到转动的大立方体的各个面上,会有转动的条纹状颜色的小立方体。
分层渲染
分层渲染是指将渲染结果分层输出到可索引的 2D 纹理上,这和之前学习的数组纹理是一个概念。因为我们之前已经学习过数组纹理,所以分层渲染非常容易理解,我们直接看到代码。
帧缓存的设置如代码清单 1.5 所示,主要差异点就是需要创建数组纹理 —— GL_TEXTURE_2D_ARRAY。
着色器中使用内置变量 gl_Layer 来指定渲染的输出层数。如代码清单 1.6 所示,我们在几何着色器中实现分层渲染。几何着色器生成 16 层颜色、旋转角度各不相同的渲染结果。
最后同样是将各层渲染结果以数组纹理的方式显示到窗体上。结果如图 2 所示,有 12 个颜色、旋转角度各不相同的圆环。
反混叠
信号采样速率(采样率)不满足信号内容要求时会发生混叠。自己通俗的理解就是渲染的内容有锯齿、不平滑。这节介绍 OpenGL 的反混叠操作,代码不多,自己实验下来也只是前后对比,了解感受一下反混叠的效果。
过滤法反混叠
过滤法反混叠是去除信号中的高频内容。它在颜色混合的基础上开启平滑,比如以下代码片段可以开启线平滑:
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glEnable(GL_LINE_SMOOTH);
使用线平滑前后的效果如图 3 所示,因为图片宽高较小,且图片压缩的原因,这边将细节放大。图 3 上方是没有开启线平滑的效果,下方是开启了线平滑的效果,可以看出反混叠效果显著。
如图 4 上方所示,当以纯白渲染立方体时,外部边缘的锯齿严重。这时候可以将线平滑换成多边形平滑:
- glEnable(GL_POLYGON_SMOOTH);
如图 4 下方所示,多边形平滑还存在一个问题,虽然外部边缘的锯齿消失了,但是内部边缘却变得更加明显了。
多样本反混叠
多样本反混叠采取的手段是提高采样率。可使用目前框架代码开启多采用,如下是选择了八样本反混叠:
- virtual void init()
- {
- sb7::application::init();
- info.samples = 8;
- }
多采样默认是开启的,可以使用 glDisable 关闭,glEnable 启用:
- glEnable(GL_MULTISAMPLE);
图 5 是开启八样本反混叠的结果,可以看出不仅外部边缘锯齿消失,内部边缘的分界也变成不可见的了。
采样率着色
之前我们了解到着色的时候是会进行插值计算的,所以也会存在欠采样的情况。如代码清单 2 所示,可以在片段着色器中生成高频输出。
图 6 上方是没有开启采样率着色的效果,可以看出着色有明显的锯齿。我们可以按如下开启采样率着色:
- glEnable(GL_SAMPLE_SHADING);
并且使用 glMinSampleShading() 函数指定多少部分采用样本率着色。其函数原型为:
- void glMinSampleShading(GLfloat value);
参数 value 指定至少 value 比率的样本运行着色器。比如 value 为 0.5 时,表示至少一半的样本运行着色器;value 为 1.0 时,表示各个样本都进行单独着色。
总结
这两节内容,书上介绍的内容很多,但是对应的代码样例比较少,这边也是基于代码样例进行“筛选”学习。遗留的一些知识点感觉专业性很强,今后有空再回过头来学习。