Ogre 3D: Double Buffering

Exclusive offer: get 50% off this eBook here
OGRE 3D 1.7 Beginner's Guide

OGRE 3D 1.7 Beginner's Guide — Save 50%

Create real time 3D applications using OGRE 3D from scratch

$26.99    $13.50
by Felix Kerger | November 2010 | Beginner's Guides Open Source

This article, by Felix Kerger, author of Ogre 3D 1.7, is a continuation of the previous article Starting 3D, in which we learned various startup features of Ogre 3D.

In this article we will learn about double buffering in Ogre 3D, in which we will cover:

  • Adding input
  • Our own main loop
  • Adding a camera
  • Adding compositors
  • Adding a plane
  • Adding user control
  • Adding animation

 

OGRE 3D 1.7 Beginner's Guide

OGRE 3D 1.7 Beginner's Guide

Create real time 3D applications using OGRE 3D from scratch

  • Easy-to-follow introduction to OGRE 3D
  • Create exciting 3D applications using OGRE 3D
  • Create your own scenes and monsters, play with the lights and shadows, and learn to use plugins
  • Get challenged to be creative and make fun and addictive games on your own
  • A hands-on do-it-yourself approach with over 100 examples

Images

        Read more about this book      

(For more resources on this subject, see here.)

Introduction

When a scene is rendered, it isn't normally rendered directly to the buffer, which is displayed on the monitor. Normally, the scene is rendered to a second buffer and when the rendering is finished, the buffers are swapped. This is done to prevent some artifacts, which can be created if we render to the same buffer, which is displayed on the monitor. The FrameListener function, frameRenderingQueued, is called after the scene has been rendered to the back buffer, the buffer which isn't displayed at the moment. Before the buffers are swapped, the rendering result is already created but not yet displayed. Directly after the frameRenderingQueued function is called, the buffers get swapped and then the application gets the return value and closes itself. That's the reason why we see an image this time.

Now, we will see what happens when frameRenderingQueued also returns true.

Time for action – returning true in the frameRenderingQueued function

Once again we modify the code to test the behavior of the Frame Listener

  1. Change frameRenderingQueued to return true:

    bool frameRenderingQueued (const Ogre::FrameEvent& evt)
    {
    std::cout << «Frame queued» << std::endl;
    return true;
    }

  2. Compile and run the application. You should see Sinbad for a short period of time before the application closes, and the following three lines should be in the console output:
    Frame started
    Frame queued
    Frame ended

What just happened?

Now that the frameRenderingQueued handler returns true, it will let Ogre 3D continue to render until the frameEnded handler returns false.

Like in the last example, the render buffers were swapped, so we saw the scene for a short period of time. After the frame was rendered, the frameEnded function returned false, which closes the application and, in this case, doesn't change anything from our perspective.

Time for action – returning true in the frameEnded function

Now let's test the last of three possibilities.

  1. Change frameRenderingQueued to return true:

    bool frameEnded (const Ogre::FrameEvent& evt)
    {
    std::cout << «Frame ended» << std::endl;
    return true;
    }

  2. Compile and run the application. You should see the scene with Sinbad and an endless repetition of the following three lines:
    Frame started
    Frame queued
    Frame ended

What just happened?

Now, all event handlers returned true and, therefore, the application will never be closed; it would run forever as long as we aren't going to close the application ourselves.

Adding input

We have an application running forever and have to force it to close; that's not neat. Let's add input and the possibility to close the application by pressing Escape.

Time for action – adding input

Now that we know how the FrameListener works, let's add some input.

  1. We need to include the OIS header file to use OIS:

    #include "OIS\OIS.h"

  2. Remove all functions from the FrameListener and add two private members to store the InputManager and the Keyboard:

    OIS::InputManager* _InputManager;
    OIS::Keyboard* _Keyboard;

  3. The FrameListener needs a pointer to the RenderWindow to initialize OIS, so we need a constructor, which takes the window as a parameter:

    MyFrameListener(Ogre::RenderWindow* win)
    {

  4. OIS will be initialized using a list of parameters, we also need a window handle in string form for the parameter list; create the three needed variables to store the data:

    OIS::ParamList parameters;
    unsigned int windowHandle = 0;
    std::ostringstream windowHandleString;

  5. Get the handle of the RenderWindow and convert it into a string:

    win->getCustomAttribute("WINDOW", &windowHandle);
    windowHandleString << windowHandle;

  6. Add the string containing the window handle to the parameter list using the key "WINDOW":

    parameters.insert(std::make_pair("WINDOW", windowHandleString.
    str()));

  7. Use the parameter list to create the InputManager:

    _InputManager = OIS::InputManager::createInputSystem(parameters);

  8. With the manager create the keyboard:

    _Keyboard = static_cast<OIS::Keyboard*>(_InputManager-
    >createInputObject( OIS::OISKeyboard, false ));

  9. What we created in the constructor, we need to destroy in the destructor:

    ~MyFrameListener()
    {
    _InputManager->destroyInputObject(_Keyboard);
    OIS::InputManager::destroyInputSystem(_InputManager);
    }

  10. Create a new frameStarted function, which captures the current state of the keyboard, and if Escape is pressed, it returns false; otherwise, it returns true:

    bool frameStarted(const Ogre::FrameEvent& evt)
    {
    _Keyboard->capture();
    if(_Keyboard->isKeyDown(OIS::KC_ESCAPE))
    {
    return false;
    }
    return true;
    }

  11. The last thing to do is to change the instantiation of the FrameListener to use a pointer to the render window in the startup function:

    _listener = new MyFrameListener(window);
    _root->addFrameListener(_listener);

  12. Compile and run the application. You should see the scene and now be able to close it by pressing the Escape key.

What just happened?

We added input processing capabilities to our FrameListener but we didn't use any example classes, except our own versions.

OGRE 3D 1.7 Beginner's Guide Create real time 3D applications using OGRE 3D from scratch
Published: November 2010
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on this subject, see here.)

Pop quiz – the three event handlers

Which three functions offer the FrameListener interface and at which point is each of these functions called?

Our own main loop

We have used the startRendering function to fire up our application. After this, the only way we knew when a frame was rendered was by relying on the FrameListener. But sometimes it is not possible or desirable to give up the control over the main loop; for such cases, Ogre 3D provides another method, which doesn't require us to give up the control over the main loop.

Time for action – using our own rendering loop

Using the code from before we now are going to use our own rendering loop.

  1. Our application needs to know if it should keep running or not; add a Boolean as a private member of the application to remember the state:

    bool _keepRunning;

  2. Remove the startRendering function call in the startup function.
  3. Add a new function called renderOneFrame, which calls the renderOneFrame function of the root instance and saves the return value in the _keepRunning member variable. Before this call, add a function to process all window events:

    void renderOneFrame()
    {
    Ogre::WindowEventUtilities::messagePump();
    _keepRunning = _root->renderOneFrame();
    }

  4. Add a getter for the _keepRunning member variable:

    bool keepRunning()
    {
    return _keepRunning;
    }

  5. Add a while loop to the main function, which keeps running as long as the keepRunning function returns true. In the body of the loop, call the renderOneFrame function of the application.

    while(app.keepRunning())
    {
    app.renderOneFrame();
    }

  6. Compile and run the application. There shouldn't be any noticeable difference to the last example.

What just happened?

We moved the control of the main loop from Ogre 3D to our application. Before this change, Ogre 3D used an internal main loop over which we hadn't any control and had to rely on the FrameListener to get notified if a frame was rendered.

Now we have our own main loop. To get there, we needed a Boolean member variable, which signals if the application wishes to keep running or not; this variable was added in step 1. Step 2 removed the startRendering function call so we wouldn't hand over the control to Ogre 3D. In step 3, we created a function, which first calls a helper function of Ogre 3D, which processes all window events we might have gotten from the operating system. It then sends all messages we might have created since the last frame, and therefore makes the application "well-behaved" in the context of the host windowing system.

