OpenGL - 如何绘制到多重采样帧缓冲区并将结果用作普通纹理?

8
我正在开发一个小型的游戏开发库。该库的一个元素是Canvas(离屏绘图区),它是通过OpenGL帧缓冲实现的。到目前为止,一切都很好,我生成一个纹理,将其附加到帧缓冲中,进行渲染,然后使用帧缓冲的纹理作为Texture2D。
现在,我想在我的库中添加抗锯齿功能,因此我想能够在Canvas上设置多重采样。但我现在感到困惑,因为我发现需要修改着色器才能使用多重采样纹理等。
那么,我应该怎么做才能在我的帧缓冲中启用多重采样,以便最小化更改库中其余代码?如果可能的话,我想只使用渲染结果作为常规Texture2D。
1个回答

12

为了避免混淆,你不能仅仅创建一个x倍大小的纹理,然后希望滤波器能够自动处理。因为GL_LINEAR等只会对最靠近被纹理化像素中心的四个纹素进行平均

要创建一个多重采样纹理,你需要使用glTexImage2DMultisample()(自3.2版本起已经成为核心功能)。你可以按照以下方式设置它。

glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGBA8, width, height, false);

这段文字的意思是,samples代表多重采样纹理中的采样数量,internalformat可以根据需要进行更改。

要将纹理附加到帧缓冲区,同样使用glFramebufferTexture2D()。但是,将textarget设置为GL_TEXTURE_2D_MULTISAMPLE,而不是GL_TEXTURE_2D

glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);

记得检查你的帧缓冲状态。

在着色器中,您还需要使用sampler2DMS才能访问多重采样纹理。但请注意,与常规纹理相比,多重采样纹理的工作方式有很大不同。如果要从纹理中读取,则必须使用texelFetch()

因此,如果您想从多重采样纹理中进行采样,则不能使用texture(),而是必须利用texelFetch(),例如:

uniform int texSamples;
uniform sampler2DMS tex;

vec4 textureMultisample(sampler2DMS sampler, ivec2 coord)
{
    vec4 color = vec4(0.0);

    for (int i = 0; i < texSamples; i++)
        color += texelFetch(sampler, coord, i);

    color /= float(texSamples);

    return color;
}

请注意,texelFetch() 不接受归一化坐标,您可以通过以下方式规避此问题:
vec2 uv = vec2(0.5, 0.5); // normalized coordinates
ivec2 texSize = textureSize(tex, 0);
ivec2 texCoord = ivec2(uv * texSize);
vec4 color = textureMultisample(tex, texCoord);

如果你想要显示清晰的抗锯齿结果,最终你需要将其复制到屏幕上。

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glDrawBuffer(GL_BACK);
glBlitFramebuffer(0, 0, src_width, src_height, 0, 0, dst_width, dst_height, GL_COLOR_BUFFER_BIT, GL_LINEAR);

如果需要多重采样深度缓冲区,请查看glRenderbufferStorageMultisample()
此外,请确保启用glEnable(GL_MULTISAMPLE)。然而,现在大多数驱动程序默认启用它。
最后,这里有一些与多重采样相关的Stack Overflow/Exchange问题,你可能会感兴趣。

感谢您的详细回答!所以,如果我理解正确,如果我想要绘制到多重采样纹理并将结果用作常规纹理,我首先必须将该多重采样纹理(帧缓冲区)复制到非多重采样纹理,是吗?还是有其他方法绕过这个问题? - faiface
如果您可以接受sampler2DMStexelFetch()/textureMultisample(),那么您可以在着色器中像使用普通纹理一样使用它。但是除此之外,如果您想将多重采样纹理转换为普通纹理,则需要在两个帧缓冲区之间进行传输。 - vallentin
你能解释一下为什么这里说gsampler2DMS支持texture()吗?是文档错误吗? - Bim
@Vallentin 我正在使用glBlitFramebuffer来解决MSAA纹理,在Windows上运行良好,但在Linux上该命令会出现臭名昭著的1280错误。结果发现,glBlitFramebuffer(https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBlitFramebuffer.xhtml)要求两个帧缓冲具有相同数量的采样,因此看起来这种方法在技术上违反了规范。我有什么遗漏吗? - Egor
“你不能只是创建一个x倍大的纹理,然后希望滤镜会自动处理” - 实际上,你可以这样做,但你需要使用mip mapping滤镜,并在渲染到纹理和采样之间调用glGenerateMipmap()。超级采样和典型的多重采样之间的实际区别在于后者由于每个片段而不是每个样本执行片段着色器而更快(这并不总是正确的,请参见glMinSampleShading())。 - Ruslan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接