imgui
imgui copied to clipboard
sRGB framebuffer and color-edit gradients
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:
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
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.
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.
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.
Same answer, but PR for this are welcome.
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.
https://github.com/ocornut/imgui/blob/cc3a2200a9443974b998ef88d0054600f5afe147/imgui_widgets.cpp#L5516-L5518
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.
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.