After this we call the Ogre 3D function renderOneFrame, which does exactly what the name suggests: it renders the frame and also calls the frameStarted, frameRenderingQueued, and frameEnded event handler of each registered FrameListener and returns false if any of these functions returned false. Since we assign the return value of the function to the _keepRunning member variable, we can use this variable to check if the application should keep running. When renderOneFrame returns a false, we know some FrameListener wants to close the application and we set the _keepRunning variable to false. The fourth step just added a getter for the _keepRunning member variable.

In step 5, we used the _keepRunning variable as the condition for the while loop. This means the while loop will run as long as _keepRunning is true, which will be the case until one FrameListener returns false, which then will result in the while loop to exit and with this the whole application will be closed. Inside the while loop we call the renderOneFrame function of the application to update the render window with the newest render result. This is all we needed to create our own main loop.

Adding a camera

We want a controllable camera in our own implementation of the frame listener, so here we go.

Time for action – adding a frame listener

Using our FrameListener we are going to add a user controlled camera.

  1. To control the camera we need a mouse interface, a pointer to the camera, and a variable defining the speed at which our camera should move as a member variable of our FrameListener:

    OIS::Mouse* _Mouse;
    Ogre::Camera* _Cam;
    float _movementspeed;

  2. Adjust the constructor and add the camera pointer as the new parameter and set the movement speed to 50:

    MyFrameListener(Ogre::RenderWindow* win,Ogre::Camera* cam)
    {
    _Cam = cam;
    _movementspeed = 50.0f;

  3. Init the mouse using the InputManager:

    _Mouse = static_cast<OIS::Mouse*>(_InputManager-
    >createInputObject( OIS::OISMouse, false ));

  4. And remember to destroy it in the destructor:

    _InputManager->destroyInputObject(_Mouse);

  5. Add the code to move the camera using the W, A, S, D keys and the movement speed to the frameStarted event handler:

    Ogre::Vector3 translate(0,0,0);
    if(_Keyboard->isKeyDown(OIS::KC_W))
    {
    translate += Ogre::Vector3(0,0,-1);
    }
    if(_Keyboard->isKeyDown(OIS::KC_S))
    {
    translate += Ogre::Vector3(0,0,1);
    }
    if(_Keyboard->isKeyDown(OIS::KC_A))
    {
    translate += Ogre::Vector3(-1,0,0);
    }
    if(_Keyboard->isKeyDown(OIS::KC_D))
    {
    translate += Ogre::Vector3(1,0,0);
    }
    _Cam->moveRelative(translate*evt.timeSinceLastFrame * _
    movementspeed);

  6. Now do the same for the mouse control:

    _Mouse->capture();
    float rotX = _Mouse->getMouseState().X.rel * evt.
    timeSinceLastFrame* -1;
    float rotY = _Mouse->getMouseState().Y.rel * evt.
    timeSinceLastFrame * -1;
    _Cam->yaw(Ogre::Radian(rotX));
    _Cam->pitch(Ogre::Radian(rotY));

  7. The last thing to do is to change the instantiation of the FrameListener:

    _listener = new MyFrameListener(window,camera);

  8. Compile and run the application. The scene should be unchanged but now we can control the camera:

What just happened?

We used our knowledge from to add a user-controlled camera. The next step will be to add compositors and other features to make our application more interesting and to leverage some of the techniques we learned along the way.

Adding compositors

Previously, we have created three compositors, which we are now going to add to our application with the capability to turn each one off and on using keyboard input.

Time for action – adding compositors

Having almost finished our application, we are going to add compositors to make the application more interesting.

  1. We are going to use compositors in our FrameListener, so we need a member variable containing the viewport:

    Ogre::Viewport* _viewport;

  2. We also are going to need to save which compositor is turned on; add three Booleans for this task:

    bool _comp1, _comp2, _comp3;

  3. We are going to use keyboard input to switch the compositors on and off. To be able to differentiate between key presses, we need to know the previous state of the key:

    bool _down1, _down2, _down3;

  4. Change the constructor of the FrameListener to take the viewport as a parameter:

    MyFrameListener(Ogre::RenderWindow* win,Ogre::Camera*
    cam,Ogre::Viewport* viewport)

  5. Assign the viewport pointer to the member and assign the Boolean value their starting value:

    _viewport = viewport;

    _comp1 = false;
    _comp2 = false;
    _comp3 = false;

    _down1 = false;
    _down2 = false;
    _down3 = false;

  6. If the key number 1 is pressed and it wasn't pressed before, change the state of the key to pressed, flip the state of the compositor, and use the flipped value to enable or disable the compositor. This code goes into the frameStarted function:

    if(_Keyboard->isKeyDown(OIS::KC_1) && ! _down1)
    {
    _down1 = true;
    _comp1 = !comp1;
    Ogre::CompositorManager::getSingleton().setCompositorEnabled(_
    viewport, "Compositor2", _comp1);
    }

  7. Do the same for the other two compositors we are going to have:

    if(_Keyboard->isKeyDown(OIS::KC_2) && ! _down2)
    {
    _down2 = true;
    _comp2 = !comp2;
    Ogre::CompositorManager::getSingleton().setCompositorEnabled(_
    viewport, "Compositor3", _comp2);
    }
    if(_Keyboard->isKeyDown(OIS::KC_3) && ! _down3)
    {
    _down3 = true;
    _comp3 = !comp3;
    Ogre::CompositorManager::getSingleton().setCompositorEnabled(_
    viewport, "Compositor7", _comp3);
    }

  8. If a key is no longer pressed, we need to change the state of the key:

    if(!_Keyboard->isKeyDown(OIS::KC_1))
    {
    _down1 = false;
    }
    if(!_Keyboard->isKeyDown(OIS::KC_2))
    {
    _down2 = false;
    }
    if(!_Keyboard->isKeyDown(OIS::KC_3))
    {
    _down3 = false;
    }

  9. In the startup() function, add the three compositors to the viewport to the end of the function:

    Ogre::CompositorManager::getSingleton().addCompositor(viewport,
    "Compositor2");
    Ogre::CompositorManager::getSingleton().addCompositor(viewport,
    "Compositor3");
    Ogre::CompositorManager::getSingleton().addCompositor(viewport,
    "Compositor7");

  10. Remember to change the instantiation of the FrameListener to add the viewport pointer as parameter:

    _listener = new MyFrameListener(window,camera,viewport);

  11. Compile and run the application. Using the 1, 2, 3 keys, you should be able to turn different compositors on and off. The 1 key is for making the image black and white, the 2 key inverts the image, and the 3 key makes the image look like it has a smaller resolution; you can combine all of the effect the way you like:

What just happened?

We added the compositors we wrote in the article about and made it possible to turn them on and off using the 1, 2, and 3 keys. To combine the compositors, we used the fact that Ogre 3D automatically chains compositors if more than one is enabled.

OGRE 3D 1.7 Beginner's Guide Create real time 3D applications using OGRE 3D from scratch
Published: November 2010
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on this subject, see here.)

