- Mastering openFrameworks:Creative Coding Demystified
- Denis Perevalov
- 1791字
- 2021-08-06 16:54:17
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:
- Declare an
ofFbo
object,fbo
, in thetestApp
class declaration.ofFbo fbo;
- Initialize
fbo
with some size in thetestApp::setup()
function.int w = ofGetWidth(); int h = ofGetHeight(); fbo.allocate( w, h );
- Draw something in
fbo
. You can do it not only intestApp::draw()
but also intestApp::setup()
andtestApp::update()
. To begin drawing, callfbo.begin()
. After this, all drawing commands, such asofBackground()
andofLine()
, will draw tofbo
. To finish drawing, callfbo.end()
. For example, to fillfbo
with white color, use the following code:fbo.begin(); ofBackground( 255, 255, 255 ); fbo.end();
- Draw
fbo
on the screen using thefbo.draw( x, y )
orfbo.draw( x, y, w, h )
functions. Here,x
andy
are the top-left corner, andw
andh
are the optional width and height of the renderedfbo
image on the screen. The drawing should be done in thetestApp::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 typeofTexture
, which holds its current picture. The texture can be accessed usingfbo.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 usingfbo
that is double the size of the screen and shrinkingfbo
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 drawingfbo
itself on the screen. In the opposite case, transparent pixels offbo
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-valuedfbo
by allocating it with the optional last parameterGL_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:
- Set
a = 0
andb = 0
. - Set the
pos
point's position to the screen center. - Set
a += b
. - Set
b += 0.5
. - Move the
pos
point a step of fixed length in the direction defined by the anglea
measured in degrees. - Each 100 steps change the drawing color to a new color, generated randomly.
- Draw a line between the last and current positions of
pos
. - 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:

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.
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.

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.
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.
- Python快樂編程:人工智能深度學習基礎
- Java 開發從入門到精通(第2版)
- Ceph Cookbook
- Cocos2d-x游戲開發:手把手教你Lua語言的編程方法
- C#程序設計教程
- 從學徒到高手:汽車電路識圖、故障檢測與維修技能全圖解
- Spring實戰(第5版)
- Building an RPG with Unity 2018
- 基于ARM Cortex-M4F內核的MSP432 MCU開發實踐
- Learning Node.js for .NET Developers
- Rust游戲開發實戰
- Cocos2d-x Game Development Blueprints
- 算法秘籍
- Java高并發編程詳解:深入理解并發核心庫
- Mapping with ArcGIS Pro