How to use FBO in libcinder?


#1

I’m new to cinder, and I have read several of cinder tutorials, and codes on github. But I’m running into here. My goal is to make a new red circle object to at the position of mouse click. So I decided not to clear screen every frame to retained the previous frame circles. But in doing so, I ran into the first problem. The screen flickered. After some research, I found out it is because there are 2 default frame buffers, which alternate between even and odd frame. So I tried to use FBO instead. By calling render() method and then draw texture from FBO to screen on draw life-cycle hook. Now something weird happen. After I set color of FBO in render() method to red, it never turn off! And none of the circles were drawn! I am absolutely lost here. Please detail me on how to use FBO properly, and also working tutorial too (if it is not too much to ask). Thank you in advanced.

void setup() {
    solidShader = gl::getStockShader(gl::ShaderDef().color());
    mCircleBatch = gl::Batch::create(geom::Circle().radius(5), solidShader);
    mFbo = gl::Fbo::create(640, 480);
    gl::enableDepthRead();
    gl::enableDepthWrite();
}
void resetDrawPoint() {
    drawPoint.x = -1;
    drawPoint.y = -1;
    drawPoint.isDraw = false;
}
void render() {
    if (drawPoint.isDraw) {
        gl::ScopedModelMatrix modelScope;
        gl::color(Color(255, 0, 0));
        gl::translate(vec2(drawPoint.x, drawPoint.y));
        mCircleBatch->draw();
        resetDrawPoint();
    }
}
void mouseDown(MouseEvent event) {
    drawPoint.x = event.getX();
    drawPoint.y = event.getY();
    drawPoint.isDraw = true;
}
void draw() {
    gl::clear(Color(0.25, 0.5f, 1.0f));
    mFbo->bindTexture();
    gl::draw(mFbo->getColorTexture(), Rectf(0.0f, 0.0f, 480.0f, 480.0f));
    mFbo->unbindTexture()
}

#2

Hi @WakemeUpwhen

You are nearly there, the main problem i can see is that you are not drawing your circle into the framebuffer. You need to bind the framebuffer to be be able to draw into it. You can do this like below:

void setup() {
    solidShader = gl::getStockShader(gl::ShaderDef().color());
    mCircleBatch = gl::Batch::create(geom::Circle().radius(5), solidShader);
    mFbo = gl::Fbo::create(640, 480);
    gl::enableDepthRead();
    gl::enableDepthWrite();
}
void resetDrawPoint() {
    drawPoint.x = -1;
    drawPoint.y = -1;
    drawPoint.isDraw = false;
}
void render() {
    if (drawPoint.isDraw) {
        // This draws everything in this scope into the framebuffer 
        gl::ScopedFramebuffer fboScope(mFbo);
        gl::ScopedModelMatrix modelScope;
        gl::ScopedColor colorScope(Color(255, 0, 0));
        gl::translate(vec2(drawPoint.x, drawPoint.y));
        mCircleBatch->draw();
        resetDrawPoint();
    }
}
void mouseDown(MouseEvent event) {
    drawPoint.x = event.getX();
    drawPoint.y = event.getY();
    drawPoint.isDraw = true;
}
void draw() {
    gl::clear(Color(0.25, 0.5f, 1.0f));
    // No need to bind the textures here as the draw call will know what to do
    gl::draw(mFbo->getColorTexture(), Rectf(0.0f, 0.0f, 480.0f, 480.0f));
}

As a side note it is also good to set the viewport and viewmatrices to the size of the fbo before drawing into it so you have specified exactly how you want to draw into the fbo.

Hope this helps


#3

Hi, and welcome to Cinder!

Another side note: it’s better not to enable the depth buffer in your situation, because you’re rendering in 2D. The Fbo does not even have a depth buffer by default. It’s better to remove the 2 lines in your setup.

Also, if you want to make sure the Fbo has the same size as your window, you can create it in the resize() function of your application, which is called immediately after the window has been created and also every time its size changes:

void MyApp::resize()
{
    const auto w = toPixels( getWindowWidth() );
    const auto h = toPixels( getWindowHeight() );
    mFbo = gl::Fbo::create( w, h );
}

-Paul


#4

Thank you very much. Your code fixed my issue!
Also in my original code I use

gl::color(Color(255, 0, 0));

Instead of

gl::ScopedColor colorScope(Color(255, 0, 0));

Which I think it bind the color to FBO rather than the default frame buffers (that is why my screen isn’t red anymore :-P)

But I wonder what do ‘bindTexture’ do? (I see it from here here)


#5

Hi,

OpenGL is a state machine, so whenever you change a setting like the current color, it will keep using that setting until you change it again. Which, for colors, means that you have to take extra care resetting it to white after setting it to red. In your original code, you draw a red circle while rendering to the Fbo, but you never reset it back to white before drawing the Fbo's texture. If the texture had contained a color image, you would only have seen the red channel.

The easiest way to set and reset colors, is by using the gl::ScopedColor class. It will create a variable that sets the color on its construction and resets the color to what it was before on destruction. Note that the variable is destroyed when it goes out of scope and you don’t have to remember to delete it. This nifty little trick is called RAII.

So in our code above, the color is red when you draw the circle and white when you draw the Fbo texture, thanks to ScopedColor.

The bindTexture() function tells OpenGL to start drawing to the Fbo. But here, too, you want to make sure you don’t forget to unbind it when you’re done drawing. This is where gl::ScopedFramebuffer comes in, which will take care of that for you.

The example you were referring to, needs a little update to also use the scoped classes. It’s the recommended way of doing things nowadays.

-Paul