Adding a plane

Without a reference to where the ground is, navigation in 3D space is difficult, so once again let's add a floor plane.

Time for action – adding a plane and a light

Everything we are going to add this time is going in the createScene() function:

  1. As we already know we need a plane definition, so add one:

    Ogre::Plane plane(Ogre::Vector3::UNIT_Y, -5);
    Ogre::MeshManager::getSingleton().createPlane("plane",
    Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane,
    1500,1500,200,200,true,1,5,5,Ogre::Vector3::UNIT_Z);

  2. Then create an instance of this plane, add it to the scene, and change the material:

    Ogre::Entity* ground= _sceneManager->createEntity("LightPlaneEnti
    ty", "plane");
    _sceneManager->getRootSceneNode()->createChildSceneNode()-
    >attachObject(ground);
    ground->setMaterialName("Examples/BeachStones");

  3. Also we would like to have some light in the scene; add one directional light:

    Ogre::Light* light = _sceneManager->createLight("Light1");
    light->setType(Ogre::Light::LT_DIRECTIONAL);
    light->setDirection(Ogre::Vector3(1,-1,0));

  4. And some shadows would be nice:

    _sceneManager->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_
    ADDITIVE);

  5. Compile and run the application. You should see a plane with a stone texture and on top the Sinbad instance throwing a shadow on the plane.

