You can paste this function into a shader and call it if needed to color grade your output.
Should be used after tonemapping, it assumes already gamma spaced LDR RGB values with the channels in the 0.0 to 1.0 range.
vec3 color_grade(vec3 col3) {
float lmip = clamp((1.0-col3.b),0.00001,0.99999)*30.999;
lmip = clamp(lmip, 0.0, 30.8);
float lmip1 = floor(lmip);
lmip = fract(lmip);
vec2 llook = vec2(clamp((1.0-col3.x)*0.985,0.017,0.9999)*0.03125+lmip1*0.03125, 0.015+col3.g*0.981);
llook.g = clamp(llook.g, 0.0, 0.983);
vec2 llook2 = llook;
llook2.x += 0.03125;
llook.y=1.0-llook.y;
llook2.y=1.0-llook2.y;
return mix(textureLod(lutMap, llook, 0.0).rgb, textureLod(lutMap, llook2, 0.0).rgb, lmip);
}
Using it is simple: new_rgb_value = color_grade(old_rgb_value);
You also need to load a LUT (lookup table) first, should be a sampler2D named lutMap.
This assumes usual 32*32*32 LUTs, saved as 2D texture (so that this will work in engines that can't load volumetric textures.) The interpolation for the third axis is done manually in the function with 2 lookups and a blend factor.
Basically, this means the LUT is a 1024*32 2D texture. I use DDS, but any format will do if it doesn't change values too much due to compression.
I calibrated the values in C3D so they may need slight adjustments in other engines. Easy to check if you are on point: just load the unaltered base LUT into the engine and toggling the color grading should make almost no visual difference. (there is a bit of quantization going on in the LUT due to the small texture sizes, but that shouldn't have too much visual impact because bilinear filtering interpolates the inbetween-values nicely.)
If it looks totally messed up, eventually you have to comment out these:
llook.y=1.0-llook.y;
llook2.y=1.0-llook2.y;
Also fun to do:
Save 8 LUTs into one texture vertically (so that you have a 1024*256 texture with your 8 LUTs) and add this before the final line:
llook.y*=0.125;
llook.y+=luter*0.125;
llook2.y*=0.125;
llook2.y+=luter*0.125;
Define and update an uniform named luter and you can easily switch between your 8 packed LUTs from your main program.
This is what I use. Fun to switch around and see which looks best in a scene.
I attached a base LUT you can use as starting point.
What I do to get new LUTs is this:
I take a screenshot of the scene with no color grading, paste it into Photoshop, paste the base LUT in the next layer.
Then I adjust the look and feel to my liking with adjustment layers on top, so that they effect both image layers.
When I am happy with the look, I select the LUT (it's in an own layer, so just loosely mask around it, move it one pixel and the mask will snap exactly around it) and then I do a shift copy to copy it with all adjustment layers applied.
I can then make a new image, paste the clipboard in and save it as new LUT that will make the rendered runtime scene look exactly like what I adjusted it to in Photoshop.
There is also a LUT based adjustment layer that can load many LUT formats. This makes it very easy to get LUTs from the web into your game.
Have fun

Edit:
Added a demo zip with a simple Blitzmax+OpenB3D setup. (no exe, should be safe

)
Shows how it can be easily used with a scene that has no shaders applied.
If you don't want to mess with a post process setup in your own project you can ofcourse also skip that whole part and just use the color grading function in object shaders as last step.

