Skip to content

Lab 6: Framebuffer with 2D animation

ArthurFirmino edited this page Feb 28, 2018 · 1 revision

Lab 6: Framebuffer with 2D animation

In this lab, we will practice using the framebuffer object to add full-screen effects to a 2D animation. Framebuffer objects are bitmaps on the video card that the GPU can put up on the screen. But we don't always let the video card put up images immediately -- we can apply graphical effects with shaders and show the rendered image with many interesting effects.

This programming practice should be filled in project "framebuffer2d", in file "main.cpp".

Using the Framebuffer class

In this practice, we will use OpenGP's wrapper classes to do our operations. They are the "Framebuffer" object, implemented in "/GL/Framebuffer.h" and "Texture" objects in "/GL/Texture.h". In our lab today, we will first render a simple animation onto a framebuffer object, apply post-process effect onto the rendered image, then display the image with those effects.

Framebuffers and two-pass rendering are very powerful tools to create many interesting visual effects. Here, we will only demo how to use these two classes from a user's perspective, without going into details about their internal implementation.

  • Initialization

First, declare the two object on the top of the source file. Also, in the main() method, add two lines of initialization code. Note that you need to create a Framebuffer object first, before attaching a Texture object.

/// TODO: declare Framebuffer and color buffer texture
std::unique_ptr<Framebuffer> fb;
std::unique_ptr<RGBA8Texture> c_buf;
/// TODO: initialize framebuffer
fb = std::unique_ptr<Framebuffer>(new Framebuffer());
/// TODO: initialize color buffer texture, and allocate memory
c_buf = std::unique_ptr<RGBA8Texture>(new RGBA8Texture());
c_buf->allocate(width, height);
/// TODO: attach color texture to framebuffer
fb->attach_color_texture(*c_buf);
  • Draw the animation

We will draw an animation as we did before, using Eigen transform methods and transform matrices. Note that we also call glEnable() and glBlendFunc() to let opengl draw textures with alpha blending. This is done for you in the drawScene(float timeCount) function, do take a look.

  • Two-pass Rendering

At this point, if you run the program, a simple animation will show up. To use the framebuffer object we just made, we will first render the animation frame onto the framebuffer, then apply the effect shader contained in the ScreenQuad class.

In the display callback, fill in the following and try to understand what is happening:

    Window& window = app.create_window([](Window&){
        glViewport(0,0,width,height);
        /// TODO: First draw the scene onto framebuffer
        /// bind and then unbind framebuffer
        fb->bind();
            glClear(GL_COLOR_BUFFER_BIT);
            drawScene(glfwGetTime());
        fb->unbind();
        /// Render to Window, uncomment the lines and do TODOs
        glViewport(0, 0, width, height);
        glClear(GL_COLOR_BUFFER_BIT);
        fbShader->bind();
        /// TODO: Bind texture and set uniforms
        glActiveTexture(GL_TEXTURE0);
        c_buf->bind();
        fbShader->set_uniform("tex", 0);
        fbShader->set_uniform("tex_width", float(width));
        fbShader->set_uniform("tex_height", float(height));
        quad->set_attributes(*fbShader);
        quad->draw();
        /// Also unbind the texture    
        c_buf->unbind();
        fbShader->unbind();
    });

Using the fragment shader to implement post-process effects.

The major part of the visual effects is implemented in the fragment shader "fb_fshader.glsl". In the file there are two segment of demo code that implements the Gaussian blur effect and the Sobel edge detection operator. Please refer to the glsl documentation to study how to write openGL shader codes. Feel free to invent your own shader and show your creativity!

#version 330 core
uniform sampler2D tex;
uniform float tex_width;
uniform float tex_height;
in vec2 uv;
out vec3 color;

#define GAUSSIAN
//#define SOBEL
//#define NORMAL

#ifdef SOBEL
float rgb_2_luma(vec3 c){ return .3*c[0] + .59*c[1] + .11*c[2]; }
#endif

void main() {
#ifdef GAUSSIAN
    ///--- Gaussian convolution
    float std = 2; ///< standard deviation (<.3 disable)
    // float std = .1; ///< standard deviation (<.3 disable)
    vec3 color_tot = vec3(0,0,0);
    float weight_tot = 0;
    int SIZE = 1 + 2 * 3 * int( ceil(std) );
    for(int i=-SIZE; i<=SIZE; i++){
        for(int j=-SIZE; j<=SIZE; j++){
            float w = exp(-(i*i+j*j)/(2.0*std*std*std*std));
            vec3 neigh_color = texture(tex, uv+vec2(i/tex_width,j/tex_height)).rgb;
            color_tot += w * neigh_color; 
            weight_tot += w;
        }
    }
    color = color_tot / weight_tot; ///< ensure \int w = 1
#endif

#ifdef SOBEL
    float t_00 = rgb_2_luma( textureOffset(tex, uv, ivec2(-1, -1)).rgb );
    float t_01 = rgb_2_luma( textureOffset(tex, uv, ivec2(-1,  0)).rgb );
    float t_02 = rgb_2_luma( textureOffset(tex, uv, ivec2(-1, +1)).rgb );
    ///--- x=0
    float t_10 = rgb_2_luma( textureOffset(tex, uv, ivec2( 0, -1)).rgb );
    float t_12 = rgb_2_luma( textureOffset(tex, uv, ivec2( 0, +1)).rgb );
    ///--- x=+1
    float t_20 = rgb_2_luma( textureOffset(tex, uv, ivec2(+1, -1)).rgb );
    float t_21 = rgb_2_luma( textureOffset(tex, uv, ivec2(+1,  0)).rgb );
    float t_22 = rgb_2_luma( textureOffset(tex, uv, ivec2(+1, +1)).rgb );

    float Gx = t_20 + t_21*2 + t_22 - t_00 - t_01*2 - t_02;
    float Gy = t_02 + t_12 * 2 + t_22 - t_00 - t_10*2 - t_20;
    float G = sqrt(Gx * Gx + Gy * Gy);
    color = vec3(G, G, G);
#endif

#ifdef NORMAL
    color = texture(tex,uv).rgb;
#endif
}

Clone this wiki locally