Skip to main content

History: Intermediate Tutorial 5

Preview of version: 48

Any problems you encounter during working with this tutorial should be posted in the Help Forum(external link).

Introduction

If we can let Ogre know that an object will not be moved, then it can optimize how it handles that object. This is the core idea behind StaticGeometry objects. StaticGeometry is a bit of a misnomer in this case, because we can use some tricks to accomplish things like grass waving in the wind, but the general idea is that this is an object that will not be manipulated a great deal. Good examples include rocks, trees, and buildings. StaticGeometry objects can hold a large number of meshes that will all be "batched" together and drawn efficiently as a group. We will also go into some more detail on the use of ManualObject. We will expand on what was introduced in Intermediate Tutorial 4. This tutorial is largely based on the Grass Demo in the Ogre Samples. The source for that demo can be read for further details.

The full source for this tutorial is here.

Note: There is also source available that uses the BaseApplication framework and Ogre 1.7 here.

Prerequisites

This tutorial assumes that you already know how to set up an Ogre project and compile it successfully. Knowledge of the topics from previous tutorials is also assumed.

Setting Up the Scene

The first thing we will do is create the grass mesh we will be rendering. We will use a pattern you've probably seen in games before to create the illusion of grass. We will render three square quads that have a grass texture applied to them. We will create one, then place another rotated 60 degrees, and then place a third rotated 120 degrees. This will create a simple illusion of 3D grass. As in the previous tutorial, we will be using a ManualObject to generate our mesh, but this time we will create a solid mesh instead of a 2D outline. This will require an index buffer to connect the vertices.

The first step will be to define some variables. We will define the widtha nd height of our quad, then we will initialize a Vector3 that will be used to define the four corners of our quad. We will again be using Quaternions to handle the rotations. Our plan is to use the Vector3 to represent the orientation of our quad, then we will rotate it with a Quaternion and repeat. Add the following to createGrassMesh:

Copy to clipboard
const float width = 25; const float height = 30; Ogre::ManualObject obj("GrassObject"); Ogre::Vector3 vec(width/2, 0, 0); Ogre::Quaternion quat; quat.FromAngleAxis(Ogre::Degree(60), Ogre::Vector3::UNIT_Y);


This should look somewhat familiar. We have created a Quaternion that represents a 60 degree rotation around the y-axis.

We will now begin defining our ManualObject. We set the RenderOperation to be OT_TRIANGLE_LIST. This means that after we define our vertices with the position method, we then have to let Ogre know how to setup the index buffer by giving it a list of triangles made from the vertices.

Copy to clipboard
obj.begin("Examples/GrassBlades", Ogre::RenderOperation::OT_TRIANGLE_LIST); for (int i = 0; i < 3; ++i) {


For each quad we are going to define four vertices. We will also specify a texture coordinate. These are normalized coordinates that tell Ogre how to map the texture on to our geometry. These coordinates a very simple since we are creating a solid square. They simply correspond to the four corners.

Copy to clipboard
^ obj.position(-vec.x, height, -vec.z); obj.textureCoord(0, 0); obj.position(vec.x, height, vec.z); obj.textureCoord(1, 0); obj.position(-vec.x, 0, -vec.z); obj.textureCoord(0, 1); obj.position(vec.x, 0, vec.z); obj.textureCoord(1, 1);


The Vector3 we are using starts out pointing down the x-axis with a length of half the width of our quad. This may seem confusing at first, because you'll notice all of the z components are zero for the first quad. The vector may seem like overkill, but once we rotate it to set up our next quad the z values will no longer be zero. This may be a little hard to visualize. Here is a picture to help:

quad_visual.png

Remember that the x and z are the plane of the floor. So our vector keeps track of where the foundation of our quad is, we build everything from that. We've also labeled the four corners 0, 1, 2, 3 to help with creating the triangles.

To ensure that both triangles face the same direction, we need to provide the points of the triangles in counter-clockwise order. The triangle method does not directly take positions. Instead it takes three numbers that represent the order in which the points were created. This is why we labeled the four corners in our image.

Copy to clipboard
^ int offset = 4 * i; obj.triangle(offset + 0, offset + 3, offset + 1); obj.triangle(offset + 0, offset + 2, offset + 3);


First, ignore the offset value and look at the numbers we are adding. They match the numbers we assigned in the image. The first triangle connects the 0th, 3rd, and 1st corners. The second triangle connects the 0th, 2nd, and 3rd corners. You can look at the image to see these are in counter-clockwise order.


Now that we have defined the four corners of our quad, we now need to create the faces. As we mentioned briefly in the previous tutorial, you must specify faces by creating triangles, and you must be sure to wind them counter clockwise to face towards you. For each quad, we will build two triangles. The first will be from the (0th, 3rd, 1st) vertices defined, and the second from the (0th, 2nd, 3rd) vertices defined. This properly defines the quad. Also remember that we are looping a few times, and every time through we create 4 vertices, thus we have to use an offset variable to select the proper vertex:


Next we need to rotate the vector we are using to create the current quad and continue looping. After the loop is finished we must call ManualObject::end to complete the object:

Copy to clipboard
^ vec = quat * vec; } obj.end();


