5
\$\begingroup\$

I'm currently using a simple implementation of linear to sRGB transform:

float sRGB(float x) {
    if (x <= 0.00031308)
        return 12.92 * x;
    else
        return 1.055*pow(x,(1.0 / 2.4) ) - 0.055;
}
vec3 sRGB_v3(vec3 c) {
    return vec3(sRGB(c.x),sRGB(c.y),sRGB(c.z));
}

Possibly, that the correction for small values (x <= 0.00031308) will not matter in practice.

I considered to just let ´pow(x, 1.0 / 2.4) but I'm not sure how much impact it will have.

Is there any way to optimize it? Can be vectorization help? Can some polynomal fitting be better?

\$\endgroup\$
2
  • \$\begingroup\$ A project that does colour space conversions in GLSL: GLSL-Color-Spaces \$\endgroup\$
    – legends2k
    Jun 4, 2021 at 7:54
  • 2
    \$\begingroup\$ You have a typo, it's 0.0031308, only 2 zeroes. Then it's not so small. \$\endgroup\$
    – hyperknot
    Oct 12, 2021 at 22:46

4 Answers 4

6
\$\begingroup\$

Adjusted version of Tynach's answer that leaves the alpha channel alone, since it's already linear.

// Converts a color from linear light gamma to sRGB gamma
vec4 fromLinear(vec4 linearRGB)
{
    bvec3 cutoff = lessThan(linearRGB.rgb, vec3(0.0031308));
    vec3 higher = vec3(1.055)*pow(linearRGB.rgb, vec3(1.0/2.4)) - vec3(0.055);
    vec3 lower = linearRGB.rgb * vec3(12.92);

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

// Converts a color from sRGB gamma to linear light gamma
vec4 toLinear(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);
}
\$\endgroup\$
5
\$\begingroup\$

A few days ago, I ran into this when messing around on Shadertoy. I was curious and some solutions to the various problems I faced, so I may as well post what I've made here for you.

Note, however, that I have never benchmarked this and I don't know how fast it works. But I've heard that different drivers/hardware/etc. implement sRGB conversion differently, so I wanted to make my own, correct implementation... And as far as I can tell, I succeeded. It does not use ifs and operates on entire color vectors at a time.

The trick with bvecs was learned from here. He says it won't work on most GLSL ES versions, but it seems to work well in the browsers I've tested (including Chrome on my Pixel). Still something to consider, especially since my uses are pretty limited (just making test images for my desktop, though I did test on my phone too).

I'm sure somebody could make a more optimal version that's just as accurate, especially as I'm very new to GLSL in general. However, since nobody was really giving you actual code (except to suggest x1/2.2 and the like), and my own code seemed to follow the general advice of avoiding branches, I figured I may as well post it.

// Converts a color from linear light gamma to sRGB gamma
vec4 fromLinear(vec4 linearRGB)
{
    bvec4 cutoff = lessThan(linearRGB, vec4(0.0031308));
    vec4 higher = vec4(1.055)*pow(linearRGB, vec4(1.0/2.4)) - vec4(0.055);
    vec4 lower = linearRGB * vec4(12.92);

    return mix(higher, lower, cutoff);
}

// Converts a color from sRGB gamma to linear light gamma
vec4 toLinear(vec4 sRGB)
{
    bvec4 cutoff = lessThan(sRGB, vec4(0.04045));
    vec4 higher = pow((sRGB + vec4(0.055))/vec4(1.055), vec4(2.4));
    vec4 lower = sRGB/vec4(12.92);

    return mix(higher, lower, cutoff);
}
\$\endgroup\$
2
  • 2
    \$\begingroup\$ This also affects the alpha channel, which is incorrect. The sRGB EOTF should only be applied to the RGB channels. \$\endgroup\$
    – rdb
    Sep 5, 2020 at 10:02
  • 1
    \$\begingroup\$ @rdb Yeah, that's fair. It's also trivial to edit the code to fix that, but there are multiple ways of going about that and I'm not sure which I prefer. It could simply use vec3s the whole way through, and thus require the person to convert to/from vec4 outside the function... Or it could specifically only affect .rgb/.xyz, avoiding any modification to .a/.w. \$\endgroup\$
    – Tynach
    Sep 12, 2020 at 1:06
2
\$\begingroup\$
  • Balance between speed and accuracy: pow(x, 2.2) for textures and pow(x, 1.0/2.2) for output
  • More speed, less accuracy: x*x for textures and sqrt(x) for output
\$\endgroup\$
2
  • 3
    \$\begingroup\$ sRGB is not a simple x^2.2 curve. It is linear at small value, then x^2.4 at larger value, resulting an average of 2.2 gamma. \$\endgroup\$ Mar 15, 2017 at 3:34
  • \$\begingroup\$ @StarBrilliant not sure what I was thinking when I wrote that but you're right, so I rewrote the answer without the inaccurate statements. \$\endgroup\$
    – snake5
    Mar 21, 2017 at 6:31
1
\$\begingroup\$

If you're worried about optimization then use vectors more, the optimizer may or may not do that for you.
Avoid conditionals, all branches of them will be executed.
But the major bottleneck on modern GPUs is from samplers, pow and log are pretty negligible.

If possible then just use sRGB internalformat in your framebuffer, most gpus will do the conversion in hardware.

\$\endgroup\$
1
  • \$\begingroup\$ Is there a practical reason for do POW approximation with fitting or polynomials ? \$\endgroup\$ Jan 11, 2015 at 14:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .