Search code examples
openglcolorsglslshaderlookup-tables

Using a 3D Lookup Table from a 2D PNG in GLSL and OpenGL in C++


I'm trying to load a PNG representing a color look up table, for my color grading shader.

Here is the Neutral LUT png representation:

img

I'm unsure how to properly load this as a 3d texture and then pass it into my shader. Clearly, there is color transformation occurring, but I end up with a noisy rainbow mess, rather than the neutral image. For example: img

Here is how I create the texture

    glGenTextures(1, &lut_texture);
    glBindTexture(GL_TEXTURE_3D, lut_texture);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB8, 32, 32, 32, 0, GL_RGB,
        GL_UNSIGNED_BYTE, &lut_image[0]);

Here is how I pass it into the shader:

    glUseProgram(colorGradingProgram);
    glUniform1f(glGetUniformLocation(colorGradingProgram, "tex0"), 0);
    glUniform1f(glGetUniformLocation(colorGradingProgram, "lut"), 1);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, colorBuffers[0]); 
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_3D, lut_texture); 
    drawQuad();
    glBindTexture(GL_TEXTURE_3D, 0); 
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, 0); 
    glUseProgram(0);

Here is the frag shader (using GLSL 1.x):

uniform sampler2D tex0;
uniform sampler3D lut;

void main(void)
{
  vec4 colorIn = texture2D(tex0, gl_TexCoord[0].st);

  vec4 colorOut;    
  colorOut.rgb = texture3D(lut, colorIn.rgb).rgb;   

  gl_FragColor = colorOut;  
}

What I'm most unclear about is dimensions specified for the call to

glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB8, 32, 32, 32, 0, GL_RGB,
        GL_UNSIGNED_BYTE, &lut_image[0]);

Depending on what values I use here, my results vary wildly. The closest thing I can get to a neutral image, is using (2,2,2) as width,height,depth, but this does not resonate with me intuitively and is not totally neutral. I do not understand what I am doing wrong or how this is supposed to be achieved (translating the 2d texture to a 3d volume)


Solution

  • Based on my educated guess from the comments above, you need to

    1. Upload each texture layer separately.
    2. Specify GL_RGBA format.

    The folowing code should do that (disclaimer -- untested):

    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB8, 32, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, 1024);
    for(int z = 0; z < 32; ++z) {
        glPixelStorei(GL_UNPACK_SKIP_PIXELS, 32*z);
        glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, z, 32, 32, 1, GL_RGBA, GL_UNSIGNED_BYTE, &lut_image[0]);
    }