Table of Contents

LAB6: Scene Graph

This lab is based on “Chapter 6: Scene Managers” in the book “Ogre 3d 1.7 Beginner's Guide” by Felix Kerger.

Discussion

Discussion thread for this lab is here: Lab 6 Discussion Thread

Goal

The primary goal of this lab is to see how the representation of geometry in a scene can affect rendering performance. In particular, a scene where everything is represented as a movable object can get super heavy. Instead, you can “bake” meshes that won't move into static batches, which get optimized for rendering performance.

Preparation

Continue with your project or create a new project from the template, you need to be able to move the camera.

Lab Project

Follow these steps to complete the lab project:

  1. Create a New Project Create a new empty project called “Lab6” in the same way you have created new projects for other lab projects.
  2. Create a Manual Grass Object You will use the createManualObject() method in Ogre to specify new geometry inside your code representing a single bundle of grass straws. This method is handy for simple geometries or when you want to procedurally generate them. The grass bundle you create is essentially a set of three quads, each at a 60 degree angle to the next, and that all crossing in the middle - producing a star with 6 spikes if viewed from the top. If viewed from the side, you always see the front of one or two quads, regardless of where you stand. You can therefore make this object hold a texture that is visible from all directions, a property that is called billboarding. To get started, create a new method in your MyApplication class which you should call createGrass(). Inside this method construct the quads and assign grass tecture to them. You construct them by specifying all the vertices first (in model space) and then the indices of each triangle that make up the three quads. Here is the code with the initialization of the manual object and the first quad specified:
    		Ogre::ManualObject* manual = _sceneManager->createManualObject("grass");
    		manual->begin("Examples/GrassBlades", Ogre::RenderOperation::OT_TRIANGLE_LIST);
    		// Vertices for quad 1
                    manual->position(5,0,0);
    		manual->textureCoord(1,1);
    		manual->position(-5,10,0);
    		manual->textureCoord(0,0);
    		manual->position(-5,0,0);
    		manual->textureCoord(0,1);
    		manual->position(5,10,0);
    		manual->textureCoord(1,0);
                    // Put vertices for other 2 quads here
                    // ...
                    // Triangles for quad 1
    		manual->index(0);
    		manual->index(1);
    		manual->index(2);
     
    		manual->index(0);
    		manual->index(3);
    		manual->index(1);
                    // Put triangles for other 2 quads here                
                    // ...
                    // And now wrap up the object and add to scene graph for display
                    manual->end();
    		Ogre::SceneNode* grassNode = _sceneManager->getRootSceneNode()->createChildSceneNode("GrassNode2");
    		grassNode->attachObject(manual);

    To add the two missing quads, use the following corner vertex positions: Quad 2 = (2.5,0,4.3),(-2.5,10,-4.3), (-2.5,0,-4.3), (2.5,10,4.3), Quad 3 = (2.5,0,-4.3),(-2.5,10,4.3),(-2.5,0,4.3),(2.5,10,-4.3). Don't forget to give the vertices texture coordinates, which are the same for all quads. Now call createGrass() from your createScene() method and you should see a single bundle of grass right in the middle of the scene.

  3. Instance Grass Object To create multiple instances of the object you created, you need to first convert it into a proper Ogre Mesh. In the createGrass() method, replace your last two lines with the following line:
    manual->convertToMesh("BladesOfGrass");

    This essentially creates a new model with the name “BladesOfGrass”, which you can now refer to in subsequent createEntity(<name>) calls. So do that next: Let's create 2500 instances of this model, spread across the ground in a 50×50 square patch! You can do this using a nested for-loop (over i=0-49 and j=0-49) and inside the loop do the following:

    Ogre::Entity* ent = _sceneManager->createEntity("BladesOfGrass");
    Ogre::SceneNode* node = _sceneManager->getRootSceneNode()->createChildSceneNode(Ogre::Vector3(i*3,-10,j*3));
    node->attachObject(ent);

    Notice that you are not naming each entity or node when you create them! But Ogre requires unique names for each of its nodes and entities. When a name is not given, Ogre actually assigns unique names. you can take a look at these names by printing them out inside the loop:

    std::cout << node->getName() << "::"<< ent->getName() << std::endl;

    (Just remember to remove the print-outs for later steps). Do you notice that with 2500 grass models the rendering has slowed dramatically?

  4. Optimize using Static Geometry If we assume that the each grass model will stay in the same location in world space throughout your application life, we can optimize rendering by bundling them up into a packet of pre-transformed static geometry. First create a single new StaticGeometry instance. Add this line after the convertToMesh call:
    Ogre::StaticGeometry* field = _sceneManager->createStaticGeometry("FieldOfGrass");

    And now, instead of instancing the grass entities into the scene graph directly, we add them into the static geometry object in the body of our nested for-loop. So, the body of the loop becomes:

    Ogre::Entity* ent = _sceneManager->createEntity("BladesOfGrass");
    field->addEntity(ent,Ogre::Vector3(i*3,-10,j*3));

    Finally, after we have added all the entities to our field, we need to ask the static geometry to “bake”/optimize with the following call:

    field->build();

    Now you should try running the scene again and see if there is any improved performance!

Bonus

Manual Object

Create a method that takes 3 parameters,
	// radius of the base of the cone
	// height of the cone
	// detail is the number of vertices around the base.
	void createCone(float radius, float height, int detail);

This method should create a cone mesh with the tools used in the lab. Extra kudos for those who get the object to cast shadows and have an okish normal mapping. You can use this material for the object

material MyMat/Red
{
    receive_shadows on 

    technique
    {
        pass
        {
            ambient 0.3 0.1 0.1 1.0
            diffuse 1.0 0.1 0.1 1.0
            specular 1.0 1.0 1.0 1.0 0.2

            
            shading flat


        }
    }
}

Notice

If you encounter problems starting your project after finishing the next bonus part and it complains about missing textures, you might have to replace the pk3 file /Ogre1.9_vc110/OgreSDK_vc11_v1-9-0 /media/packs/chiropteraDM.pk3 Use of course your own Ogre sdk path.

with this pk3 file: chiropteraDM.zip Rename it back to chiropteraDM.pk3, make sure to change the extension of the file to pk3 not just the name

The original file in the OgreSdk folder seemed to be missing a few textures, for this one I replaced the missing textures with one colored textures.

Bonus point

Testing Another Scene Manager The so called “world geometry” of a game is often considered static and can be highly optimized for rendering. One optimized format for representing a static world is the BSP level format used originally by id's Quake engine. Ogre can read worlds in this format and display them using the BspSceneManager. To try this, add the following method in your application
	void loadQuakeMap() {     
		// add the Quake archive to the world resource group
		Ogre::ResourceGroupManager& rgm = Ogre::ResourceGroupManager::getSingleton();
		rgm.addResourceLocation("/Development/OgreSDK_vc10_v1-8-1/media/packs/chiropteraDM.pk3", "Zip", rgm.getWorldResourceGroupName(), true);
		// associate the world geometry with the world resource group, and then load the group
		rgm.linkWorldGeometryToResourceGroup(rgm.getWorldResourceGroupName(),"maps/chiropteradm.bsp", _sceneManager);
		rgm.initialiseResourceGroup(rgm.getWorldResourceGroupName());
		rgm.loadResourceGroup(rgm.getWorldResourceGroupName(), false);
	}

Of course replace the absolute path here with a path that is correct on your system. To initialize the camera to properly view this map (it's actually rotated), add the following camera initialization method:

	void setupQuakeMapView(Ogre::Camera* cam){
		// modify camera for close work
		cam->setNearClipDistance(4);
		cam->setFarClipDistance(4000);
		// set a random player starting point
		Ogre::ViewPoint vp = _sceneManager->getSuggestedViewpoint(true);
		// Quake uses the Z axis as the up axis, so make necessary adjustments
		cam->setFixedYawAxis(true, Ogre::Vector3::UNIT_Z);
		cam->pitch(Ogre::Degree(90));
		cam->setPosition(vp.position);
		cam->rotate(vp.orientation);
	}  

Finally, you need to tell Ogre to use the BSP scene manager, which you do when you create your scene manager in the startup() method of your application. You can name the scene manager explicitly like this:

_sceneManager =_root->createSceneManager("BspSceneManager");

Now you have everything you need to display the Quake level, all you have to do is to call loadQuakeMap() and setupQuakeMapView(camera) from your startup'' method and comment out some of the other things you don't wish to include (e.g. the grass above). Also, you may want to free up the camera movement (i.e. go back to the free mouse movement of camera, instead of having camera following the Ogre).

When You Are Finished

Upload your source files into Lab6 in MySchool. The lab projects will not be graded, but their completion counts towards your participation grade.