What just happened?

Again, we used our previously gained knowledge to create a plane, light, and add shadows to the scene.

Adding user control

We have our model instance on a plane, but we can't move it yet; let's change this now.

Time for action – controlling the model with the arrow keys

Now we are going to add interactivity to the scene by adding the user control to the movements of the model.

  1. The FrameListener needs two new members: one pointer to the node we want to move, and one float indicating the movement speed:

    float _WalkingSpeed;
    Ogre::SceneNode* _node;

  2. The pointer to the node is passed to us in the constructor:

    MyFrameListener(Ogre::RenderWindow* win,Ogre::Camera*
    cam,Ogre::Viewport* viewport,Ogre::SceneNode* node)

  3. Assign the node pointer to the member variable and set the walking speed to 50:

    _WalkingSpeed = 50.0f;
    _node = node;

  4. In the frameStarted function we need two new variables, which will hold the rotation and the translation the user wants to apply to the node:

    Ogre::Vector3 SinbadTranslate(0,0,0);
    float _rotation = 0.0f;

  5. Then we need code to calculate the translation and rotation depending on which arrow key the user has pressed:

    if(_Keyboard->isKeyDown(OIS::KC_UP))
    {
    SinbadTranslate += Ogre::Vector3(0,0,-1);
    _rotation = 3.14f;
    }
    if(_Keyboard->isKeyDown(OIS::KC_DOWN))
    {
    SinbadTranslate += Ogre::Vector3(0,0,1);
    _rotation = 0.0f;
    }
    if(_Keyboard->isKeyDown(OIS::KC_LEFT))
    {
    SinbadTranslate += Ogre::Vector3(-1,0,0);
    _rotation = -1.57f;
    }
    if(_Keyboard->isKeyDown(OIS::KC_RIGHT))
    {
    SinbadTranslate += Ogre::Vector3(1,0,0);
    _rotation = 1.57f;
    }

  6. Then we need to apply the translation and rotation to the node:

    _node->translate(SinbadTranslate * evt.timeSinceLastFrame * _
    WalkingSpeed);
    _node->resetOrientation();
    _node->yaw(Ogre::Radian(_rotation));

  7. The application itself also needs to store the node pointer of the entity we want to control:

    Ogre::SceneNode* _SinbadNode;

  8. The FrameListener instantiation needs this pointer:

    _listener = new MyFrameListener(window,camera,viewport,_
    SinbadNode);

  9. And the createScene function needs to use this pointer to create and store the node of the entity we want to move; modify the code in the function accordingly:

    _SinbadNode = _sceneManager->getRootSceneNode()-
    >createChildSceneNode();
    _SinbadNode->attachObject(sinbadEnt);

  10. Compile and run the application. You should be able to move the entity with the arrow keys:

What just happened?

We added entity movement using the arrow keys in the FrameListener. Now our entity floats over the plane like a wizard.

Adding animation

Floating isn't exactly what we wanted; let's add some animation.

Time for action – adding animation

