Using FBO for offscreen drawings

FBO in computer graphics stands for frame buffer object. This is an offscreen raster buffer where openFrameworks can draw just like on the screen. You can draw something in this buffer, and then draw the buffer contents on the screen. The picture in the buffer is not cleared with each testApp::draw() calling, so you can use FBO for accumulated drawings.

In openFrameworks, FBO is represented by the class ofFBO.

The typical scheme of its usage is the following:

  1. Declare an ofFbo object, fbo, in the testApp class declaration.
    ofFbo fbo;
  2. Initialize fbo with some size in the testApp::setup() function.
    int w = ofGetWidth();
    int h = ofGetHeight();
    fbo.allocate( w, h );
  3. Draw something in fbo. You can do it not only in testApp::draw() but also in testApp::setup() and testApp::update(). To begin drawing, call fbo.begin(). After this, all drawing commands, such as ofBackground() and ofLine(), will draw to fbo. To finish drawing, call fbo.end(). For example, to fill fbo with white color, use the following code:
    fbo.begin();
    ofBackground( 255, 255, 255 );
    fbo.end();
  4. Draw fbo on the screen using the fbo.draw( x, y ) or fbo.draw( x, y, w, h ) functions. Here, x and y are the top-left corner, and w and h are the optional width and height of the rendered fbo image on the screen. The drawing should be done in the testApp::draw() function. The example of the corresponding code is the following:
    ofSetColor( 255, 255, 255 );	
    fbo.draw( 0, 0 );

Note

The ofFbo class has drawing behavior similar to the image class ofImage. So, the ofSetColor( 255, 255, 255 ); line is needed here to draw fbo without color modulation (see details in the Color modulation section in Chapter 4, Images and Textures).

You can use many FBO objects and even draw one inside another. For example, if you have ofFbo fbo2, you can draw fbo inside fbo2 as follows:

fbo2.begin();
ofSetColor( 255, 255, 255 );
fbo.draw( 0, 0 );
fbo2.end();

Note

Be careful: if you call fbo.begin(), you should always call fbo.end() ; do it before drawing FBO's contents anywhere.

The following tips will be helpful for advanced ofFbo usage:

  • fbo has texture of the type ofTexture, which holds its current picture. The texture can be accessed using fbo.getTextureReference(). See the Using ofTexture for memory optimization section in Chapter 4, Images and Textures, for details on operations with textures.
  • The settings of your video card, such as like antialiasing smoothing, does not affect FBO, so it may happen that your smooth drawing on screen becomes aliased when you perform this drawing using fbo. One possible solution for smooth graphics is using fbo that is double the size of the screen and shrinking fbo to screen size during drawing.
  • When you perform semi-transparent drawing to fbo (with alpha-blending enabled), most probably you should disable alpha-blending when drawing fbo itself on the screen. In the opposite case, transparent pixels of fbo will be blended in the screen one more time, so the resultant picture will be overblended. See the Transparency section in Chapter 4, Images and Textures, for details on blending.
  • By default, fbo holds color components of its pixels as unsigned char values. When more accuracy is needed, you can use float-valued fbo by allocating it with the optional last parameter GL_RGB32F_ARB.
    fbo.allocate( w, h, GL_RGB32F_ARB );

    See an example of using this method in the Implementing a particle in the project section in Chapter 3, Building a Simple Particle System.

Let's consider an example of using the ofFbo object for accumulated drawing.

Spirals example

Consider a drawing algorithm consisting of the following steps:

  1. Set a = 0 and b = 0.
  2. Set the pos point's position to the screen center.
  3. Set a += b.
  4. Set b += 0.5.
  5. Move the pos point a step of fixed length in the direction defined by the angle a measured in degrees.
  6. Each 100 steps change the drawing color to a new color, generated randomly.
  7. Draw a line between the last and current positions of pos.
  8. Go to step 3.

This algorithm is a kind of generative art algorithm—it is short and can generate interesting and unexpected drawings.

The result of the algorithm will be a picture with the the colored trajectory of pos moving on the screen. The b value grows linearly, hence the a value grows parabolically. The value of a is an angle that defines the step pos will move. It is not easy to predict the behavior of steps when the angle changes parabolically, hence it is hard to imagine how the resultant curve will look. So let's implement the algorithm and see it.

We will use the ofFbo fbo object for holding the generated picture.

Note

This is example 02-2D/06-Spirals.

The example is based on the emptyExample project in openFrameworks. In the testApp class declaration of the testApp.h file, add declarations for a, b, pos, fbo, and some additional variables. Also, we declare the function draw1(), which draws one line segment by performing steps 3 to 7 of the drawing algorithm.

double a, b;           //Angle and its increment
ofPoint pos, lastPos;  //Current and last drawing position
ofColor color;         //Drawing color
int colorStep;         //Counter for color changing
ofFbo fbo;             //Drawing buffer
void draw1();          //Draw one line segment

