转载请标明出处:http://blog.csdn.net/yunchao_he/article/details/78354528

Multi-sampling或者说Multi-sample Anti-Alias (简称MSAA)是一种抗锯齿的技术,它通过在一个像素上进行多次采样多次计算并最终汇总(Resolve to single-sample),可使绘制的图像边缘更加平滑。通过这种方式绘制出来的图片质量更高,显得更真实。但同时,它对绘制的性能也会产生负面影响。所以,是否使用这项技术,需要开发者在图片质量(Quality)和性能(Performance)之间进行权衡。那么,MSAA到底对整个绘制流水线(Rendering Pipeline)产生了什么影响?本文将进行深入分析,从而帮助自己及读者在相关问题上有更深的理解,从而做出正确合理的决策:比如在开发过程中是否使用MSAA。

MSAA简介

首先简单讲一下什么是MSAA。MSAA就是把每个pixel(或者说fragment)细分为多个sub-pixel,比如分为4个、8个、16个甚至32个sub-pixel,分别对应MSAA4, MSAA8, MSAA16, MSAA32。我们知道,图形学里,每个piexl占据屏幕上的一小块矩形网格。比如对于1920*1280的显示器,就有1920*1280个小的矩形网格,每个网格都是一个pixel。而MSAA则把每个小的矩形网格再进行细分。比如MSAA4/MSAA8分别把每个piexl再分为4个或者8个sub-pixel,其中每一个sub-pixel称为一个sample。而正常pipeline里的所有per-pixel(per-fragment)的操作,打开MSAA后,理论上都可以per-sample来处理。这样,每个pixel里的多个sample, 都可以独立进行插值、独立执行fragment shader,计算出独立的颜色值、深度值。然后求出同一个pixel的所有sample的算术平均值(也就是resolve to single sample),就得出这个pixel的最终颜色。通过这种方式,图形边缘的绘制会更精细更平滑。当然,对于1920*1280的网格,MSAA4相当于在处理1920*1280*4个网格,计算量(以及显存里某些变量的存储空间)也是成倍增加。

MSAA分析

MSAA在Rendering Pipeline的过程中,可能的影响有以下几方面。

1)光栅化阶段(Rasterization)

光栅化阶段一个重要的工作就是插值计算(Interpolation),所以多重采样作用到这个阶段主要是多重插值。

这个阶段的multisampling可以分为几种,一种是重量级的多重插值,一种是定制化的多重插值,还有一种是轻量级的多重插值。当然,这只是我个人的简单分类,具体区别详见下文。

先讲重量级、重负载的多重插值。我们知道,在Rasterization阶段,需要对fragment shader阶段的所有inputs进行插值计算。可能的插值计算变量包括但不限于颜色,法线,纹理坐标等等。比如上图手绘的图片里,对三角形的绘制,开发者通常只设置顶点信息。这里以颜色为例,三个顶点A/B/C的颜色分别为蓝色、黑色、红色。三角形覆盖的区域(网格区),都是GPU在Rasterization阶段根据各个像素所在位置,进行插值计算,得出各个pixel/fragment的颜色值。这个颜色值显然是三个顶点颜色值的混合。理论上,凡是需要per-pixel插值的变量,也可以进行per-sample插值,也就是多重插值。注意,这里的“多重”,其实是站在每个pixel(或者说fragment)的角度。因为同一个pixel有多个sample,每个sample都是对这个pixel进行了细分,是它的sub-pixel(可理解为多个"子网格")。根据具体硬件的布局实现,同一个pixel内的多个sample之间,位置有差异,从而可计算出per-sample的更精细的值。这样就相当于对这个pixel进行了多次插值计算。实际上对于每个sample, 当然还是只进行一次插值计算。

如果对所有需要插值的变量,比如fragment shader的所有inputs,都进行这种多重插值,这就是重量级的多重插值。

比如对于颜色,如果是绘制一个很大的矩形,比如1920*1280的矩形, 对颜色变量进行插值计算时则需要1920*1280次计算,也需要在显存(video memory)里占用这么多的存储空间。这是光栅化阶段不可避免的计算消耗和存储消耗。如果打开4倍的MSAA(MSAA4),则需要1980*1280*4次计算,同时也需要相应规模的显存空间。所以计算消耗和存储消耗一下子扩大了4倍。当然,对于其它需要进行插值的变量也是如此。所以,如果对所有需要插值的变量都做多重插值,显然消耗很大。但GL确实可以这么做,调用

glMinSampleShading(1.0);

就可以对所有变量都进行多重插值。注意,进行多重插值,不仅计算量显著增加,显存消耗量也会显著增加。

第二种是定制化的多重插值。可以在vertex shader的某个或者某些outputs以及fragment shader里相应的inputs变量前加'sample'关键字。这样,插值计算时只会对你指定的变量进行多重插值。比如以下的一段简单的fragment shader代码,对fragment shader里的部分input变量添加了'sample'关键字(这里是color),指定对它(们)进行多重插值,而其它变量则没有多重插值:

#version 450 coresample in vec4 color;
in vec4 normal;out vec4 fColor;void main()
{// do something
}

这两种多重插值,实际上都需要和per-sample shading相结合,才有意义。也即是说,需要fragment shader的执行是per sample,而不是per pixel。关于这一点,详见后面的片段着色阶段如何enable多重采样。

最后一种,是轻量级的多重插值。它对需要插值的变量本身不进行多重插值。只针对color变量,附加coverage计算。而这个计算是per-sample的的。也就是说,对于MSAA4,它会对每个pixel申请4个bits的gl_coverage变量。记录rasterization过程中这个sample有没有被覆盖。如果某个sample被覆盖,则在gl_coverage里相应的bit位设置为1。如果某个sample没有被覆盖,则相应的bit位设置为0。这样,可以根据coverage来调整最终颜色。比如处于图像边缘的像素,如果4个sample里有1个sample被覆盖,而可以使用1/4来调和这个像素的颜色。从而达到MSAA的效果。但本身并不需要针对每个color进行4次插值计算,也不需要4倍的显存空间存储color的值。

当然,对于depth/stencil的值,又有一些区别。通常情况下,如果draw framebuffer里有depth/stencil 的多重采样缓冲区,则会对depth/stencil的值做多重插值,并且在per-fragment operation阶段,会进行per-sample的depth/stencil test.

如果申请的render target是支持多重采样的,则会自动enable轻量级的插值计算。这包括两种情况,第一种情况是绘制到离屏的fbo里,这时需要使用multisample renderbuffer或者multisample texture作为color buffer,必要的话,可以使用multisample renderbuffer或者multisample texture作为depth_stencil buffer,然后进行离屏渲染,绘制到这个fbo里。而且,绘制完成后,需要开发者主动blitting到single-sample的framebuffer去显示。Chromium里WebGL的实现,会默认打开anti-alias,用的正是这种方式。相应的示例代码如下:

// 创建multisample texture
glGenTextures( 1, &tex );
glBindTexture( GL_TEXTURE_2D_MULTISAMPLE, tex );
glTexStorage2DMultisample( GL_TEXTURE_2D_MULTISAMPLE, num_samples, GL_RGBA8, width, height, false );// 把multisample texture 作为fbo的color bufferglGenFramebuffers( 1, &fbo );glBindFramebuffer( GL_FRAMEBUFFER, fbo );glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0 );rendering();  // 离屏渲染,绘制到fbo里。开发者需要根据自己的业务逻辑,自行实现// blitting
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glDrawBuffer(GL_BACK);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);

另一种情况是直接绘制到窗口的默认缓存(default framebuffer), 这需要在窗口创建的时候声明为Multisample的窗口, glut可以帮助你完成这个操作,而不需要对各种窗口系统进行处理。其代码如下(这段代码将创建一个RGBA颜色格式的multisample default framebuffer, 而且是双缓冲):

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_MULTISAMPLE);

这种情况下,系统也会自动帮你resolve到single sample的buffer并显示,不需要开发者做更多操作。实际上,如果创建窗口默认缓存(default framebuffer)时就enable了MSAA,窗口系统会申请一个msaa的color buffer, 同时也会申请single-sample 的color buffer。但default framebuffer的depth/stencil buffer, 则只有一个msaa的depth/stencil buffer, 没有single-sample的depth/stencil buffer.

2)采样阶段

绑定多重采样的texture, 然后使用texelFetch之类的采样函数,则可从multisample texture里进行逐sample的纹理采样(per-sample texture fetching)。当然,per-sample texture fetching可以结合gl_sampleID来进行,也就是同一个pixel里的每个sample,有自己的sampleID,然后通过gl_sampleID为每个sample获取不同的值。以下是一个简单的fragment shader的例子,将插值得到的color和multisample里的多重采样值,进行融混。

#version 450uniform sample2DMS tex;
in ivec2 texCoord;
in color;out fColor;void main()
{fColor = 0.5 * color + 0.5 * texelFetch(tex, texCoord, gl_SampleID);
}