Our model can move but it isn't animated yet, let's change this.

  1. The FrameListener needs two animation states:

    Ogre::AnimationState* _aniState;
    Ogre::AnimationState* _aniStateTop;

  2. To get the animation states in the constructor, we need a pointer to the entity:

    MyFrameListener(Ogre::RenderWindow* win,Ogre::Camera*
    cam,Ogre::Viewport* viewport,Ogre::SceneNode* node,Ogre::Entity*
    ent)

  3. With this pointer we can retrieve the AnimationState and save them for later use:

    _aniState = ent->getAnimationState("RunBase");
    _aniState->setLoop(false);

    _aniStateTop = ent->getAnimationState(«RunTop»);
    _aniStateTop->setLoop(false);

  4. Now that we have the AnimationState, we need to have a flag in the frameStarted function, which tells us whether or not the entity walked this frame. We add this flag into the if conditions that query the keyboard state:

    bool walked = false;
    if(_Keyboard->isKeyDown(OIS::KC_UP))
    {
    SinbadTranslate += Ogre::Vector3(0,0,-1);
    _rotation = 3.14f;
    walked = true;
    }
    if(_Keyboard->isKeyDown(OIS::KC_DOWN))
    {
    SinbadTranslate += Ogre::Vector3(0,0,1);
    _rotation = 0.0f;
    walked = true;
    }
    if(_Keyboard->isKeyDown(OIS::KC_LEFT))
    {
    SinbadTranslate += Ogre::Vector3(-1,0,0);
    _rotation = -1.57f;
    walked = true;
    }
    if(_Keyboard->isKeyDown(OIS::KC_RIGHT))
    {
    SinbadTranslate += Ogre::Vector3(1,0,0);
    _rotation = 1.57f;
    walked = true;
    }

  5. If the model moves, we enable the animation; if the animation has ended, we loop it:

    if(walked)
    {
    _aniState->setEnabled(true);
    _aniStateTop->setEnabled(true);
    if(_aniState->hasEnded())
    {
    _aniState->setTimePosition(0.0f);
    }
    if(_aniStateTop->hasEnded())
    {
    _aniStateTop->setTimePosition(0.0f);
    }
    }

  6. If the model didn't move, we disable the animation and set it to the start position:

    else
    {
    _aniState->setTimePosition(0.0f);
    _aniState->setEnabled(false);
    _aniStateTop->setTimePosition(0.0f);
    _aniStateTop->setEnabled(false);
    }

  7. In each frame, we need to add the passed time to the animation; otherwise, it wouldn't move:

    _aniState->addTime(evt.timeSinceLastFrame);
    _aniStateTop->addTime(evt.timeSinceLastFrame);

  8. The application now also needs a pointer to the entity:

    Ogre::Entity* _SinbadEnt;

  9. We use this pointer while instantiating the FrameListener:

    _listener = new MyFrameListener(window,camera,viewport,_
    SinbadNode,_SinbadEnt);

  10. And, of course, while creating the entity:

    _SinbadEnt = _sceneManager->createEntity("Sinbad.mesh");

  11. Compile and run the application. Now the model should be animated when it moves:

What just happened?

We added animation to our model, which is only enabled when the model is moved.

Summary

In this article we learned:

  • Adding input
  • Our own main loop
  • Adding a camera
  • Adding compositors
  • Adding a plane
  • Adding user control
  • Adding animation

Further resources on this subject:


About the Author :


Felix Kerger

Felix Kerger is a Computer Science Student at the Technical University of Darmstadt and has been developing 3D real-time applications using OGRE 3D for more than 5 years. He has given several talks on software development and 3D real-time applications at different conferences and has been working for three years as an assistant researcher at the Fraunhofer Institute for Computer Graphics Research. He also works as a freelance journalist and reports yearly from the Game Developer Conference Europe.

Books From Packt


Blender 3D 2.49 Architecture, Buildings, and Scenery
Blender 3D 2.49 Architecture, Buildings, and Scenery

Unity 3D Game Development by Example Beginner's Guide
Unity 3D Game Development by Example Beginner's Guide

3D Graphics with XNA Game Studio 4.0
3D Graphics with XNA Game Studio 4.0

Papervision3D Essentials
Papervision3D Essentials

3D Game Development with Microsoft Silverlight 3: Beginner's Guide
3D Game Development with Microsoft Silverlight 3: Beginner's Guide

Blender 3D 2.49 Incredible Machines
Blender 3D 2.49 Incredible Machines

Irrlicht 1.7.1 Realtime 3D Engine Beginner's Guide: RAW
Irrlicht 1.7.1 Realtime 3D Engine Beginner's Guide: RAW

Away3D 3.6 Essentials
Away3D 3.6 Essentials


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software