Now that we have defined the manual object, we are almost ready to start creating our StaticGeometry. One last thing we are going to do is create a mesh out of our ManualObject. Meshes are a touch more optimized than using a ManualObject directly for rendering. To do this, we simply need to call the ManualObject::convertToMesh with a name to store the mesh as:

Copy to clipboard
obj.convertToMesh("GrassBladesMesh");


We are now finished creating the grass mesh. Note that if you have created a highly complex mesh in this way, you may save it to file and simply load the file back in instead of recreating the ManualObject every time you load the program. To do this, we will take the return value of convertToMesh (which we discarded in the actual code), and feed the Mesh it returns to the MeshSerializer::exportMesh function. Here is an example of how to do that:

Copy to clipboard
// Do not add to the code! Ogre::MeshPtr ptr = mo.convertToMesh("GrassBladesMesh"); Ogre::MeshSerializer ser; ser.exportMesh(ptr.getPointer(), "grass.mesh");


We are now ready to create the StaticGeometry.

Adding Static Geometry

The createScene method is already populated with several things. I have already added code to create a floor plane, add a robot, set the Camera's position, and so on since we have covered how to do those things in previous tutorials. Be sure to make sure you understand the current contents of this function before continuing.

The first thing we are going to do now is create an Entity based off of the grass mesh we created earlier and create a StaticGeometry object. Note that we will only create one Entity to use with our StaticGeometry. Find the createScene method and add the following code to the end of it:

Copy to clipboard
Ogre::Entity *grass = mSceneMgr->createEntity("grass", "GrassBladesMesh"); Ogre::StaticGeometry *sg = mSceneMgr->createStaticGeometry("GrassArea"); const int size = 375; const int amount = 20;


The size variable will define how large of an area we are covering with grass and the amount variable will define how many objects we will put in each row of our StaticGeometry.

The next thing we need to do is define the size and origin of the StaticGeometry. Once we build the object (by calling StaticGeometry::build), we can no longer change the origin or region the StaticGeometry defines. The origin is the top left corner of the region that the StaticGeometry defines. If you want to place the StaticGeometry around a point, you will need set the origin's x and z coordinates to be half of the region's size for x and z:

Copy to clipboard
sg->setRegionDimensions(Ogre::Vector3(size, size, size)); sg->setOrigin(Ogre::Vector3(-size/2, 0, -size/2));

This will center the object around the point (0, 0, 0). To center it around a point in 3D space, you would need to do something similar to this:

Copy to clipboard
// Do not add to the project! sg->setOrigin(Vector3(-size/2, -size/2, -size/2) + Vector3(x, y, z));


Where x, y, z is the point in 3D space to center it around. Also note that we do define the vertical height of the object when setting the region. Be sure that the y component of setRegionDimensions is at least as large as the highest object in the StaticGeometry.

The next thing we need to do is add objects to the StaticGeometry. This next piece of code is somewhat complex because we are adding a whole grid of grass to the geometry, and giving a random shift in x, z position, a random rotation, and a random vertical scale to it. In reality, the most important thing to understand in this is the StaticGeometry::addEntity:

Copy to clipboard
for (int x = -size/2; x < size/2; x += (size/amount)) { for (int z = -size/2; z < size/2; z += (size/amount)) { Ogre::Real r = size / (float)amount / 2; Ogre::Vector3 pos(x + Ogre::Math::RangeRandom(-r, r), 0, z + Ogre::Math::RangeRandom(-r, r)); Ogre::Vector3 scale(1, Ogre::Math::RangeRandom(0.9, 1.1), 1); Ogre::Quaternion orientation; orientation.FromAngleAxis(Ogre::Degree(Ogre::Math::RangeRandom(0, 359)), Ogre::Vector3::UNIT_Y); sg->addEntity(grass, pos, orientation, scale); } }


