For simple 3D drawing in openFrameworks, follow these steps:
ofEnableDepthTest()
function call in the beginning of the testApp::draw()
function to enable z-buffering. If you omit it, all the graphics objects will be rendered without respect to their z coordinate in correspondence with the graphical primitives' rendering order.ofLine( x1, y1, z1, x2, y2, z2 )
function draws a line segment between points (x1
, y1
, z1
) and (x2
, y2
, z2
). There is an overloaded version of the function, ofLine( p1, p2 )
, where p1
and p2
have type ofPoint
. Use the ofSetColor()
and ofSetLineWidth()
functions to adjust its rendering properties of color and line width.In Chapter 2, Drawing in 2D, we used the ofPoint
class to represent 2D points using its fields x
and y
. Actually, ofPoint
has a third field z
, which, by default, is equal to zero. So ofPoint
can represent points in 3D. Just declare ofPoint p
and work with values p.x
, p.y
, and p.z
.
ofTriangle( p1, p2, p3 )
function draws a triangle with vertices in points p1
, p2
, and p3
. Use the ofSetColor()
, ofFill()
, ofSetLineWidth()
, and ofNoFill()
functions to adjust its rendering properties.ofRect( x, y, z, w, h )
function draws a rectangle with the top-left corner at (x, y, z)
and the width w
and height h
, oriented parallel to the screen plane. If you need to get a rotated rectangle, you need to rotate the coordinate system using the ofRotate()
function.ofBeginShape(); //Begin shape ofVertex( x1, y1, z1 ); //The first vertex ofVertex( x2, y2, z2 ); //The second vertex //... ofVertex( xn, yn, zn ); //The last vertex ofEndShape(); //End shape
To draw arbitrary polygons—for example, quadrangles—use the following method:
If ofFill()
was called before drawing, the shape will be drawn filled and closed. If ofNoFill()
was called before drawing, just an unclosed polygon will be drawn.
As in a 2D case, use ofPushMatrix()
and ofPopMatrix()
to store and retrieve the current coordinate system in a matrix stack.
Now we will illustrate these steps in an example.
Let's draw 1500 random triangles, located at an equal distance from the center of the coordinates. This will look like a triangle cloud in the shape of a sphere. To make the visualization more interesting, colorize the triangles with random colors from black to red and add constant rotation to the cloud.
The example is based on the emptyExample
project in openFrameworks. In the testApp.h
file, inside the testApp
class declaration, add arrays vertices
and colors
to hold the vertices and the colors of the triangles and variables nTri
and nVert
corresponding to the number of triangles and their vertices:
vector<ofPoint> vertices; vector<ofColor> colors; int nTri; //The number of triangles int nVert; //The number of the vertices equals nTri * 3
The setup()
function fills the arrays for the triangles' vertices and colors. The vertices of the first triangle are stored in vertices[0]
, vertices[1]
, and vertices[2]
. The vertices of the second triangle are stored in vertices[3]
, vertices[4]
, vertices[5]
, and so on. In general, the vertices of the triangle with index i (where i is in range from 0 to N-1) are stored in the vertices
with the indices i * 3
, i * 3 + 1
, and i * 3 + 2
.
void testApp::setup() { nTri = 1500; //The number of the triangles nVert= nTri * 3; //The number of the vertices float Rad = 250; //The sphere's radius float rad = 25; //Maximal triangle's "radius" //(formally, it's the maximal coordinates' //deviation from the triangle's center) //Fill the vertices array vertices.resize( nVert ); //Set the array size for (int i=0; i<nTri; i++) { //Scan all the triangles //Generate the center of the triangle //as a random point on the sphere //Take the random point from //cube [-1,1]x[-1,1]x[-1,1] ofPoint center( ofRandom( -1, 1 ), ofRandom( -1, 1 ), ofRandom( -1, 1 ) ); center.normalize(); //Normalize vector's length to 1 center *= Rad; //Now the center vector has //length Rad //Generate the triangle's vertices //as the center plus random point from //[-rad, rad]x[-rad, rad]x[-rad, rad] for (int j=0; j<3; j++) { vertices[ i*3 + j ] = center + ofPoint( ofRandom( -rad, rad ), ofRandom( -rad, rad ), ofRandom( -rad, rad ) ); } } //Fill the array of triangles' colors colors.resize( nTri ); for (int i=0; i<nTri; i++) { //Take a random color from black to red colors[i] = ofColor( ofRandom( 0, 255 ), 0, 0 ); } }
The update()
function is empty here, and the draw()
function enables z-buffering, which rotates the coordinate system based on time, and draws the triangles with the specified colors.
void testApp::draw(){ ofEnableDepthTest(); //Enable z-buffering //Set a gradient background from white to gray //for adding an illusion of visual depth to the scene ofBackgroundGradient( ofColor( 255 ), ofColor( 128 ) ); ofPushMatrix(); //Store the coordinate system //Move the coordinate center to screen's center ofTranslate( ofGetWidth()/2, ofGetHeight()/2, 0 ); //Calculate the rotation angle float time = ofGetElapsedTimef(); //Get time in seconds float angle = time * 10; //Compute angle. We rotate at speed //10 degrees per second ofRotate( angle, 0, 1, 0 ); //Rotate the coordinate system //along y-axe //Draw the triangles for (int i=0; i<nTri; i++) { ofSetColor( colors[i] ); //Set color ofTriangle( vertices[ i*3 ], vertices[ i*3 + 1 ], vertices[ i*3 + 2 ] ); //Draw triangle } ofPopMatrix(); //Restore the coordinate system }
Run the code and you will see a sphere-like rotating cloud of triangles as shown in the following screenshot:
To draw the background, we use the ofBackgroundGradient( color1, color2, type )
function. It creates the gradient filling of type type
for the entire application's screen, with colors interpolated from color1
to color2
. The possible values of type
are as follows:
Note that each triangle moves and rotates on the screen but its color always remains unchanged. The reason for this is that we don't use light and normals, which control how a graphics primitive is lit and shaded.
The simplest way to add lighting and normals is using the ofMesh
class, which we will consider now.