The ofMesh
class is a powerful class that is used for representing, modifying, and rendering 3D objects. By default, it draws triangle meshes, but it can also be used for drawing curves and points.
The ofMesh
class performs rendering of many thousands and even millions of triangles by one OpenGL call, at the highest possible speed. Even though using ofMesh
will at first seem slightly more complicated than using ofTriangle()
, it will give you more flexibility in creating and modifying 3D objects in return. So it is highly recommended that you use ofMesh
for 3D in all cases, except the very beginning or for learning 3D. You can use ofMesh
not only for 3D but for 2D graphics as well.
openFrameworks has one more class, named ofVBOMesh
, that is used for working with meshes. The class name means "mesh based on Vertex Buffer Object (VBO)". This class is similar to ofMesh
, but it renders significantly faster when the vertices of the mesh are not changing. See details of its usage and performance in comparison with ofMesh
in openFrameworks example examples/gl/vboExample
.
To draw a surface consisting of a number of triangles, follow these steps:
mesh
of type ofMesh
in the testApp
class declaration:ofMesh mesh;
mesh.addVertex( p )
function. Note that if a vertex belongs to several triangles, you should specify these vertices just once. This feature is very useful for changing the surface; you change the position of just one vertex, and all the triangles will be drawn correctly.Vertices are added to the end of a special array of vertices in the mesh and are later referenced by indices in this array. So the first vertex has the index 0, the second vertex has the index 1, and so on. For example, to draw a pyramid, we specify its four vertices as follows:
//Pyramid's base vertices with indices 0, 1, 2 mesh.addVertex( ofPoint( -200, -100, -50 ) ); mesh.addVertex( ofPoint( 200, -100, -50 ) ); mesh.addVertex( ofPoint( 0, 200, 0 ) ); //Pyramid's top vertex with index 3 mesh.addVertex( ofPoint( 0, 0, 50 ) );
mesh.addTriangle( index1, index2, index3 )
function. Be careful to order this in the clockwise direction for correct lighting. In our pyramid example, we specify just three of its four triangles, so that you can see the interior of the object.//Vertices with indices 3, 2, 0 mesh.addTriangle( 3, 2, 0 ); //Vertices with indices 3, 1, 2 mesh.addTriangle( 3, 1, 2 ); //Vertices with indices 3, 0, 1 mesh.addTriangle( 3, 0, 1 );
testApp::draw()
function using the mesh.draw()
function. You may need coordinate system transformations for moving and rotating the object. For example, a rotating pyramid can be drawn with the following code in testApp::draw()
:ofEnableDepthTest(); //Enable z-buffering
//Set a background
ofBackgroundGradient( ofColor( 255 ), ofColor( 128 ) );
ofPushMatrix(); //Store the coordinate system
//Move coordinate center to screen's center
ofTranslate( ofGetWidth()/2, ofGetHeight()/2, 0 );
//Rotate the coordinate system
float time = ofGetElapsedTimef(); //Get time in seconds
float angle = time * 30; //Rotate angle
ofRotate( angle, 0, 1, 1 );
ofSetColor( 0, 128, 0 ); //Set a dark green color
mesh.draw(); //Draw the mesh
ofPopMatrix(); //Restore the coordinate system
When you run this code, you will see the pyramid is uniformly colored a dark green color. It looks like some animated 2D polygon and it is hard to make out that this is really a 3D pyramid surface. To see the mesh as a 3D object, you need to enable lighting for the scene and add normals information to the mesh. Let's do it.
Lighting is needed for different parts of the surface to have different shading, depending on their orientation to the viewer. Such shading makes the surfaces look much more interesting than if just rendered with a uniform color because it emphasizes the 3D curvature of the surfaces. openFrameworks has an ofLight
class for controlling light sources.
To use one light source with default parameters, add the following line in the testApp
class declaration:
ofLight light;
Add the following line in the testApp::setup()
function to enable it:
light.enable(); //Enabling light source
For the light to interact with the mesh properly, you need to set up normal vectors for all the vertices using the mesh.addNormal( normal )
function. Each normal vector should have unit length and direction perpendicular to the surface in the vertex. Information about the normals gives openFrameworks information about the correct lighting of the surface. Across the chapter, we will use the setNormals()
function for normals computing, which we will discuss.
To compute normals for a mesh consisting of triangles, you can use the following function:
//Universal function which sets normals for the triangle mesh
void setNormals( ofMesh &mesh ){
//The number of the vertices
int nV = mesh.getNumVertices();
//The number of the triangles
int nT = mesh.getNumIndices() / 3;
vector<ofPoint> norm( nV ); //Array for the normals
//Scan all the triangles. For each triangle add its
//normal to norm's vectors of triangle's vertices
for (int t=0; t<nT; t++) {
//Get indices of the triangle t
int i1 = mesh.getIndex( 3 * t );
int i2 = mesh.getIndex( 3 * t + 1 );
int i3 = mesh.getIndex( 3 * t + 2 );
//Get vertices of the triangle
const ofPoint &v1 = mesh.getVertex( i1 );
const ofPoint &v2 = mesh.getVertex( i2 );
const ofPoint &v3 = mesh.getVertex( i3 );
//Compute the triangle's normal
ofPoint dir = ( (v2 - v1).crossed( v3 - v1 ) ).normalized();
//Accumulate it to norm array for i1, i2, i3
norm[ i1 ] += dir;
norm[ i2 ] += dir;
norm[ i3 ] += dir;
}
//Normalize the normal's length
for (int i=0; i<nV; i++) {
norm[i].normalize();
}
//Set the normals to mesh
mesh.clearNormals();
mesh.addNormals( norm );
}
To use it in your project, insert this function at the end of the testApp.cpp
file, and add its declaration in the testApp.h
file (outside the testApp
class):
//Universal function which sets normals for the triangle mesh void setNormals( ofMesh &mesh );
Now you can call setNormals( mesh )
and the normals will be computed. You need to call the setNormals( mesh )
function after each modification of vertices of mesh
for the normals to be up-to-date.
With lighting and normals, the pyramid looks a little more like a 3D object, which changes its shade depending on its orientation:
Note that the lightness of all the surface triangles mainly depends on the orientation of the central ("top") vertex of the pyramid. The reason is that shading of each triangle is computed by interpolating the normals of its vertices, and in our case, the normal of the central vertex is perpendicular to the pyramid's base. Such an approach works well for drawing smooth surfaces; see the The oscillating plane example section. Although in our case of pyramid, it can look a little bit unnatural.
To obtain the most natural visualization of the pyramid with sharp edges, we need to draw triangles independently without formally creating any common vertices.