Learn OpenGL 笔记5.11 Anti Aliasing(抗锯齿)

Learn OpenGL 笔记5.11 Anti Aliasing(抗锯齿)_第1张图片

这种清晰地看到边缘组成的像素结构的效果称为锯齿。 有很多称为抗锯齿技术的技术可以通过产生更平滑的边缘来对抗这种锯齿行为。(小时候打开一个新游戏,第一件事情就是把抗锯齿给关了,开抗锯齿太卡了)

起初,我们有一种称为super sample anti-aliasing超级采样抗锯齿 (SSAA) 的技术,它临时使用更高分辨率的渲染缓冲区来渲染场景(超级采样)。 然后在渲染完整场景时,将分辨率下采样回正常分辨率。 这种额外的分辨率用于防止这些锯齿状边缘。 虽然它确实为我们提供了锯齿问题的解决方案,但它带来了一个主要的性能缺陷,因为我们必须比平时绘制更多的片段。 因此,这项技术只有短暂的辉煌时刻。

这种技术确实催生了一种更现代的技术,称为multisample anti-aliasing多采样抗锯齿MSAA,它借鉴了 SSAA 背后的概念,同时实现了一种更有效的方法。 在本章中,我们将广泛讨论 OpenGL 中内置的这种 MSAA 技术。(早期显卡驱动设置里面经常能碰到这两设置,各种采样抗锯齿)

基础知识:

1.Multisampling 多重采样

片段的最终坐标受屏幕分辨率的约束。

Learn OpenGL 笔记5.11 Anti Aliasing(抗锯齿)_第2张图片

 其中每个像素的中心包含一个sample point(用来确定一个像素是否被三角形覆盖,覆盖了的地方才能转换为fragment)。三角形边缘的某些部分进入某些屏幕像素,但像素的采样点并未被三角形内部覆盖,因此该像素不会受到任何片段着色器的影响。

Learn OpenGL 笔记5.11 Anti Aliasing(抗锯齿)_第3张图片

多重采样的作用不是使用单个采样点来确定三角形的覆盖范围,而是使用多个sample point采样点。 我们将在一般模式中放置 4 个subsamples子样本,并使用它们来确定像素覆盖范围,而不是在每个像素的中心放置单个样本点。

Learn OpenGL 笔记5.11 Anti Aliasing(抗锯齿)_第4张图片

图像的左侧显示了我们通常如何确定三角形的覆盖范围。 这个特定的像素不会运行片段着色器(因此保持空白),因为它的采样点没有被三角形覆盖。

图像的右侧显示了一个多重采样版本,其中每个像素包含 4 个sample point采样点。 在这里我们可以看到只有 2 个样本点覆盖了三角形。(所以得到四个sample point的混合颜色?)

