GLSL中的HDR、自适应色调映射和MSAA

6
为了学习OpenGL,我正在阅读《Superbible》第五版(原书链接)
目前,我正在尝试弄清楚如何结合HDR和MSAA(正如第9章所述)。
对于HDR,该书建议采用一种基于计算每个片段的5x5卷积滤波器的平均亮度的自适应色调映射方法。
对于MSAA,使用的方法是通过从样本距离计算出的权重来平均所有样本。
在下面的pastebin中,我的尝试将色调映射应用于每个样本,然后将它们平均以计算最终的片段颜色。
性能很差(也许可以预料?):每个样本25次查找,乘以4的4xMSAA,我猜GPU花费大部分时间查找我的FBO纹理。切换到代码路径由use_HDR统一控制时,性能从400+fps降至简单场景下的不到10fps。
我的问题有两个:
  • 这是一种合理的色调映射方法吗?如果不是,你有什么建议?

  • MSAA和基于卷积的滤波器应该如何组合?我猜我会在任何需要查找相邻纹理像素的滤镜中再次遇到这个问题,比如bloom、blur等。

代码:

#version 330
in Data
{
    vec4 position;
    vec4 normal;
    vec4 color;
    vec2 texCoord;
    mat4 mvp;
    mat4 mv;
} gdata;

out vec4 outputColor;
uniform sampler2DMS tex;
uniform sampler1D lum_to_exposure;
uniform samplerBuffer weights;
uniform int samplecount;
uniform bool use_HDR;

vec4 tone_map(vec4 color, float exp)
{
    return 1.0f - exp2(-color * exp);
}

const ivec2 tc_offset[25] = ivec2[](ivec2(-2, -2), ivec2(-1, -2), ivec2(0, -2), ivec2(1, -2), ivec2(2, -2),
                                    ivec2(-2, -1), ivec2(-1, -1), ivec2(0, -1), ivec2(1, -1), ivec2(2, -1),
                                    ivec2(-2,  0), ivec2(-1,  0), ivec2(0,  0), ivec2(1,  0), ivec2(2,  0),
                                    ivec2(-2,  1), ivec2(-1,  1), ivec2(0,  1), ivec2(1,  1), ivec2(2,  1),
                                    ivec2(-2,  2), ivec2(-1,  2), ivec2(0,  2), ivec2(1,  2), ivec2(2,  2));

void main()
{
    ivec2 itexcoords = ivec2(floor(textureSize(tex) * gdata.texCoord));
    float tex_size_x = textureSize(tex).x;
    float tex_size_y = textureSize(tex).y;
    outputColor = vec4(0.0f, 0.0f, 0.0f, 1.0f);
    // for each sample in the multi sample buffer...
    for (int i = 0; i < samplecount; i++)
    {
        // ... calculate exposure based on the corresponding sample of nearby texels
        vec4 sample;
        if (use_HDR)
        {
            sample = texelFetch(tex, itexcoords, i);

            // look up a 5x5 area around the current texel
            vec4 hdr_samples[25];
            for (int j = 0; j < 25; ++j)
            {
                ivec2 coords = clamp(itexcoords + tc_offset[j], ivec2(0, 0), ivec2(tex_size_x, tex_size_y));
                hdr_samples[j] = texelFetch(tex, coords, i);
            }
            // average the surrounding texels
            vec4 area_color = (
                     ( 1.0f * (hdr_samples[0] + hdr_samples[4] + hdr_samples[20] + hdr_samples[24])) +
                     ( 4.0f * (hdr_samples[1] + hdr_samples[3] + hdr_samples[5] + hdr_samples[9]
                             + hdr_samples[15] + hdr_samples[19] + hdr_samples[21] + hdr_samples[23])) +
                     ( 7.0f * (hdr_samples[2] + hdr_samples[10] + hdr_samples[14] + hdr_samples[22])) +
                     (16.0f * (hdr_samples[6] + hdr_samples[8] + hdr_samples[16] + hdr_samples[18])) +
                     (26.0f * (hdr_samples[7] + hdr_samples[11] + hdr_samples[13] + hdr_samples[17])) +
                     (41.0f * (hdr_samples[12]))
                     ) / 273.0f;
            // RGB to luminance formula : lum = 0.3R + 0.59G + 0.11B
            float area_luminance = dot(area_color.rgb, vec3(0.3, 0.59, 0.11));
            float exposure = texture(lum_to_exposure, area_luminance/2.0).r;
            exposure = clamp(exposure, 0.02f, 20.0f);


            sample = tone_map(sample, exposure);
        }
        else
            sample = texelFetch(tex, itexcoords, i);

        // weight the sample based on its position
        float weight = texelFetch(weights, i).r;
        outputColor += sample * weight;
    }
}
2个回答

5
我没有《超级圣经》的副本,所以不知道他们的确切建议,但这种方法似乎非常低效和不精确:你的5x5过滤器只访问每个纹素的第'i'个样本,并完全忽略其他样本。
对于过滤阶段,我会像kvark建议的那样,使用glBlitFramebuffer在另一个纹理中解析,以便所有样本都在HDR中累积。之后,在另一个HDR纹理中进行过滤,可能使用可分离滤波器来提高性能,甚至使用GPU硬件来帮助进一步提高性能,使用bilinear filtering
这将给您一个模糊的纹理,然后您可以在色调映射着色器中对其进行采样。这应该大大提高性能,但使用更多内存。
请注意,还存在其他色调映射运算符,在此领域没有“绝对真理”。您可以选择使用更有效的方法,而不是使用如此精细的亮度估计。
你可以查看Matt Pettineo最近的博客文章,了解色调映射的相关内容,这可能会给你一些提示,也许可以通过使用glGenerateMipMaps来创建亮度纹理来改善事情。
关于使用MSAA进行色调映射的具体问题,我唯一知道的是建议在MSAA解析之前对单个样本进行色调映射,以防止出现走样伪影。before the MSAA resolve

谢谢提供这些链接,我还在消化所有的信息,但它们确实有助于澄清理论! - Nicolas Lefebvre
有一点仍然让我困惑:我也读到过,MSAA解决应该在色调映射之后进行(因此在我的天真尝试中,对每个样本进行色调映射,然后平均色调映射的样本)。您使用中间模糊纹理的解决方案是否与此相矛盾?还是MSAA解决了两次?(一次是通过复制创建一个模糊纹理,该纹理在色调映射后被丢弃,一次是手动对色调映射的结果进行解决) - Nicolas Lefebvre
1
@Bethor - 确实,中间的HDR模糊纹理是通过HDR MSAA缓冲区解析出来的。但由于它没有进行色调映射,因此使用简单的blit解析就足够了。它的唯一作用是提供区域内HDR像素亮度的估计值以进行色调映射。之后它会被丢弃,正如你所提到的。另一个更棘手的选择是使用上一帧完成的色调映射解析来获取当前帧的估计值。我相信Valve在《半条命2》中就是这样做的。 - rotoglup

2
据我从您的GLSL代码中看到的,像素的所有样本权重都是相等的。从这里我可以得出结论,该代码对每个像素的这些样本的总和感兴趣。总和是平均值乘以样本数。从这里至少有两种优化技术。两者都使用一个中间的单样本纹理,您的代码应该从中进行采样,而不是原始的多样本纹理:
  1. (精确地做到您正在做的事情)。使用着色器生成一个中间纹理,写入每个像素样本的平均值。
  2. (快速逼近)。让中间纹理成为已解决的原始纹理。通过调用glBlitFramebuffer()可以有效地完成此操作。这将产生略微不同的结果(因为样本位置不在网格上),但对于您的任务 - HDR - 这并不重要,因为它几乎都是一个近似值 :)
祝你好运!

谢谢您的建议,我会尝试使用glBlitFramebuffer来加速MSAA解析! - Nicolas Lefebvre

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