The addEntity function takes in the Entity to use, the position of the object, the orientation of the object, and the scale of the object. When you are defining StaticGeometry you will either use the addEntity function or the addSceneNode function. The addSceneNode function walks the -SceneNode adding all Entities to the static geometry, using the position, orientation, and scale of the children SceneNodes instead of specifying them manually. Note that if you use the addSceneNode function, be sure to remove the node from its parent -SceneNode, since the addSceneNode function does not remove it for you. If you do not, Ogre will render both the StaticGeometry you created and the original -SceneNode which is not what you want.

Finally we need to build the StaticGeometry before it is displayed:

Copy to clipboard
sg->build();


Compile and run your application, you should now see a robot standing in a small patch of grass.

Modifying StaticGeometry

Once the StaticGeometry is created, you are not supposed to do too much with it, since that would mostly defeat the purpose. You can, however, do things like wave the grass with the wind. If you are interested in how to do this, take a look at the grass demo which comes with Ogre. The GrassListener::waveGrass function modifies the grass to perform a wave-like motion.

Advanced Object Batching

This is, of course, just the beginnings of object batching. You should use StaticGeometry any time you have objects that are grouped together and will not move. If you are trying to create something as intensive or as expansive as a forest or trying to add grass to a huge amount of terrain, you should take a look at one of the more advanced batching techniques, like the PagedGeometry Engine.

Creating a ManualObject from the SceneManager

In addition to instantiating a ManualObject directly, you can also use Ogre::SceneManager::createManualObject to instantiate a ManualObject. One user reported an inability to render ManualObjects when they were instantiated directly, so if you're encountering errors, it may be worth using the SceneManager to instantiate your ManualObjects as part of troubleshooting.

Exercises

Easy

  1. Exercise

Intermediate

  1. Exercise

Difficult

  1. Exercise

Advanced

  1. Exercise

Conclusion

TODO

Full Source

The full source for this tutorial is here.

Next

Intermediate Tutorial 5


Alias: Intermediate_Tutorial_5

History

Information Version
Sat 14 of Aug, 2021 16:49 GMT-0000 paroj 88
Tue 07 of Apr, 2015 02:12 GMT-0000 kabbotta 87
Mon 06 of Apr, 2015 21:59 GMT-0000 kabbotta 86
Mon 30 of Mar, 2015 09:38 GMT-0000 kabbotta 85
Mon 30 of Mar, 2015 09:38 GMT-0000 kabbotta 84
Mon 30 of Mar, 2015 09:37 GMT-0000 kabbotta 83
Mon 30 of Mar, 2015 09:36 GMT-0000 kabbotta 82
Mon 30 of Mar, 2015 09:28 GMT-0000 kabbotta 81
Mon 30 of Mar, 2015 09:18 GMT-0000 kabbotta 80
Mon 30 of Mar, 2015 09:17 GMT-0000 kabbotta 79
Mon 30 of Mar, 2015 09:15 GMT-0000 kabbotta 78
Mon 30 of Mar, 2015 02:04 GMT-0000 kabbotta 77
Mon 30 of Mar, 2015 02:04 GMT-0000 kabbotta 76
Mon 30 of Mar, 2015 02:03 GMT-0000 kabbotta 75
Mon 30 of Mar, 2015 01:18 GMT-0000 kabbotta 74
Sun 29 of Mar, 2015 05:26 GMT-0000 kabbotta 73
Fri 27 of Mar, 2015 04:34 GMT-0000 kabbotta 72
Fri 27 of Mar, 2015 04:33 GMT-0000 kabbotta 71
Fri 27 of Mar, 2015 04:29 GMT-0000 kabbotta 70
Fri 27 of Mar, 2015 04:28 GMT-0000 kabbotta 69
Fri 27 of Mar, 2015 04:25 GMT-0000 kabbotta 68
Fri 27 of Mar, 2015 04:25 GMT-0000 kabbotta 67
Fri 27 of Mar, 2015 04:17 GMT-0000 kabbotta 66
Fri 27 of Mar, 2015 04:17 GMT-0000 kabbotta 65
Fri 27 of Mar, 2015 04:14 GMT-0000 kabbotta 64