Date
1 - 3 of 3
LUT tables in OCIO
ggar...@...
Currently, I am using OCIO with a mixed CPU/GPU pipeline. Luts are 4 float colors which get fed like:
//
//
// Compute lutMin, lutMax, and scale and offset
// values, lutM and lutT, so that
//
// lutM * lutMin + lutT == 0
// lutM * lutMax + lutT == 1
//
void init_pixel_values( float* pixelValues )
{
static const float MIDDLE_GRAY = 0.18f;
static const int NUM_STOPS = 10;
lutMin = MIDDLE_GRAY / (1 << NUM_STOPS);
lutMax = MIDDLE_GRAY * (1 << NUM_STOPS);
float logLutMin = logf (lutMin);
float logLutMax = logf (lutMax);
lutM = 1 / (logLutMax - logLutMin);
lutT = -lutM * logLutMin;
//
// Build a 3D array of RGB input pixel values.
// such that R, G and B are between lutMin and lutMax.
//
for (size_t ib = 0; ib < _lutN; ++ib)
{
float b = float(ib) / float(_lutN - 1.0);
float B = expf((b - lutT) / lutM);
for (size_t ig = 0; ig < _lutN; ++ig)
{
float g = float(ig) / float(_lutN - 1.0);
float G = expf ((g - lutT) / lutM);
for (size_t ir = 0; ir < _lutN; ++ir)
{
float r = float(ir) / float(_lutN - 1.0);
float R = expf ((r - lutT) / lutM);
size_t i = (ib * _lutN * _lutN + ig * _lutN + ir) * 4;
pixelValues[i + 0] = R;
pixelValues[i + 1] = G;
pixelValues[i + 2] = B;
pixelValues[i + 3] = 1.0f;
}
}
}
}
void calculate_ocio()
{
if ( !_inited )
{
//
// Init lut table to 0
//
clear_lut();
//
// Init table of pixel values
//
init_pixel_values( lut );
}
try {
OCIO::DisplayTransformRcPtr transform = OCIO::DisplayTransform::Create();
transform->setDisplay( display.c_str() );
transform->setView( view.c_str() );
OCIO::ConstContextRcPtr context = config->getCurrentContext();
OCIO::ConstProcessorRcPtr processor =
config->getProcessor(context, transform,
OCIO::TRANSFORM_DIR_FORWARD);
// Create LUT
OCIO::PackedImageDesc img(&lut[0], lut_size()/4,
/*height*/ 1, /*channels*/ 4);
processor->apply( img );
catch( const OCIOException& e )
{
LOG_ERROR( e.what() );
}
And finally, the glsl shader does:
// Images
uniform sampler2D fgImage;
uniform sampler3D lut;
// Lut variables
uniform bool enableLut;
uniform bool lutF;
uniform float lutMin;
uniform float lutMax;
uniform float lutM;
uniform float lutT;
void main()
{
vec4 c = texture2D(fgImage, gl_TexCoord[0].st);
if (enableLut)
{
c.rgb = lutT + lutM * log( clamp(c.rgb, lutMin, lutMax) );
c.rgb = exp( texture3D(lut, c.rgb).rgb );
}
gl_FragColor = c;
}
This seems to work on some images and fail in others (syntheticChart.exr for example). The problem, afaict, is in the init_pixel_values. I am unsure what to set the minimum and maximum for that lut. Currently, this is done like in CTL, which may not be optimal for OCIO. I am still trying to find where in OCIO the lut table is created, since ociodisplay seems to show color spaces better.
Gonzalo Garramuno <ggar...@...>
if (enableLut){c.rgb = lutT + lutM * log( clamp(c.rgb, lutMin, lutMax) );c.rgb = exp( texture3D(lut, c.rgb).rgb );}
I was able to match the output of ociodisplay with a NUM_STOPS of 8, a lattice of 64x64x64 and with the following change to the shader code:
c.rgb = exp( scale * texture3D(lut, c.rgb).rgb + offset );
where scale and offset are based on the lut edge len, as shown in the original Sony Imageworks' GPUGems 2, chapter 24.
Now I need to understand why the GpuShaderDesc of OCIO spits out some more vector multiplications before the shader look up. That seems to improve the lookup so that only a lattice of 32x32x32 is needed.