转载请标明出处: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. Xcode文件名后的字母含义
  2. s()++php,jquery siblings()函数正确用法
  3. OVS之vhost-net中VM通信(十)
  4. 089-袁佳鹏-实验报告1
  5. csdn学院 python_确认!别再相信Python了! 程序员:就你敢说...
  6. Java开发笔记(五十六)利用枚举类型实现高级常量
  7. caffe上手:微调CaffeNet用于车颜色识别
  8. 深入解读Linux进程调度系列(6)——抢占与非抢占
  9. linux文件权限的设置命令
  10. MySQL安装包下载及配置方法
  11. 上传大文件至阿里云服务器解决方案(理论上无限大文件,支持批量处理)
  12. WINCE环境下 helloWorld
  13. 《2022谷歌开发者大会》参会之旅
  14. 修真院_JAVA_TASK_1
  15. 单点故障解决方案介绍smart link/monitor link /stp
  16. Java 埃拉托色尼筛选法
  17. Zabbix-2.4-安装-1
  18. Linux系统挂起之后退出的方法
  19. 九、python学习之HTTP协议
  20. es根据字段长度过滤_ES Aggs根据聚合的结果(数值)进行过滤

热门文章

  1. 从需求到设计,嵌入式产品开发流程
  2. mysql 客服_MySQL
  3. 传奇霸业微端登陆服务器无响应,传奇霸业微端和浏览器崩溃白屏修复教程
  4. java workerdone_【架构】Java并发编程——线程池的使用
  5. springmvc的执行流程_springmvc执行流程
  6. 华为笔记本搭载鸿蒙系统,华为MatePad Pro2入网!预装鸿蒙系统 搭载麒麟9000
  7. php模拟环境搭建,PHP环境搭建最新方法
  8. ajax datatype_JavaScript学习笔记(二十七) ajax及ajax封装
  9. 高级软件工程第九次作业:东理三剑客团队作业-随笔4
  10. 《javascript 高级程序设计》 笔记1 1~7章