OpenGL学习25——抗锯齿化

抗锯齿化(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后处理效果
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容