3D graphics often looks more impressive than 2D graphics because 3D has unique expressive capabilities, such as depth, perspective, and shading. Also, the third dimension allows objects to interweave and twist in the space in ways that are hard to achieve using 2D graphics. In this chapter we will cover the basics of rendering and animating 3D surfaces and primitive clouds with openFrameworks. We'll cover the following topics:
Working with 3D means working with objects modeled in the three-dimensional scene, where the dimensions are horizontal (x), vertical (y), and depth (z). The resulting 3D scene is projected either onto a 2D image to show it on the screen, two 2D images for stereoscreen, or even printed as a 3D object using a 3D printer.
Each 3D object is represented using a number of elementary primitives such as points, line segments, triangles, or other polygons. Methods of the object's representation are as follows:
These methods refer to realistic representation of real-world objects. We are interested in experimental 3D, so we can play with representations freely. For example:
In openFrameworks, you can represent and draw 3D objects by yourself; see the Simple 3D drawing section. But normally it is preferable to use a powerful ofMesh
class, which lets you represent and draw surfaces, curves, particles, and distinct primitives at the fastest speed; see the Using ofMesh section. Also you can manipulate the static and animated 3D models stored in files such as 3DS; see the Additional topics section.
In this chapter we will consider rendering a 3D scene on a 2D screen (and will not consider stereoscreens and 3D printers).
Recall that, when we draw a flat 2D scene, we just imprint objects such as images and curves onto the screen at the specified coordinates. And the order of the object's drawing defines its visibility; the last object is visible as a whole and can occlude the objects drawn before it.
The rendering of a 3D scene differs from the case of a 2D scene because the object's visibility here is defined by its z coordinate (depth). By default, in openFrameworks, points with a zero value for the z coordinate forms an xy plane, which is used for 2D drawing. Increasing and decreasing the value of the z coordinate leads to moving the objects closer or farther correspondingly.
openFrameworks graphics is based on Open Graphics Library (OpenGL), which renders objects using z-buffering technology. This technology just stores z values for each screen pixel in a special buffer, called z-buffer (or depth buffer). During rendering, if the z value of the object's pixel is greater than the z value in the buffer, the pixel is rendered and the z-buffer is updated to this value. Otherwise, the object's pixel is not rendered.
By default, the z-buffering is disabled. To enable it, call the following function:
ofEnableDepthTest();
When enabled, the z-buffer clears automatically at each frame, together with the background drawing (if you do not call ofSetBackgroundAuto( false )
). To disable z-buffering, use the ofDisableDepthTest()
function.
There is another 3D rendering technology, called ray tracing. Instead of directly projecting the pixels of primitive onto the screen, it simulates light ray propagation from the light sources to the camera. Such a method is a natural way to construct shadows and other natural-world lighting effects. It is used for the highest quality 3D graphics and is available in 3D animation software. But its real-time implementations are currently very resource intensive, and we do not consider them here.
The volumetric nature of the 3D objects introduces new attributes into the 3D scene. These are lights, the object's materials interacting with lights, the 3D scene perspective, and virtual cameras. See the Enabling lighting and setting normals and Additional topics sections for more information.
Note, the modern approach in 3D that includes advanced lighting and shading, object's shape manipulation, and the rendered scene postprocessing requires using shaders; see Chapter 8, Using Shaders, for further details.
openFrameworks is a thin wrapper over OpenGL, so it provides low-level functionality, which is great for working with custom-generated 3D graphics. However, if you need to work with 3D worlds consisting of many life-like models and characters, it is probably better to use some other 3D engine, such as Unity 3D. We use Unity 3D for complex 3D world rendering and add interactivity by controlling it from openFrameworks' project, which processes sensors such as depth cameras. openFrameworks and Unity 3D are connected via OSC network protocol; see Chapter 11, Networking.
Now we will consider a simple 3D drawing example with openFrameworks.