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.


Gonzalo Garramuno <ggar...@...>
 


      c.rgb = exp( scale * texture3D(lut, c.rgb).rgb + offset ); 

Sorry, that should have been:
 
c.rgb = exp( tex3D(lut, c.rgb * scale + offset ).rgb );