c++ – Problem with calculating Automatic Exposure using luminance histogram

I’m trying to calculate the average luminance of my HDR scene using Luminance Histogram method so I can use it and perform tone mapping but for some reason I can’t get it right. I used this article as a reference Automatic Exposure Using a Luminance Histogram

This is the pass where I calculate the histogram then average it using compute shaders. After this pass I should have my histogram average. This is my first time setting and using compute shaders in OpenGl so I’m not sure if everything is set correctly, I found it really hard to find good resources on Compute Shaders in OpenGl

void SPBR::Calculate_Scene_Exposure()
{

    static bool flag = false;
    static unsigned int ssbo;
    const uint32_t GROUP_SIZE = 256;
    const uint32_t THREAD_X = 16;
    const uint32_t THREAD_Y = 16;

    if (!flag) 
    {
        glGenBuffers(1, &ssbo);
        glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
        glBufferData(GL_SHADER_STORAGE_BUFFER, GROUP_SIZE * sizeof(unsigned int), nullptr, GL_DYNAMIC_READ);

        flag = true; 
    }

    game->shaders_table["luminanceHisto"].Bind();

    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

    glBindImageTexture(1, scene_refered_render_target.color_buffers[0].id, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
    game->shaders_table["luminanceHisto"].Set_Int_Uniform("hdr_scene", 1);

    game->shaders_table["luminanceHisto"].Set_Vec3_Uniform("u_params", glm::vec3(-10.0f, (1.0f / 12.0f), 1.0f));

    glDispatchCompute((WINDOW_WIDTH / THREAD_X) + 1, (WINDOW_HEIGHT / THREAD_Y) + 1, 1);
    glMemoryBarrier(GL_ALL_BARRIER_BITS);

    game->shaders_table["luminanceHisto"].Un_Bind();

    static bool flag2 = false; 

    if (!flag2) 
    {
        glGenTextures(1, &avg_luminance_text);
        glBindTexture(GL_TEXTURE_1D, avg_luminance_text);
        glTexImage1D(GL_TEXTURE_1D, 0, GL_R32F, 1, 0, GL_RED, GL_FLOAT, 0);
    }

    game->shaders_table["HistoAvg"].Bind();

    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

    glBindImageTexture(1, avg_luminance_text, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32F);
    game->shaders_table["HistoAvg"].Set_Int_Uniform("target", 1); 

    game->shaders_table["HistoAvg"].Set_Vec4_Uniform("u_params", glm::vec4(-10.0f, 12.0f, game->deltatime, WINDOW_WIDTH * WINDOW_HEIGHT)); 
    glDispatchCompute(1, 1, 1);
    glMemoryBarrier(GL_ALL_BARRIER_BITS);

    game->shaders_table["HistoAvg"].Un_Bind(); 
}

Here is luminanceHisto Shader

#version 430 core

#define GROUP_SIZE 256
#define RGB_TO_LUM vec3(0.2125, 0.7154, 0.0721)
#define EPSILON 0.005
#define THREAD_X 16
#define THREAD_Y 16

layout (std430, binding = 0) buffer myBuffer
{
    uint data[];
} histogram;

layout (rgba32f, binding = 1) uniform image2D hdr_scene;

layout (local_size_x = THREAD_X, local_size_y = THREAD_Y, local_size_z = 1) in;

uniform vec3 u_params;

uint Color_To_Bin(vec3 color, float minLogLum, float inverseLogLumRange)
{
    float lum = dot(color, RGB_TO_LUM);

    if(lum < EPSILON)
    {
        return 0; 
    }

    float logLum = clamp((log2(lum) - minLogLum) * inverseLogLumRange, 0.0, 1.0);

    return uint(logLum * 254.0 + 1.0);
}

shared uint shared_histogram[GROUP_SIZE]; 

void main()
{
    shared_histogram[gl_LocalInvocationIndex] = 0; 
    barrier(); 
    
    uvec2 dim = imageSize(hdr_scene).xy; 
    
    if(gl_GlobalInvocationID.x < dim.x && gl_GlobalInvocationID.y < dim.y)
    {
        vec3 color = imageLoad(hdr_scene, ivec2(gl_GlobalInvocationID.xy)).rgb; 
        uint bin = Color_To_Bin(color, u_params.x, u_params.y);

        atomicAdd(shared_histogram[bin], 1);
    }

    barrier();
    atomicAdd(histogram.data[gl_LocalInvocationIndex], shared_histogram[gl_LocalInvocationIndex]);
}

and AvgHisto Shader The histogram buffer here is the buffer calculated in the previous compute shader.

#version 430 core

#define GROUP_SIZE 256
#define localIndex gl_LocalInvocationIndex

layout (std430, binding = 0) buffer myBuffer
{
    uint data[];
} histogram;

layout (r32f, binding = 1) uniform image1D target;

layout (local_size_x = GROUP_SIZE, local_size_y = 1, local_size_z = 1) in; 

uniform vec4 u_params;
#define minLogLum u_params.x
#define logLumRange u_params.y
#define deltatime u_params.z
#define numPixels u_params.w

shared uint shared_histogram[GROUP_SIZE];

void main()
{
    shared_histogram[localIndex] = histogram.data[localIndex] * localIndex; 
    barrier(); 
    
    uint black_pixels =  histogram.data[0];
    histogram.data[localIndex] = 0; 

    if(localIndex == 0)
    {
        for(uint i = 1; i < GROUP_SIZE; ++i)
        {
            shared_histogram[0] += shared_histogram[i]; 
        }
        
        float weightedLogAverage = (shared_histogram[0] / max(numPixels - float(black_pixels), 1.0)) - 1.0;
        float weightedAvgLum = exp2(((weightedLogAverage / 254.0) * logLumRange) + minLogLum);
        float lumLastFrame = imageLoad(target, 0).x;
        float adaptedLum = lumLastFrame + (weightedAvgLum - lumLastFrame) *  (1.0f - exp(-deltatime * 1.1f));
        imageStore(target, 0, vec4(adaptedLum, 0.0, 0.0, 0.0));
    }
}

and finally Tone Mapping Fragment Shader

#version 430 core

uniform sampler2D scene_refered_image;
uniform sampler1D avg_luminance;

in vec2 o_uv; 

out vec4 FragColor; 

float Reinhard2(float x) {
    const float L_white = 1.0;
    return (x * (1.0 + x / (L_white * L_white))) / (1.0 + x);
}

void main()
{
    float gamma = 2.2f; 

    vec3 hdr_color = texture(scene_refered_image, o_uv).rgb;
    float avgLum = texture(avg_luminance, 0.0f).r;

    mat3 rgb2xyz = mat3(vec3(0.4123, 0.3575, 0.1804),
    vec3(0.2126, 0.7151, 0.0721),
    vec3(0.0193, 0.1191, 0.9505));

    vec3 XYZ = rgb2xyz * hdr_color;

    float L_i = XYZ.y / (9.6f * avgLum + 0.0001f);

    XYZ.y =  Reinhard2(L_i); 

    vec3 rgb = inverse(rgb2xyz) * XYZ; 

    FragColor = vec4(pow(rgb, vec3(1.0f / gamma)), 1.0f);
}

here avg_luminance is the target image of the second compute shader where i stored the final average luminance value and scene_refered_image is the HDR Image generated from a previous pass.

Here is the output without tone mapping just rendering the HDR image on a quad.

and Here after applying the tone mapping shader

With Tone Mapping

So what am I doing wrong?

Leave a Comment