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:
ofFbo
object, fbo
, in the testApp
class declaration.ofFbo fbo;
fbo
with some size in the testApp::setup()
function.int w = ofGetWidth(); int h = ofGetHeight(); fbo.allocate( w, h );
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();
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 );
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();
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.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.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.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.
Consider a drawing algorithm consisting of the following steps:
a = 0
and b = 0
.pos
point's position to the screen center.a += b
.b += 0.5
.pos
point a step of fixed length in the direction defined by the angle a
measured in degrees.pos
.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.
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:
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:
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.
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.
Gradually, the two-spiral structure will be ruined and the curve will demonstrate unexpected behavior, drawing circles of different sizes.
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.
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.