Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sRGB framebuffer and color-edit gradients #4890

Open
chrislu opened this issue Jan 11, 2022 · 6 comments
Open

sRGB framebuffer and color-edit gradients #4890

chrislu opened this issue Jan 11, 2022 · 6 comments

Comments

@chrislu
Copy link

chrislu commented Jan 11, 2022

Hi,
I am using imgui under Vulkan with a sRGB framebuffer (VK_FORMAT_B8G8R8A8_SRGB). I modified the fragment shader so linearize the colors before writing them out to the framebuffer:

#version 450 core
layout(location = 0) out vec4 fColor;

layout(set=0, binding=0) uniform sampler2D sTexture;

layout(location = 0) in struct {
    vec4 Color;
    vec2 UV;
} In;

vec4 to_linear(vec4 sRGB)
{
    bvec3 cutoff = lessThan(sRGB.rgb, vec3(0.04045));
    vec3  higher = pow((sRGB.rgb + vec3(0.055)) / vec3(1.055), vec3(2.4));
    vec3  lower  = sRGB.rgb / vec3(12.92);

    return vec4(mix(higher, lower, cutoff), sRGB.a);
}

void main()
{
    fColor = to_linear(In.Color) * to_linear(texture(sTexture, In.UV.st));
}

The result is pretty much what I would expect. However, I noticed that the colorpicker gradients are still off:
imgui_srgb_framebuffer_gradients

The left is the sRGB framebuffer with my changes applied to the imgui shader. The right is plain imgui on a standard unorm framebuffer.

This is really strange as I tried several things like linearizing in the vertex shader. The result is always the same. All colors change, even the hue-slider changes as expected BUT not the large gradient box. I also tried wild things like inverting the In.Color.rgb values and these changes applied to the gradient. Also notice the alpha gradient also is off.

What do I have to change to also get these gradients to render correctly on an sRGB framebuffer?

Thanks
-chris

@chrislu
Copy link
Author

chrislu commented Jan 12, 2022

After a lot of testing and playing around this is related to the alpha channels. The black/grey ramp is blended over the white/blue ramp. So, I assume that we get sRGB/non-linear colors into the shader and blend them using the hardware (which I assume blends in linear space). I think there is no way of getting the right result just by adapting the shader here.

@ocornut
Copy link
Owner

ocornut commented Jan 12, 2022

I have no commentary on this yet as this is going over my head and I haven't got the time to understand this well.
But linking to #578 #1724, #2943 for reference.

@InevitablyDivinity
Copy link

It would be most convenient to allow the programmer to specify a colour-space for imgui to output to when the Vulkan backend is in use, so that the programmer doesn't need to build their render passes around what imgui expects.

@ocornut
Copy link
Owner

ocornut commented Oct 16, 2022

Same answer, but PR for this are welcome.

@osor-io
Copy link

osor-io commented Nov 29, 2022

I was looking at this as well and seeing how converting form SRGB -> Linear at the vertex level fixes the colours in the rest of the widgets but not the ColorPicker3/4 SV square.

I believe it must come from the way its done with a horizontal gradient first from white to the hue colour, then a vertical one from transparent black to opaque black.

imgui/imgui_widgets.cpp

Lines 5516 to 5518 in cc3a220

// Render SV Square
draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);
draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);

Because the hardware will take both sRGB values, convert to linear, then blend and convert back to sRGB. The vertical gradient will essentially show up as if it was done in linear space as @chrislu said.

I'm wondering if there's a way to get around this with the current functionality (while keeping the SRGB framebuffer/swapchain view). You could fiddle with the alpha of that second black gradient on the pixel shader, but not in a generic way that I can see yet. Modifying the vertex colours at the source won't do anything because what we want to change here is the curve of the interpolated vertex values on the pixel shader if we can't change how they blend.

@osor-io
Copy link

osor-io commented Nov 29, 2022

I tried locally a version of the SV square that just renders a grid of quads and it goes back to rendering as expected. But it's obviously slower and takes more verts. Code could be made much better but just to confirm that this was the issue:

// Render SV Square
int subdivisions = 10;
ImVec2 sub_quad_size = ImVec2(sv_picker_size, sv_picker_size) / float(subdivisions);
for (int y = 0; y < subdivisions; ++y)
for (int x = 0; x < subdivisions; ++x)
{
    float saturations[2];
    saturations[0] = float(x)   / float(subdivisions);
    saturations[1] = float(x+1) / float(subdivisions);

    float values[2];
    values[0] = float(subdivisions-y)   / float(subdivisions);
    values[1] = float(subdivisions-y-1) / float(subdivisions);

    ImVec4 colors[4] = { ImVec4(0,0,0,1), ImVec4(0,0,0,1), ImVec4(0,0,0,1), ImVec4(0,0,0,1)};
    ColorConvertHSVtoRGB(H, saturations[0], values[0], colors[0].x, colors[0].y, colors[0].z);
    ColorConvertHSVtoRGB(H, saturations[1], values[0], colors[1].x, colors[1].y, colors[1].z);
    ColorConvertHSVtoRGB(H, saturations[1], values[1], colors[2].x, colors[2].y, colors[2].z);
    ColorConvertHSVtoRGB(H, saturations[0], values[1], colors[3].x, colors[3].y, colors[3].z);

    ImVec2 top_right   = picker_pos + ImVec2(float(x),float(y))     * sub_quad_size; 
    ImVec2 bottom_left = picker_pos + ImVec2(float(x+1),float(y+1)) * sub_quad_size; 
    draw_list->AddRectFilledMultiColor(top_right, bottom_left,
                                       ColorConvertFloat4ToU32(colors[0]),
                                       ColorConvertFloat4ToU32(colors[1]),
                                       ColorConvertFloat4ToU32(colors[2]),
                                       ColorConvertFloat4ToU32(colors[3]));
}

Another drawback is that depending on how many quads you draw you can get to a point where you see the interpolation artifacts.

Then I checked the original issue to add a colour picker. And it seems that originally the code ran in a similar way, then got changed to the smarter two-draw method.

mkskelet added a commit to crustal-creations/imgui that referenced this issue Apr 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants