美文网首页
OpenGL学习25——抗锯齿化

OpenGL学习25——抗锯齿化

作者: 蓬篙人 | 来源:发表于2021-07-31 16:46 被阅读0次

抗锯齿化(Anti Aliasing)

  • 锯齿形边缘(jagged edges) 出现的原因取决于光栅化时如何将顶点数据转换为实际片元。
  • 最早,有一种叫做超分辨率抗锯齿化(super sample anti-aliasing, SSAA) 的技术,它使用一个更高分辨率的渲染缓冲区来渲染场景,然后当整个场景渲染完成时再通过降采样将分辨率恢复正常。因为这项技术在性能上有重大缺点,因此也只应用一时。
  • 从SSAA技术发展了更现代的抗锯齿化技术:多重采样抗锯齿化(multisample anti-aliasing, MSAA),它借用了SSAA的一些概念但以更高效的方式实现。本章节重点介绍OpenGL内置的MSAA技术。

1. 多重采样

  • 想要了解多重采样和它如何解决锯齿化问题,我们需要更深入的了解OpenGL的光栅化程序(rasterizer)。光栅化程序接收单个基元的顶点数据将其转换为片元集合。顶点坐标理论上可以是任何坐标,但是由于显示屏幕分辨率的缘故,片元却不能。因此顶点和片元之间的坐标几乎不可能进行一对一转换,所以光栅化程序需要以某种方式将指定顶点坐标转换为最终的片元/屏幕坐标。见下图:(图片取自书中
    光栅化
  • 如上图所示,在一个像素网格中,每个像素的中心都有一个采样点(sample point),采样点决定了该像素是否包含在三角形中。如果采样点包含在三角形中(图中红色采样点)则为对应的像素生成片元。但是在三角形的边上,虽然三角形经过屏幕像素,可采样点并不包含在三角形中,因此片元着色器并不会影响到该屏幕像素。
  • 对于单个采样点,上图中的三角形最终渲染结果大致如下:(图片取自书中
    单采样点三角形渲染
  • 多重采样所做的,就是不使用单个采样点决定基元是否包含某个屏幕像素,而是多个采样点。例如我们以常规方式放置四个子采样(subsamples)来决定是否包含像素。从下图我们可以看出,左侧单个采样点像素未包含在三角形中,因此该像素不会运行片元着色器输出颜色;右侧四个采样点中有两个包含在三角形中,因此像素运行片元着色器产生颜色。(图片取自书中
    单采样点与多采样点
  • 注意:采样的数量可以是任何数字,更多的采样点能更精确地决定部分包含的像素的渲染结果。
  • 在MSAA中,对于每个像素片元着色器只运行一次,不管该像素中多少个子采样点被基元包含。片元着色器使用插值到像素中心的顶点数据运行,然后MSAA使用一个更大的深度/模板缓冲区来决定是否包含子采样点,最后被包含的子采样点的数量决定了将多少三角形的颜色写入到帧缓冲区。例如上面的图中,四个子采样点中有两个被三角形包含,那么像素最终的颜色就是一半三角形的颜色与一半帧缓冲区的颜色的混合。
  • 最终的结果就是使用更高分辨率的颜色缓冲区(和更高分辨率的深度/模板缓冲区)来产生光滑的边缘。(图片取自书中
    多采样点
  • 最后三角形边缘的渲染结果大致如下,注意边缘像素的颜色:(图片取自书中
    多采样点渲染
  • 注意:虽然片元着色器每个像素只运行一次,但是颜色值、深度值和模板值则是按子采样点数量存储。

2. OpenGL中的MSAA

  • 如果我们想要使用OpenGL的MSAA,我们需要一个能存储每个像素不止一个采样值的缓冲区——一种能存储指定数量采样的缓冲区类型,多重采样缓冲区(multisample buffer)
  • 大部分窗体系统都为我们提供了一个多重采样缓冲区。在GLFW中,我们只需在创建窗体之前调用glfwWindowHint函数指示GLFW我们需要N采样的多重采样缓冲区来代替常规缓冲区即可。
glfwWindowHint(GLFW_SAMPLES, 4);
  • 在OpenGL中我们则只需调用glEnable函数启动多重采用即可(不过OpenGL一般默认启动多重采样)。
glEnable(GL_MULTISAMPLE);
  • 下面是单采样和多采样的绿色立方体渲染效果。


    单采样点立方体
    多采样点立方体

3. 离屏MSAA

  • 因为GLFW内置了多重采样缓冲区的创建,因此启动多重采样很容易。但是如果我们想使用自己的帧缓冲区,我们必须自己生成多重采样缓冲区。有两种方式可以生成多重采样缓冲区来作为帧缓冲区的附件:纹理附件和渲染缓冲区附件。与我们在帧缓冲区章节讨论的常规附件相似。

3.1 多重采样纹理附件

  • 要创建支持存储多个采样点的纹理我们使用glTexImage2DMultisample而不是glTexImage2D
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
// samples: 采样点数
// 最后一个参数GL_TRUE表示:对纹理像素采用相同的采用位置和相同的子采样数量
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGB, width, height, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
  • 要将多重采样纹理附加到帧缓冲区,我们使用glFramebufferTexture2D函数。
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);

3.2 多重采样渲染缓冲区对象

  • 与纹理相似,创建多重采样渲染缓冲区对象并不困难,我只需将glRenderbufferStorage函数更换为glRenderbufferStorageMultisample,并配置(当前绑定)渲染缓冲区的内存。
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height);

3.3 渲染到多重采样帧缓冲区

  • 因为多重采样帧缓冲区比较特别,对于一些操作如着色器中的采样,我们不能直接使用。解析一个多重采样的帧缓冲区一般使用glBlitFramebuffer函数,该函数从一个帧缓冲区中的一个区域将数据拷贝到另外一个缓冲区中,同时对多重采样缓冲区进行了解析。从帧缓冲区章节我们知道有GL_READ_FRAMEBUFFERGL_DRAW_FRAMEBUFFER两个缓冲区目标。glBlitFramebuffer函数能够从这两个目标读取数据并自动确定哪一个是源哪一个是目标帧缓冲区。因此我们可以通过将图像块传输到默认帧缓冲区来将多重采样帧缓冲区数据输出到屏幕。
glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampleFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
  • 综上所述,离屏MSAA的渲染循环中的大致过程如下:
// framebuffer:多重采样的帧缓冲区
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
ClearScene();
DrawScene();
// 将多重采样帧缓冲区输出到默认帧缓冲区(屏幕显示的缓冲区)
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
  • 渲染效果如下,与直接在窗体系统设置多重采样相似:


    离屏MSAA渲染
  • 如果我们想对多重采样帧缓冲区进行后处理,因为我们不能直接使用当前片元着色器中的多重采样纹理,因此我们需要再创建一个非多重采样的中间帧缓冲区。我们先将多重采样帧缓冲区的数据拷贝到中间帧缓冲区,然后对中间帧缓冲区的常规2D纹理进行后处理,再将其显示到屏幕上。大致过程如下:
CreateSceneAndSetData();
CreateMultisampleFramebuffer();
CreateScreenAndSetData();
CreateIntermediaFramebuffer();

SetScreenTexture();

while (!glfwWindowShouldClose(window))
{
    BindMultisampleFramebuffer();
    DrawScene();

    glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
    // 目标设置为中间帧缓冲区
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO);
    glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    DrawScreenTexture();

    glfwSwapBuffers(window);
    glfwPollEvents();
}
  • 一个灰度化的立方体效果。


    离屏MSAA后处理效果

相关文章

  • OpenGL学习25——抗锯齿化

    抗锯齿化(Anti Aliasing) 锯齿形边缘(jagged edges) 出现的原因取决于光栅化时如何将顶点...

  • OpenGL ES 3.0 快速近似抗锯齿(Fast Appro

    OpenGL ES 3.0抗锯齿系列文档: 多重采样抗锯齿Multiple Sample Anti-aliasin...

  • OpenGL学习笔记四

    抗锯齿 抗锯齿混合的2大功能:颜色组合、抗锯齿 多重采样 多重采样,抗锯齿混合综合使用 OpenGL 数学库; M...

  • 从0开始的OpenGL学习(二十七)-抗锯齿

    本文主要解决两个问题: 1、什么是抗锯齿?2、如何在OpenGL中使用抗锯齿? 引言 抗锯齿,英文名是anti-a...

  • Android drawLine 1px

    setAntiAlias(true)会导致绘制不出1px的线,取消抗锯齿。 OpenGL核心技术之抗锯齿[http...

  • Android OpenGL ES抗锯齿

    多重采样MSAA GLSurfaceView设置多重采样 开启MULTISAMPLE 后处理FXAA Shader...

  • OpenGL学习之路(4.0) 实现抗锯齿效果

    原因 当我们放大图片的时候会发现图片上的像素点有很多锯齿形状,这样就会导致图片呈现的效果不佳,所以需要通过抗锯齿处...

  • 23.opengl高级-抗锯齿

    这两天有点疲惫,这一章节的代码没有run起来看效果,重点理解锯齿现象和抗锯齿的实现 一、锯齿生成原理 参考上图,几...

  • QT+OPenGL十八抗锯齿

    OpenglWegdit中的构造函数添加 能够由一个片段采样增加到四个从而使据此更平滑。即抗锯齿。 目录 VSC+...

  • OpenGL渲染之裁剪-混合-抗锯齿

    裁剪 只刷新屏幕上发生变化的部分可以提高渲染性能 OpenGL中是允许将要进行渲染的窗口指定一个裁剪框 裁剪框与窗...

网友评论

      本文标题:OpenGL学习25——抗锯齿化

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