Saturday, 30 July 2011

Using FrameBufferObjects in shaders

Using FrameBufferObjects in shaders would go something like this:
  • Set up your FBO (see here)
  • Render scene while writing values to gl_FragColor and gl_FragDepth (in our case). You could also have more draw buffers, then you could also write to gl_FragData[0-9].
    Keep in mind, that values will be clamped to [0,1] unless you use glClampColor to turn that behaviour off. e.g.
    glClampColor(GL_CLAMP_FRAGMENT_COLOR, GL_FALSE);
    glClampColor(GL_CLAMP_VERTEX_COLOR, GL_FALSE);
    glClampColor(GL_CLAMP_READ_COLOR, GL_FALSE);
  • Use FBO texture(s) as shader input:
    //set up textures for postprocessing, color int unit 0, depth in 1
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, fboInfo.color);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, fboInfo.depth);
    //sample depth like regular texture and values are in all channels (x,x,x,x)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
    glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
    //use compiled post-processing shader
    glUseProgram(postProcHandle);
    //set up texture unit uniforms / texture samplers
    glUniform1i(glGetUniformLocation(postProcHandle, "color"), 0);
    glUniform1i(glGetUniformLocation(postProcHandle, "depth"), 1);
    This is the post-processing shader code (If you don't see anything you might need to linearize your depth-values):
    uniform sampler2D color;
    uniform sampler2D depth;
    void main() {
        float c = texture2D(depth, gl_FragCoord.xy / vec2(SCREEN_WIDTH, SCREEN_HEIGHT)).a;
        gl_FragColor = vec4(c, c, c, 1.);
    }
  • Render a full-screen quad using you shader.
    glRectf(-1,-1,1,1);

FrameBufferObjects in OpenGL

FrameBufferObjects are basically off-screen frame buffers, similar to the regular frame buffer you are normally rendering to. Using FBOs in OpenGL you can do some nice stuff like post-processing or rendering to a texture. There's a difference between:
  • FrameBufferObjects: FBOs can be bound to / used as a texture, but performance may be lower.
and
Here's a example of how to set up an intermediate frame buffer, complete with a depth buffer:
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600

struct FBOInfo {
    GLuint id;
    GLuint color;
    GLuint depth;
};

GLuint createTexture2D(const int w, const int h, GLint internalFormat, GLenum format, GLenum type)
{
    GLuint textureId;
    glGenTextures(1, &textureId);
    glBindTexture(GL_TEXTURE_2D, textureId);
    //NOTE: You should use GL_NEAREST here. Other values can cause problems
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    if (GL_DEPTH_COMPONENT == format) {
        //sample like regular texture, value is in all channels
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
        glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
    }
    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, 0);
    glBindTexture(GL_TEXTURE_2D, 0);
    return textureId;
}

bool createFBO(FBOInfo & fboInfo)
{
    bool result = false;
    //generate textures for FBO usage. You could use other formats here, e.g. GL_RGBA8 for color
    fboInfo.color = createTexture2D(SCREEN_WIDTH, SCREEN_HEIGHT, GL_RGBA16F, GL_RGBA, GL_FLOAT);
    fboInfo.depth = createTexture2D(SCREEN_WIDTH, SCREEN_HEIGHT, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_FLOAT);
    //generate and bind FBO
    glGenFramebuffers(1, &fboInfo.id);
    glBindFramebuffer(GL_FRAMEBUFFER, fboInfo.id);
    //bind color and depth texture to FBO you could also use glFramebufferTexture2D with GL_TEXTURE_2D
    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, fboInfo.color, 0);
    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, fboInfo.depth, 0);
    //check if FBO was created ok
    if (GL_FRAMEBUFFER_COMPLETE == glCheckFramebufferStatus(GL_FRAMEBUFFER)) {
        printf("FBO %d set up successfully. Yay!\n", fboInfo.id);
        result = true;
    }
    else {
        printf("FBO %d NOT set up properly!\n", fboInfo.id);
    }
    //unbind FBO for now
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    return result;
}
When you want to use the FBO for rendering do:
//setup framebuffer object
FBOInfo fboInfo;
createFBO(fboInfo);
Then in your rendering loop do:
//REMEMBER to enable depth buffer. Otherwise nothing is written to it!
glEnable(GL_DEPTH_TEST);
//bind the FBO as the current frame buffer
glBindFramebuffer(GL_FRAMEBUFFER, fboInfo.id);
//then render your stuff
glRectf(-1,-1,1,1);
//now bind the regular frame buffer again
glBindFramebuffer(GL_FRAMEBUFFER, 0);
Now you have the color and depth buffer available as a texture and can do nice things with it. See here on how to use those textures in shaders.

Wednesday, 13 July 2011

Ease functions

Ease functions are important for interpolation of just about anything. Popular functions are "smoothstep" or "ease in" and "ease out". This images shows what they look like for x[0,1] and y[0,1]:

linear: y=x, ease out: y=(x-1)^3+1, ease in: y=x^3, smoothstep: y=3x^2-2x^3,
y=sin(x*PI/2), y=1-cos(x*PI/2)
There are other popular functions for ease in and ease out, e.g. y=sin(x*PI/2) resp. y=1-cos(x*PI/2). What is important about those functions is if they're C1- and/or C2-continuous when you want to combine functions. C1-continuity means that the first derivative or the tangent of the function is the same for x=0 and x=1, meaning you can piece together two of the curves and there are no sudden changes or jumps for y. C2-continuity means that the second derivative or how the tangent changes is the same.
y=sin(x*PI/2) and y=1-cos(x*PI/2) are not C1-continuous, but you can use them to start/end a linear interpolation for example.

The nice picture was plotted with http://fooplot.com