views:

190

answers:

4

If I have an OpenGL texture, and I need to perform HSL modifications on it before rendering the texture, from what I've heard I need a shader. Problem is, I know nothing about shaders. Does anyone know where I would need to look?

I want to write a function where I can pass in a texture and three values, a hue shift in degrees, and saturation and lightness multipliers between 0 and 2, and then have it call a shader that will apply these transformations to the texture before it renders. The interface would look something like this:

procedure HSLTransform(texture: GLuint; hShift: integer; sMult, lMult: GLfloat);

I have no idea what's supposed to go inside the routine, though. I understand the basic math involved in HSL/RGB conversions, but I don't know how to write a shader or how to apply it. Can someone point me in the right direction? Delphi examples preferred, but I can also read C if I have to.

+2  A: 

Writing a shader is not as hard as it sounds. You should look into something like Cg, or HLSL... Basically, you're going to end up writing a single function that takes a pixel and transforms it.

The CG Tutorial

Once you're shader is written, you'll load and bind it with OpenGL extension functions. It will then be applied by the rasterizer when you push geometry into the pipeline. Here is a simple tutorial that shows how to write and use a very basic shader:

Tutorial - Cg Pixel Shaders in OpenGL

dicroce
+1  A: 

If you don't want to use shaders (i.e. real time is not needed) you can get the pixels of the texture and modify it using Delphi code. Something like this:

  //Get copy of pixels. P is a pointer to floats (^single)
  GetMem(P,TextureHeight * TextureWidth * 4 * SizeOf(single));
  glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_FLOAT,P);
  ... loop and modify pixels here. 4 float values per pixels: Red, green, blue and alpha
  //Update texture with modified pixels
  glTexImage2D(GL_TEXTURE_2D, 0, 4, TextureWidth, TextureHeight , 0, GL_RGBA, GL_FLOAT, P);
  FreeMem(P);

You can use GL_UNSIGNED_BYTE if you want to work with bytes instead of floats.

It will be slower performance than shaders but you will probably get the work done in less times because shaders are tricky to write, especially if you've never written one before. They also need to be tested on both NVIDIA and ATI hardware to be sure they work properly.

Ville Krumlinde
This does have to be done in real time, unfortunately. It would be nice to be able to prerender it, but that won't work.
Mason Wheeler
+1  A: 

Use render to texture:

  1. Create a new (target) texture texHSL with the size of your source texture texRgb
  2. Set up a viewport with size of texHSL using an orthogonal projection
  3. Create and bind a framebuffer and use texture texHSL as a render target
  4. Render a screen size quad (may be larger) using a shader. For the shader bind texRgb using a uniform
  5. The shader reads the texture value for the currently rendered value (see by answer there), computes the RGB->HSL conversion and writes the HSL value to gl_FragColor
  6. Unbind and delete the framebuffer
  7. Voila, your converted image is in texHSL
Danvil
In steps 3 and 6, with "render buffer" you mean "framebuffer object"?
Thomas
Thanks, updated it.
Danvil
+3  A: 

This is quite involved, and if you're new to shaders and FBO's it will take some time to understand it. So here is some minimal, untested OpenGL code that implements Danvil's answer. Error checking has been left out; it's complicated enough as it is. If you call this function many times, you should hold on to any or all of the objects created in this code.

If you don't care about speed, VilleK's answer that runs on the CPU is a lot easier...

// Create the target texture object.
GLuint target;
glGenTextures(1, &target);

// Bind the target texture.
glBindTexture(GL_TEXTURE_2D, target);

// Allocate texture memory. width and height must be the size of the original texture.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

// Create a framebuffer object.
GLuint fbo;
glGenFramebuffers(1, &fbo);

// Bind the framebuffer object.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);

// Attach the target texture as the render target.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target, 0);

// Check framebuffer status.
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    throw "Framebuffer incomplete.";

// Create fragment shader to do the transformation.
GLuint frag = glCreateShader(GL_FRAGMENT_SHADER);

// Set the shader's source code.
char const *source =
    "uniform sampler2D input;\n"
    "uniform float hShift, sMult, lMult;\n"
    "void main() {\n"
    "    vec4 color = texture2D(input, gl_FragCoord.xy);\n"
    "    /* Do your HSL transformations here... */\n"
    "    gl_FragColor = color;\n"
    "}\n";
glShaderSource(frag, 1, &source, 0);

// Compile the shader.
glCompileShader(frag);

// Check compilation result. Here, you probably want to use glGetShaderInfoLog() to get any compilation errors.
GLint status;
glGetShader(frag, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
    throw "Shader compilation failed";

// Create the program.
GLuint program = glCreateProgram();

// Attach the shader to the program.
glAttachShader(program, frag);

// Link the program.
glLinkProgram(program);

// Check link result. Here, you probably want to use glGetProgramInfoLog() to get any link errors.
glGetProgram(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
    throw "Program linking failed"

// Use the program for subsequent rendering.
glUseProgram(program);

// Set the values of the uniform parameters.
glUniform1i(glUniformLocation(program, "input"), 0);
glUniform1f(glUniformLocation(program, "hShift"), hShift);
glUniform1f(glUniformLocation(program, "sMult"), sMult);
glUniform1f(glUniformLocation(program, "lMult"), lMult);

// Bind the source texture to read from.
glBindTexture(GL_TEXTURE_2D, texture);

// Set up the viewport and matrices.
glPushAttrib(GL_VIEWPORT_BIT | GL_TRANSFORM_BIT);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0, 1, 0, 1, 0, 1);
glViewport(0, 0, width, height);

// Render a quad.
glBegin(GL_QUADS);
glVertex2i(0, 0);
glVertex2i(1, 0);
glVertex2i(1, 1);
glVertex2i(0, 1);
glEnd();

// Restore the matrices and viewport.
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glPopAttrib();

// Stop using the program.
glUseProgram(0);

// Stop using the framebuffer object.
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// Delete created objects.
glDeleteProgram(program);
glDeleteShader(frag);
glDeleteFramebuffers(1, &fbo);

// Optionally, delete the old, untransformed texture.
glDeleteTextures(1, &texture);

// Return the new texture.
return target;

Hope that helps. For pre-2.0 OpenGL, you need to load the proper extensions first, and slap on some EXTs and/or ARBs here and there.

If you're willing to go down this road, and it doesn't work out, don't hesitate to post a comment describing the problems you encounter. I'll be happy to help!

Thomas
Wow! You did some nice work there :) I was too lazy to write it out ;)
Danvil