Hello all! I’m doing something that seems very simple. I want to overlay one texture on top of another in a custom shader using a standard ‘over’ blending function. It works great until I add “#include <colorspace_fragment>” which all THREE shaders should do to convert the output from linear to sRGB color space. This corrupts the color where the ‘over’ image is partially transparent.
I have a very simple POC in JSFiddle:
https://jsfiddle.net/Ls9ajdvg/
Comment out “#include <colorspace_fragment>” in the fragment shader to see the difference between with/without color space conversion.
Here is an example using an overlay image that contains text with a gaussian blur applied such that the edges are partially transparent. Notice the blue outline around the text where the pixels are partially transparent. I think this most clearly demonstrates the issue.
I also have a gradient texture in the JSFiddle that you can use to demonstrate the issue.
After more research, I finally found the issue. Blending colors in linear space is not the same as blending in sRGB space. My mistake is that I thought blending colors in linear space and then converting the result to sRGB (via #include <colorspace_fragment>) would produce the same result as blending the colors in sRGB space. However, this is not true due to the nature of the math / gamma curve.
So, as far as I know, the correct solution is to convert the colors to sRGB space, do the blending, and convert the result back to linear space.
The blending code would then look something like this:
vec3 linearToSRGB(vec3 color) {
const vec3 a = vec3(12.92);
const vec3 b = vec3(1.055);
const vec3 c = vec3(0.055);
const float threshold = 0.0031308;
return mix(a * color, b * pow(color, vec3(1.0 / 2.4)) - c, step(threshold, color));
}
vec3 srgbToLinear(vec3 color) {
const vec3 a = vec3(12.92);
const vec3 b = vec3(1.055);
const vec3 c = vec3(0.055);
const float threshold = 0.04045;
return mix(color / a, pow((color + c) / b, vec3(2.4)), step(threshold, color));
}
vec4 overlay(vec4 over, vec4 base) {
vec4 overSRGB = vec4(linearToSRGB(over.rgb), over.a);
vec4 baseSRGB = vec4(linearToSRGB(base.rgb), base.a);
float alpha = overSRGB.a + baseSRGB.a * (1.0 - overSRGB.a);
vec3 color = (overSRGB.rgb * overSRGB.a + baseSRGB.rgb * baseSRGB.a * (1.0 - overSRGB.a)) / alpha;
return vec4(srgbToLinear(color), alpha);
}