当然,per-sample texture fetching也是在per-sample shading的时候才有意义。

另外需要指出的是,开发者无法初始化一个multisample texture的数据。也即是不存在类似于TexImage2D这样的接口,去上传初始化multisample的原始图片。所以,multisample texture里进行采样,像素内容必定是用户自主render出来的。

3)片段着色阶段

片段着色阶段进行多重采样,就是指fragment shader的执行不是per pixel/fragment, 而是per sample。也是说,对于1920*1280的render target, 普通情况下,需要调用1920*1280次fragment shader, 如果使用了MSAA4,则需要调用1920*1280*4次fragment shader!

所以,如果fragment shader很复杂,4倍的计算量将会严重影响性能。

当然,per-sample shading并不会自动打开,需要开发者主动调用上文提到的glMinSampleShading(1.0)。当然,MinSampleShading里的参数可以选择其它数据,比如0.5。则对MSAA4,它会选择4个sample里的2个sample进行per-sample shading,计算量为2倍。另外,前面已经讲到,per-samper shading还会针对需要插值的变量,进行per-sample interpolation。这样,同一个pixel/fragment内的每个sample的插值的结果都会不一样。从而使计算结果更精细。当然代价也很大,既成倍增加插值计算量,也成倍增加显存的存储空间。同样地,也可以在fragment shader里进行per-sample texture fetching, 为同一个pixel/fragment里的不用sample获取不同的纹理采样值。

4)Blitting

blitting通常是把multisample framebuffer里的像素内容,resolve到single-sample framebuffer里。如果绘制的时候使用了multisample fbo进行离屏渲染,则需要开发者自行调用blitFramebuffer。上文也有使用blitFramebuffer从离屏的msaa fbo渲染到single-sample framebuffer的例子。

小结

最后,大家可以发现,MSAA对Rasterization之前的阶段都没有直接影响。比如CPU的操作(主要是clident driver的validation等)不会受到影响,比如也不增加从CPU上传到GPU的数据(比如顶点数据,纹理数据),也不会增加vertex shader、tessellation shader、geometry shader的执行次数。这些阶段,都不会受MSAA的直接影响。如果这些阶段是绘制程序的性能瓶颈,即使开启MSAA,性能的损失可能也不会明显。

而MSAA对性能的影响主要体现在所有per pixel/fragment的操作,比如rasterization阶段的插值计算以及存储开销, fragment shader的执行等。不同类型的MSAA操作,会对这些阶段带来显著影响。如果性能瓶颈在这些阶段,使用MSAA后,很可能导致性能显著下降。

一般来讲,使用轻量级的multisampling技术(也就是创建multisample的framebuffer),就可以达到较好的渲染质量,性能损失也不太大。但是,如果需要处理alpha-tested transparency问题,轻量级的multisampling技术则根本不起作用。当你使用一张较大的规则图片去表达不规则的贴图,多余部分则需要通过alpha值(比如alpha值为0)来剔除,这时就需要使用alpha-tested transparency技术。比如一棵树的图片,可能是规则长方形,但树木本身并不规整,原始图片里树木本身之外的像素,texture mapping时需要根据alpha值剔除,以免遮挡场景里的其它物体。这时候如果需要使用multisampling技术,则轻量级的MSAA会出问题。关于alpha-tested transparency问题,详见参考文献[7]。所以重量级的多重采样,也有其应用场景和价值。

参考文档:

[1] OpenGL, OpenGL ES specification, 主要是OpenGL 4.5/4.6, OpenGL ES 3.1/3.2 的specification.

[2] OpenGL Shading Language和OpenGL ES Shading Language, 主要是GLSL 450/460以及GLSL ES 310/320

[3] OpenGL Programming Guide 8th edition

[4] ARB_sample_shading:https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_sample_shading.txt (The Q&A part is also interesting)

[5] OES_sample_shading:https://www.khronos.org/registry/OpenGL/extensions/OES/OES_sample_shading.txt

[6] Multisampling:https://www.khronos.org/opengl/wiki/Multisampling

[7]Transparency and alpha-tested transparency:https://www.khronos.org/opengl/wiki/Transparency_Sorting
————————————————
 
原文链接:https://blog.csdn.net/yunchao_he/article/details/78354528

深入理解多重采样(Multisampling)相关推荐

  1. 反走样和OpenGL多重采样

    1. 反走样 在计算机图形学中,在屏幕上显示对象时,可能会出现许多的"锯齿",这些锯齿是由顶点数据像素化之后成为片段的方式所引起的,由于将数学意义上的坐标转换到物理的显示器硬件上进 ...

  2. Godot Engine:多重采样抗锯齿(MultiSampling Anti-Aliasing)设置

    Godot Engine 3.2.2 默认状态下Godot渲染的锯齿很严重 解决办法:开启MSAA MSAA是MultiSampling Anti-Aliasing的英文缩写,指多重采样抗锯齿,原理是 ...

  3. 对多重采样(MSAA)原理的一些疑问

    转:https://www.zhihu.com/question/58595055/answer/157756410 关于MSAA可参考官方规格说明文档 对于OpenGL是Khronos维护的:htt ...

  4. opengl 反走样 混合 多重采样 blend multisample

    1. 反走样         在光栅图形显示器上绘制非水平且非垂直的直线或多边形边界时,或多或少会呈现锯齿状或台阶状外观.这是因为直线.多边形.色彩边界等是连续的,而光栅则是由离散的点组成,在光栅显示 ...

  5. 多重采样和超级采样哪个流畅_蒙特卡洛方法-多重采样

    全球图形学领域教育的领先者.自研引擎的倡导者.底层技术研究领域的技术公开者,东汉书院在致力于使得更多人群具备内核级竞争力的道路上,将带给小伙伴们更多的公开技术教学和视频,感谢一路以来有你的支持.我们正 ...

  6. 多重采样和超级采样哪个流畅_OpenGL多重采样:结果与未使用多重采样时的结果相同...

    在QT框架中使用OpenGL(版本330)多重采样 . 渲染图像就像星形 . 我使用片段着色器在黑色画布上渲染形状强度 . 我不使用OpenGL原语 . 当不使用多重采样时,并且当渲染输出画布具有较小 ...

  7. SAP QM 采样方案的c1 d1 c2 d2 --多重采样

    SAP QM 采样方案的c1 d1 c2 d2 --多重采样 使用QDP1创建采样方案的时候为什么只要填写c1 d1不用填写c2 d2等等呢? 首先,C1/C2-C7代表接受数 D1/D2-D7代表拒 ...

  8. OpenGL MSAA多重采样抗锯齿的实例

    OpenGL MSAA多重采样抗锯齿 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> #include & ...

  9. Unity3D学习(七):Unity多重采样抗锯齿设置无效的解决办法

    前言 学习Shader的过程中发现模型锯齿严重,于是去Edit--Project Settings--Quality选项下将反锯齿设置为了8X Multi Sampling.结果没有任何改变,如图: ...

最新文章

  1. 揭秘vue——vue-cli3全面配置
  2. 嵌入式开发输出调试信息的几种方法(常规法及非常规法)
  3. 判断图有无环_链表:环找到了,那入口呢?
  4. 在Windows Server 2008上部署SVN代码管理总结
  5. 《第一行代码》学习笔记9-活动Activity(7)
  6. Go语言fmt.Printf使用指南(占位符总结)
  7. html之文档的头部和元数据定义(上)
  8. 家用linux远程管理,linux远程管理器
  9. cogs908. 校园网
  10. Java 代理使用详解
  11. html怎样修改背景图片大小,css中如何设置背景图片的大小?
  12. 调用第三方应用App
  13. ubuntu wifi变成问号 网速很慢
  14. HP G32笔记本 拆机图解
  15. dex是什么的缩写,游戏中str是什么的缩写《游戏人物的属性STR AGI VIT INT DEX CON WIS LUK各是什么意思啊?》...
  16. Tiny4412裸机程序之代码重定位初体验
  17. 手机联系人不见了怎么恢复,试下这2招
  18. opencv python 人脸识别 相似度_OpenCV+Tensorflow实现实时人脸识别演示
  19. Android基础入门教程——1.6 .9(九妹)图片怎么玩
  20. PHP商城二手交易市场课程设计

热门文章

  1. np读取csv文件_pandas.read_csv函数参数详解
  2. java 双声道音频_java实现切割wav音频文件的方法详解【附外部jar包下载】
  3. 华为云服务器密码修改,华为云鲲鹏云服务器安装MySQL 5.7.30
  4. sqli-labs less11 POST注入-字符型
  5. android:layout_width=0.0dip,【教程】状态栏显示网速
  6. java用if判断输入字符_java怎么用if判断输入的是不是数字
  7. linux100day(day3)--常用文本处理命令和vim文本编辑器
  8. vue-cli搭建的项目打包之后报“资源路径错误资源文件找不到“
  9. USACO1.1.2|贪婪的送礼者
  10. #Plugin 中国省市选择插件