OGRE (Object-Oriented Graphics Rendering Engine) is a scene-oriented, flexible 3D engine written in C++. Designed to quickly and easily produce applications with hardware-accelerated 3D graphics, the class library abstracts all the details of using the underlying system libraries such as Direct3D and OpenGL and provides an interface based on world objects and other intuitive classes. To use OpenGL, OGRE requires a base OpenGL version of 1.2.1 or later, which is quite a modest requirement by today’s standards.
OGRE works with a variety of Windows, Mac OSX, and Linux C++ IDEs, including but not limited to Visual Studio .NET 2003, XCode, GCC 3.3 thru 4.0, Code::Blocks IDE, Dev C++, Cygwin/MinGW, Anjuta, and KDevelop.
OGRE is the brainchild of Steve Streeting, instigator and self-styled “benign dictator of the project. The OGRE team, Streeting with a cadre of a half-dozen developers, insures the consistency and robustness of the design philosophy. Outside developers are encouraged to contribute so long as their code meets the design goals.
The authors are quick to point out that although OGRE is a great graphics library, it is not a standalone game engine; for other features such as sound, networking, AI, collision, physics, and so forth, you need to integrate it with other libraries. However, this ground is well trod and the OGRE FAQ contains many suggestions, such as the Open Dynamics Engine (ODE) for physics/collision support.
The OGRE 3D Scene Manager Approach
OGRE does not assume the type of game or demo you want to make. It uses a flexible class hierarchy that allows you to design plug-ins to specialize the scene organization approach you take. (A scene is an abstract representation of what is shown in a virtual world.) This enables you to make any kind of game you want—from flight simulator to first-person shooter to 2.5D combat.
Scenes may consist of static geometry (such as terrain or building interiors), models (such as trees, chairs, or monsters), light sources that illuminate the scene, and cameras that view the scene. Scenes can have quite different types. An interior scene might be made up of hallways and rooms populated by furniture and artwork. An exterior scene might consist of a terrain of rolling hills, trees, grass waving in the breeze, and a blue sky with moving clouds.
OGRE provides a set of scene managers, each of which is customized to best support a certain kind of scene. For example, the Binary Space Partition (BSP) Scene Manager handles Quake-type level maps for a first-person shooter type of scene, and the Terrain Scene Manager provides vertex program-based morphing between Levels of Detail and fast high-resolution terrain rendering, which could be used for a flight simulator. Of course, you can easily transition between any two scene managers to allow a character to move from a realistic outdoor setting into an underground dungeon, for example (see Figure 1).
Figure 1. The view from the lush, animated adventure game Ankh.
Inside the OGRE 3D Feature Set
Before digging into the API, take a quick glance at the major features OGRE 3D offers:
- Common requirements (such as render state management, hierarchical culling, dealing with transparency, and so on) handled automatically
- Support for vertex and fragment programs (shaders) including Cg, DirectX9 HLSL, or GLSL, and automatic support for many commonly bound constant parameters (such as worldview matrices, light state information, and object space eye position)
- Fixed function operations (such as multitexture and multipass blending, texture coordinate generation and modification, independent color, and alpha operations)
- Multiple pass effects, with pass iteration if required for the closest ‘n’ lights
- Material LOD support (Materials decrease in cost as the objects using them get further away.)
- Load textures from PNG, JPEG, TGA, BMP, or DDS files, including unusual formats like 1D textures, volumetric textures, cube maps, and compressed textures (DXT/S3TC)
- Textures can be provided and updated in realtime by plug-ins; for example, a video feed
- Import meshes from many software packages: MilkShape 3D, 3D Studio Max, Maya, Blender, and Wings3D
- Skeletal animation, including blending of multiple animations, variable bone weight skinning, and hardware-accelerated skinning
- Progressive meshes (LOD)
- Multiple shadow-rendering techniques, each highly configurable and taking full advantage of any hardware acceleration available
- Particle systems: emitters, affectors, and renderers customizable through plug-ins or scripting
- Support for skyboxes, skyplanes, and skydomes—very easy to use
- Billboarding for sprite graphics
- Automatically managed transparent objects (rendering order and depth buffer settings all set up for you)
The Basic Visualization Model of OGRE 3D
The Root object is the entry point to the OGRE system. As such, it must be the first object created and the last one destroyed. An easy way to do this is to aggregate it into your application object. The Root object lets you configure the system via showConfigDialog() to detect all rendering system options and pop up a dialog for the user to customize resolution, color depth, and full-screen options on the spot. It is also the mechanism for obtaining pointers to other objects in the system, such as the SceneManager, RenderSystem, and various other resource managers.
The RenderSystem object is actually an abstract class that defines the interface to the underlying 3D API. It sends rendering operations to the API and sets all the various rendering options. This class is abstract because all the implementation is rendering-API-specific: You actually would use D3DRenderSystem for Direct3D and OpenGLRenderSystem for OpenGL. After doing Root::initialize(), the RenderSystem object for the selected rendering API is available via the Root::getRenderSystem() method. However, most applications will go directly through the SceneManager, Material, and other scene-based classes to render objects and adjust settings.
The SceneManager does the brunt of the work from the application’s point of view. It does all of the following:
- Maintains the contents of the scene that the engine will render
- Organizes the contents using whatever technique it deems best
- Creates and manages all the cameras, movable objects (entities), lights, and materials (surface properties of objects)
- Manages the “world geometry,” all of the static geometry usually used to represent the immovable parts of a scene
- Sends the scene to the RenderSystem object when it’s time to draw
As you can imagine, most of the interaction with the SceneManager is during scene setup. For example, you pull a particular scene of out a database and call all the methods to create the geometry for each object one by one. Sometimes, you may want to create a FrameListener object to modify the contents of the scene dynamically during the rendering cycle. OGRE picks an appropriate SceneManager based on the criteria you specify to Root::setSceneManager. For example, if you tell it you want large indoor levels, it picks the BSP tree SceneManager.
The ResourceManager class is another abstract class specialized to manage specific resources. Typical OGRE resources include textures, meshes, and maps, which correspond to the TextureManager, MeshManager, and MapManager. These classes automatically manage memory and search in multiple locations, including compressed via addPath() and addArchive() methods, respectively.
Looking at OGRE Objects
A Mesh object represents a discrete model or a set of geometry, which is self-contained and is typically fairly small on a world scale. Mesh objects are assumed to represent movable objects as opposed to the static geometry that makes up the background of the level. As you might guess, Mesh objects are managed by the MeshManager and are loaded from the OGRE “.mesh” file format. Many translators are available for importing meshes from popular design packages, though most developers use MilkShape 3D. Mesh objects can also be animated using skeletal animation.
Mesh objects are the templates for Entities: the individual movable objects in the world.
You might have one Mesh of a zombie and then instantiate eight zombie entities from that for a particular room. SceneManager::createEntity() handles the details of naming the mesh and indicating its resource (for example, “zombie.mesh”). To make them appear, you still have to attach them to a SceneNode. By attaching entities to SceneNodes, you can create complex hierarchical relationships between the positions and orientations of entities. You then can modify the positions of the nodes to indirectly affect the entity positions later. This allows the zombie to pick up a chainsaw, for example.
When a Mesh is loaded, it comes with a number of materials defined. A Mesh can have more than one material attached to it (in other words, different parts of the mesh may use different materials). This is because a Mesh is composed of many SubMesh objects, each of which can each have its own material.
The Material object specifies what basic surface properties objects have, such as reflectance of colors, shininess, Goraud shading, texture layers, texture maps, blending, environment mapping, culling mode, and filters. Materials can either be set up programmatically by calling SceneManager::createMaterial() and tweaking the settings, or by specifying it in a dynamically run script.
Overlays and 2D Elements
Overlays allow you to render 2D and 3D elements on top of the normal scene contents to create effects like heads-up displays (HUDs), menu systems, status panels, and frame rates, or just to show you how many zombies you’ve slain. Overlays can contain 2D or 3D elements. While 2D elements are used for HUDs, 3D elements can be used to create realistic cockpit details (see Figure 2). You can create overlays either through the SceneManager::createOverlay(), or you can define them in an overlay script. You can also show multiple overlays at once, and the Overlay::setZOrder() method determines their Z order.
Figure 2. Overlay of a 2D Speedometer on the Game FragFist
The OverlayElement class abstracts the details of 2D elements, which are added to overlays. All items that can be added to overlays are derived from this class. The key common features of all OverlayElements are things like size, position, and basic material name. GuiManager::createOverlayElement() adds a 2D element to an overlay or overlay container. OGRE features an event-based GUI system that allows you to build overlays using buttons, scrollbars, text boxes, and the usual suspects.
Skeletal animation (or “skinning”) occurs by moving a set of hierarchical bones within the mesh, which in turn moves the vertices of the model according to the bone assignments stored in each vertex. These animations are configured in your modeling tool of choice. OGRE supports the following skeletal animation modes:
- One mesh per single skeleton
- Unlimited bones per skeleton
- Hierarchical forward-kinematics on bones
- Multiple named animations per skeleton (for example, “Walk,” “Run,” “Jump,” “Shoot,” and the like)
- Unlimited keyframes per animation
- Linear or spline-based interpolation between keyframes
- A vertex can be assigned to multiple bones and weighted for smoother skinning
- Multiple animations can be combined with a blended weighting
Skeletons and the animations that go with them are held in .skeleton files, which are produced by the OGRE exporters. These files are loaded automatically when you create an Entity based on a Mesh, which is linked to the skeleton in question. The entity is then given an “animation state” object per animation on the skeleton. An entity’s AnimationState object then can be used to control where it is in the loop by calling addTime().
Shadows are an important part of rendering a believable scene. They both provide drama and clarify spatial relationships. Unfortunately, shadows are also one of the most challenging aspects of 3D rendering, and they are still very much an active area of research. Because no single shadow-rendering method is perfect, OGRE provides stencil, texture-based, and modulative shadows. It also supports additive light masking, which builds up light contribution in non-shadowed areas (see Figure 3).
Figure 3. The ShortHike Space Station Simulator
Although shadows are disabled by default, you can activate them through various means:
- Use SceneManager:setShadowTechnique();.
- Create one or more lights (which cast shadows by default).
- Use setCastShadows(false) on objects you don’t want to cast shadows.
- Use SceneManager::setShadowFarDistance to limit the distance at which shadows are considered to improve performance.
- Use Material::setReceiveShadows(false) on self-illuminating objects (you don’t want your torch to be obscured by shadow).
About the Author
Victor Volkman has been writing for C/C++ Users Journal and other programming journals since the late 1980s. He is a graduate of Michigan Tech and a faculty advisor board member for the Washtenaw Community College CIS department. Volkman is the editor of numerous books, including C/C++ Treasure Chest and is the owner of Loving Healing Press. He can help you in your quest for open source tools and libraries, just drop an e-mail to [email protected].