Fast sRGB conversion GLSL snippet

EDIT: I’ve updated the code below with a new version using ternary operators that’s ~25% faster. The ternary operators compile to conditional assignment instructions that are faster than step+mix. The old slower version can be found here for reference: http://www.java-gaming.org/?action=pastebin&id=1467

Hey, this goes kind of hand-in-hand with my gamma correction post. Here’s a small snippet to manually calculate sRGB colors, which is useful in some cases if you want to do the sRGB conversion manually.

vec3 srgbEncode(vec3 color){
	float r = color.r < 0.0031308 ? 12.92 * color.r : 1.055 * pow(color.r, 1.0/2.4) - 0.055;
	float g = color.g < 0.0031308 ? 12.92 * color.g : 1.055 * pow(color.g, 1.0/2.4) - 0.055;
	float b = color.b < 0.0031308 ? 12.92 * color.b : 1.055 * pow(color.b, 1.0/2.4) - 0.055;
	return vec3(r, g, b);
}

vec3 srgbDecode(vec3 color){
	float r = color.r < 0.04045 ? (1.0 / 12.92) * color.r : pow((color.r + 0.055) * (1.0 / 1.055), 2.4);
	float g = color.g < 0.04045 ? (1.0 / 12.92) * color.g : pow((color.g + 0.055) * (1.0 / 1.055), 2.4);
	float b = color.b < 0.04045 ? (1.0 / 12.92) * color.b : pow((color.b + 0.055) * (1.0 / 1.055), 2.4);
	return vec3(r, g, b);
}

There are cases where you would want to do the conversion yourself. For example, if you want to do good dithering, you need to do it in sRGB space, or the noise you add will get distorted by the sRGB encoding. Ideally, dithering shouldn’t add any noise at all to a completely black screen, but if you dither in linear space and then convert to sRGB you’ll get values as high as 5-6 randomly popping up. So, the correct approach is to convert to sRGB space, THEN dither (with GL_FRAMEBUFFER_SRGB disabled since we did that part ourselves).

	color = srgbEncode(color);
	color = clamp(color + (rand(texCoords) - 0.5) * (1 / 255.0), 0.0, 1.0);

Now, let’s say we’re rendering to a GL_SRGB8 texture with GL_FRAMEBUFFER_SRGB on so that we get correct blending in linear space, and we realize that we need dithering to reduce banding (even with gamma correction/sRGB you may get subtle banding). In that case, we can’t encode to sRGB in the shader and disable GL_FRAMEBUFFER_SRGB since we want the gamma correct blending from GL_FRAMEBUFFER_SRGB, so we have to do something pretty ridiculous to get the correct result. We need to dither in sRGB space, but output linear colors for blending, so…

	color = srgbEncode(color);
	color = clamp(color + (rand(texCoords) - 0.5) * (1 / 255.0), 0.0, 1.0);
	color = srgbDecode(color);

Yup, we convert to sRGB, dither, then convert back to linear space again. And that’s an example of why these two functions are useful! The performance of these two functions is very good, as ternary operators are compiled to conditional assignment instructions without any branches required.

So a few questions, what does the ‘step’ function do, what is the ‘mix’ function, and does the vec3 constructor vec3(n) return a vector (n, n, n)?

  1. step() https://www.opengl.org/sdk/docs/man/html/step.xhtml
  2. mix() https://www.opengl.org/sdk/docs/man/html/mix.xhtml
  3. Yes it does! :slight_smile:

I found that a simple ternary operator had very noticeably higher performance than mix+step, so I have updated the code above. Note that each vector component had to be calculated individually as GLSL doesn’t allow (vec3 < vec3) comparisons, only (float < float).

It does with the built-in function lessThan, just not with the infix operator.

And for setting-up photoshop properly: https://docs.google.com/presentation/d/1vS5nAAnXbZ8Pt6-dXxy-xEXDkjfjXTmNnthNGccorl0/edit#slide=id.g14b3e28660_0_8