【问题标题】:HDR, adaptive tone mapping and MSAA in GLSLGLSL 中的 HDR、自适应色调映射和 MSAA
【发布时间】:2018-08-01 13:25:28
【问题描述】:

为了自学 OpenGL,我正在通过5th edition of the Superbible 努力。

我目前正试图弄清楚如何结合 HDR 和 MSAA(如第 9 章所述)。

对于 HDR,本书建议了一种自适应色调映射方法,该方法基于计算每个片段的 5x5 卷积滤波器的平均亮度。

对于 MSAA,使用的方法是通过根据样本距离计算的权重对所有样本进行平均。

我尝试将两者结合起来,在下面的 pastebin 中找到,将色调映射应用于每个样本,然后对它们进行平均以计算最终的片段颜色。

性能(正如人们应该预料到的那样?)很糟糕:每个样本查找 25 次,对于 4xMSAA 乘以 4,我猜 GPU 大部分时间都在查找我的 FBO 纹理。切换到代码中由 use_HDR uniform 控制的代码路径,对于一个简单的场景,性能从 400+fps 下降到 10 以下。

我的问题有两个:

  • 这是一种执行色调映射的合理方法吗?如果没有,你有什么建议?

  • 应该如何组合 MSAA 和基于卷积的滤波器?我猜对于任何需要查找相邻纹素的过滤器,我会再次遇到这个问题,即几乎任何东西,如绽放、模糊等?

代码:

#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;
    }
}

【问题讨论】:

    标签: opengl glsl


    【解决方案1】:

    我没有 Superbible 的副本,所以我不知道他们的确切提议,但这种方法似乎非常低效且不精确:您的 5x5 过滤器仅访问 'i'th em> 每个纹素的样本,完全错过了其他样本。

    对于过滤阶段,正如 kvark 已经建议的那样,我将使用 glBlitFramebuffer 在另一个纹理中进行解析,以将所有样本累积到 HDR 中。之后,在另一个 HDR 纹理中进行过滤,可能使用 separable filter 来获得性能,或者甚至使用 GPU 硬件来帮助进一步提高性能,使用 bilinear filtering

    这将为您提供模糊的纹理,然后您可以在色调映射着色器中对其进行采样。这应该会大大提高性能,但会占用更多内存。

    请注意,存在其他色调映射运算符,并且该域中没有“基本事实”。您可以通过不使用如此精细的亮度估计来选择使用更高效的方法。

    您可以查看Matt Pettineo's recent blog post 关于色调映射的信息,这可以为您提供有关如何改进的提示,也许可以使用glGenerateMipMaps 创建亮度纹理。

    关于使用 MSAA 进行色调映射的具体问题,我唯一知道的是建议对单个样本进行色调映射 before the MSAA resolve,以防止出现锯齿伪影。

    【讨论】:

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

    据我从您的 GLSL 代码中看到的,一个像素的所有样本的权重都是相等的。由此我得出结论,代码对每个像素的这些样本的总和感兴趣。总和是平均值乘以样本数。从这里至少揭示了两种优化技术。两者都使用中间单采样纹理,您的代码应该从中采样而不是原始的多采样纹理:

    1. (精确到你正在做的事情)。使用为每个像素写入样本平均值的着色器生成中间纹理。

    2. (快速逼近)。让中间纹理成为已解析的原始纹理。可以通过调用glBlitFramebuffer() 有效地完成。这将产生略有不同的结果(因为样本位置不在网格上),但对于您的任务 - HDR - 这无关紧要,因为这几乎是一个近似值:)

    祝你好运!

    【讨论】:

    • 感谢您的建议,我会试试 glBlitFramebuffer 以加快 MSAA 解析速度!
    猜你喜欢
    • 1970-01-01
    • 2012-02-29
    • 1970-01-01
    • 2017-04-18
    • 1970-01-01
    • 1970-01-01
    • 2012-11-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多