Previous | Home | Up | Next |
A scene graph is a directed acyclic graph (DAG) that consists of a collection of LVT Nodes arranged in a tree structure. A scene graph defines the objects in the 3D scene we wish to draw, and specifies their relationship to each other.
Scene graph Nodes are joined together via special nodes called Groups. A group is simply a collection of other Nodes (often including other Groups). When any action is applied to a Group (such as rendering or picking) that action is passed on to the Group's children in the order in which they were added to the group. Groups (and related Nodes, such as Separators) are the glue which hold scene graphs together.
Let's take a look at how we can construct and display a 3D scene using a scene graph. This first example will display a cube in the center of the screen, and allow us to rotate it, and view it from multiple angles.
Our scene graph will consist of six parts:
Group
node, to contain the whole graph.Camera
object to define the projection of the sceneLight
object to light the sceneTranslation
object to position the cube where we want itExaminer
object to allow us to manipulate a portion of the sceneCube
object, which will draw a cubeAs always, we need to include the appropriate header files and initialize the library:
#include <lvt/lvt.h> using namespace LVT; int main(int argc, char *argv[]) { App myApp; Wnd myWnd; myApp.Init(&argc, &argv); myWnd.Create("Cube", PF_DEFAULT_3D); myWnd.SetClearColor(0.2f, 0.2f, 0.2f);
Notice that our call to Wnd::Create
is
slightly different. As a second parameter, Wnd::Create
takes a pixel format which tells LVT what type of framebuffer to request
from the windowing system. The default value is PF_DEFAULT
, which is used for
2D applications like we have been creating so far. By contrast, PF_DEFAULT_3D
initializes
a z-buffer for depth testing, and a back buffer for smooth drawing. If you are unfamiliar with
the different parts of the OpenGL framebuffer, you should consult the OpenGL Red Book for information.
In most cases, PF_DEFAULT_3D
will suffice.
LVT Also allows for more fine-grained control over the attributes of the framebuffer. Consult the LVT class reference for more details.
Wnd::SetClearColor
sets the background color
of our window. In OpenGL terms, the default handler of Wnd::OnPaint
clears the framebuffer to the color specified by SetClearColor
.
Now that initialization is complete, we are ready to begin constructing our scene graph. We begin with
a Group
which will be the root of our scene graph:
Group *root = new Group;
That done, we're ready to add nodes to the scene graph.
PerspectiveCamera *cam = new PerspectiveCamera; root->AddChild(cam);
One of the first things usually done is to attach a Camera
object
to the scene graph. In this case, we're using a PerspectiveCamera
to set up a perspective projection. If we so chose, we could have used an OrthographicCamera
to define an orthographic projection.
Once the camera is created, we add it to the scenegraph by calling Group::AddChild
.
Light *light = new Light; light->SetColor(1.0f, 1.0f, 1.0f); root->AddChild(light);
We next attach a Light
object to our root node to light the other
objects in the scene graph. Remember that scene graphs are traversed depth-first, left-to-right, so any object that affects
other objects (such as lights, cameras, materials, and so forth) will affect any object attached after it. Referring back
to the diagram above, this light object will affect the three nodes that follow it.
root->AddChild(new Translation(0.0, 0.0, -5.0f));
Scene graph nodes are defined in their own local coordinate system, thus we make use of transformation objects to position them in world space. In this case, we push any following nodes out five units along the negative z-axis.
Examiner *exm = new Examiner; root->AddChild(exm);
An Examiner
translates mouse movement into a rotation. This allows us
to view objects at any angle by dragging the left mouse button.
root->AddChild(new Cube);
And finally, we attach a Cube
object, which not surprisingly,
draws a cube on the screen. And with that, our scene graph is complete. Now we have some final maintenance to
perform before we can start viewing it:
myWnd.AdjustCamera(cam); myWnd.SetInputHandler(exm); myWnd.ShowScene(root);
We often want to keep the aspect ratio of a Camera
object
the same as that of the window in which we're viewing a scene. In this way, we avoid any stretching or distortion
of the scene graph objects. Wnd::AdjustCamera
takes care of that
for us. When the Wnd
object is resized, it will resize the
aspect ratio of the Camera
object passed to it to match.
In order for our Examiner
object to work, we need some way
of informing it about mouse movement. One way to do this would be to override the OnLButtonDown
,
OnLButtonUp
, and OnMouseMove
messages and manually forward them to the
Examiner
. A cleaner way, however, is to inform the
Wnd
object to forward all of its input messages to an
InputHandler
class. An InputHandler
(from which Examiner
is derived) accepts input from a
Wnd
object and processes any input messages it understands. By
attaching the Examiner
to our Wnd
object in this way, we don't have to worry about manually forwarding mouse messages to it.
Finally, we tell the Wnd
object that we want it to display
our scene graph whenever the window needs repainted. We do this by simply passing the root node in to a call to
Wnd::ShowScene
. (The alternative would be to override the
Wnd::OnPaint
message and manually call the Render
method of the root node).
And that's all we need. We start the message loop as usual:
return myApp.Run(myWnd); }
Try compiling the cube.cpp
example program in the LVT source distribution to see what
this code produces.
In the next section, we'll look at more intricate scene graph examples, and learn more about how the library manages scene graphs internally.
Previous | Home | Up | Next |