无论三角形覆盖多少子样本,片段着色器每个像素(对于每个图元)仅运行一次; 片段着色器使用插入到像素中心的顶点数据运行。 然后 MSAA 使用更大的深度/模板缓冲区来确定子样本覆盖率。 覆盖的子样本数量决定了像素颜色对帧缓冲区的贡献程度。 由于前一张图像中仅覆盖了 4 个样本中的 2 个,因此三角形颜色的一半与帧缓冲区颜色(在本例中为透明色)混合,从而产生淡蓝色。(所以准确来说是两个sample point的混合颜色,在三角形范围内的2个

Learn OpenGL 笔记5.11 Anti Aliasing(抗锯齿)_第5张图片

对于depth test深度测试,在运行深度测试之前将顶点的深度值插入到每个子样本中。

对于stencil模板测试,我们存储每个子样本的模板值。 这确实意味着缓冲区的大小现在增加了每个像素的子样本数量。

2.MSAA in OpenGL (multisample anti-aliasing多采样抗锯齿在Opengle中的运用)

想在 OpenGL 中使用 MSAA,我们需要使用一个能够为每个像素存储多个样本值的缓冲区。 我们需要一种可以存储给定数量的多样本的新型缓冲区,这称为multisample buffer多样本缓冲区

告诉GLFW 我们想要使用具有 N 个样本的多样本缓冲区而不是普通缓冲区: 

//开启4个子采样
glfwWindowHint(GLFW_SAMPLES, 4);

我们需要通过调用带有 GL_MULTISAMPLE 的 glEnable 来启用多重采样。 在大多数 OpenGL 驱动程序中,默认情况下启用多重采样,因此此调用有点多余,但无论如何启用它通常是个好主意。 这样,所有 OpenGL 实现都启用了多重采样。

//开启多重采样
glEnable(GL_MULTISAMPLE); 

因为实际的多重采样算法是在 OpenGL 驱动程序的光栅化器中实现的,所以我们不需要做太多其他的事情。

Learn OpenGL 笔记5.11 Anti Aliasing(抗锯齿)_第6张图片

 3.Off-screen MSAA (离屏幕multisample anti-aliasing多采样抗锯齿)

然而,想使用我们自己的帧缓冲区中的图像进行MSAA,我们必须自己生成multisampled buffers多采样缓冲区

3.1 Multisampled texture attachments(多重采样纹理附件)

glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
//这一句通常情况下是glTexImage2D()
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 Multisampled renderbuffer objects(多重采样渲染缓冲区对象)

我们只需要在配置(当前绑定的)渲染缓冲区的内存存储时将 glRenderbufferStorage 更改为 glRenderbufferStorageMultisample 即可:

glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height);  

3.3 Render to multisampled framebuffer(渲染到多采样帧缓冲区)

渲染到多采样帧缓冲区很简单。 每当我们在 framebuffer 对象被绑定的情况下绘制任何东西时,光栅化器都会处理所有的多重采样操作。 但是,由于多采样缓冲区有点特殊,我们不能直接将缓冲区用于其他操作,例如在着色器中对其进行采样。 

解析multisampled framebuffer多采样帧缓冲区通常是通过 glBlitFramebuffer 完成的,glBlitFramebuffer 将一个区域从一个帧缓冲区复制到另一个帧缓冲区,同时还解析任何多采样缓冲区。

我们还可以通过将帧缓冲区分别绑定到 GL_READ_FRAMEBUFFER 和 GL_DRAW_FRAMEBUFFER 来单独绑定到这些目标。 glBlitFramebuffer 函数从这两个目标读取以确定哪个是源帧缓冲区,哪个是目标帧缓冲区。 然后我们可以通过像这样将图像块传输到default framebuffer默认帧缓冲区来将多multisampled framebuffer 采样帧缓冲区输出传输到实际屏幕:

glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampledFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); 

Learn OpenGL 笔记5.11 Anti Aliasing(抗锯齿)_第7张图片

如果我们想使用多采样帧缓冲区的纹理结果来做后处理之类的事情呢? 我们不能在片段着色器中直接使用多重采样纹理。 然而,我们可以做的是将多重采样缓冲区 blit 到具有非多重采样纹理附件的不同 FBO(frame buffer object 可以存图像的buffer)。 然后我们使用这个普通的颜色附件纹理进行后期处理,有效地对通过多重采样渲染的图像进行后期处理。

//新建一个多重采样的frame buffer obj
unsigned int msFBO = CreateFBOWithMultiSampledAttachments();
// then create another FBO with a normal texture color attachment
[...]
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0);
[...]
while(!glfwWindowShouldClose(window))
{
    [...]
    
    //开启多重采样的frame buffer obj 并绘制图像
    glBindFramebuffer(msFBO);
    ClearFrameBuffer();
    DrawScene();
    // now resolve multisampled buffer(s) into intermediate FBO
    // 把多重采样的buffer内容传入中间frame buffer object
    glBindFramebuffer(GL_READ_FRAMEBUFFER, msFBO);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO);
    //blit复制的意思
    glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    // now scene is stored as 2D texture image, so use that image for post-processing
    // 场景内容存在2d image中了,可以对其进行后期处理
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    ClearFramebuffer();
    glBindTexture(GL_TEXTURE_2D, screenTexture);
    DrawPostProcessingQuad();  
  
    [...] 
}

你可能感兴趣的:(图形学,opengles)