Note that a and b are declared as double. The reason is that a grows fast, so the accuracy of float is not enough for stable computations. However, we will play with the float case too, in the Playing with numerical instability section.

The testApp::setup() function initializes the fbo buffer, fills it with a white color, and sets initial values to all variables.

void testApp::setup(){
  ofSetFrameRate( 60 );  //Set screen frame rate

  //Allocate drawing buffer
  fbo.allocate( ofGetWidth(), ofGetHeight() );

  //Fill buffer with white color
  fbo.begin();
  ofBackground( 255, 255, 255 );
  fbo.end();

  //Initialize variables
  a = 0;
  b = 0;
  pos = ofPoint( ofGetWidth() / 2, ofGetHeight() / 2 ); 
                                        //Screen center
  colorStep = 0;
}

The testApp::update() function draws line segments in fbo by calling the draw1() function. Note that we perform 200 drawings at once for obtaining the resultant curve quickly.

void testApp::update(){
  fbo.begin();     //Begin draw to buffer
  for ( int i=0; i<200; i++ ) {
    draw1();
  }
  fbo.end();       //End draw to buffer
}

The testApp::draw() function just draws fbo on the screen.

void testApp::draw(){
  ofBackground( 255, 255, 255 );  //Set white background
  
  //Draw buffer
  ofSetColor( 255, 255, 255 );
  fbo.draw( 0, 0 );      
}

Note that calling ofBackground() is not necessary here because fbo fills the whole screen, but we have done so uniformly with other projects.

Finally, we should add a definition for the draw1() function.

void testApp::draw1(){
  //Change a
  a += b * DEG_TO_RAD;
  //a holds values in radians, b holds values in degrees,
  //so when changing a we multiply b to DEG_TO_RAD constant

  //Change b
  b = b + 0.5;

  //Shift pos in direction defined by angle a
  lastPos = pos;    //Store last pos value
  ofPoint d = ofPoint( cos( a ), sin( a ) );
  float len = 20;
  pos += d * len;

  //Change color each 100 steps
  if ( colorStep % 100 == 0 ) {
    //Generate random color
    color = ofColor( ofRandom( 0, 255 ), 
                     ofRandom( 0, 255 ), 
                     ofRandom( 0, 255 ) );
    }
  colorStep++;
    
  //Draw line segment
  ofSetColor( color );
  ofLine( lastPos, pos );
}

In the original algorithm, described at the beginning of the section, a and b are measured in degrees. In the openFrameworks implementation, we decide to hold b in degrees and a in radians. The reason for this will be explained later, in the Playing with numerical instability section. So, in the code, we convert degrees to radians using multiplication to the DEG_TO_RAD constant, which is defined in openFrameworks and is equal to π/180 degrees.

a += b * DEG_TO_RAD;

Run the project; you will see a curve with two spiral ends constantly changing their color:

Spirals example

This particular behavior of the curve is determined by the parameter 0.5 in the following line:

b = b + 0.5;

The parameter defines the speed of increasing b. Change this parameter to 5.4 and 5.5 and you will see curves with 4 and 12 spirals, as shown here:

Spirals example

Try your own values of the parameter. If the resultant curve is too large and does not fit the screen, you can control its scale by changing the len value in the following line:

float len = 20;

For example, if you set len to 10, the resultant curve shrinks twice.

Playing with numerical instability

In the openFrameworks code, we declare a and b as double values. The double type has much more accuracy when representing numbers than float, and it is essential in this example because a grows fast.

But what will happen if we declare a and b as float? Do it! Replace the line double a, b; with float a, b; and run the project. You will see that the resultant curve will be equal to the curve from the double case just in the first second of the running time. Then, the centers of the spirals begin to move.

Playing with numerical instability

Gradually, the two-spiral structure will be ruined and the curve will demonstrate unexpected behavior, drawing circles of different sizes.

Playing with numerical instability

The reason for such instability is that the values of a are computed with numerical inaccuracy.

Note that the exploited instability effect can depend on the floating-point arithmetics of your CPU, so your resultant pictures can differ from the presented screenshots.

Note

In many serious tasks such as physical simulation or optimal planning, we need to have the exact result, so such computing instability is unallowable. But from the creative coding and generative art field point of view, such instability lets you create interesting visual or audio effects. So such instability is often permitted and desirable. For more details on the mathematics of such processes, read about the deterministic chaos theory.

Now change the parameter 0.5 in the line b = b + 0.5; to 17, and you will see a big variety of shapes, including triangles, squares, heptagons, and stars. Then try the values 4, 21, and your own. You will see a large number of similar but different pictures generated by this simple drawing algorithm.

Finally, note that the main computing lines of the algorithm are the following:

a += b * DEG_TO_RAD;
//...
b = b + 0.5;
//...
ofPoint d = ofPoint( cos( a ), sin( a ) );

These are very sensitive to any changes. If you change it somehow, the resultant curves will be different (in the float case). In this sense, such creative coding can be considered art because it depends heavily on the smallest code nuances, which often cannot be predicted.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset