History: Intermediate Tutorial 5
Preview of version: 60
- «
- »
Any problems you encounter during working with this tutorial should be posted in the Help Forum
.
Table of contents
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:
const float width = 25; const float height = 30; Ogre::Vector3 vec(width/2, 0, 0); Ogre::ManualObject obj("GrassObject"); 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.
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.
^ 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 vector we are using starts out pointing down the x-axis with a length that is 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:
Remember that x and z are in 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 with the order they were created to help with creating the triangles. The count starts with the 0th corner.
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.
^ 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. The purpose of the offset is because we are creating three different quads, but they are all going to be a part of one ManualObject. So the second quad's corners will be numbered 4, 5, 6, 7. Adding the offset accounts for this.
Now we need to rotate our vector so it can be used to make the next quad. After we've created all three quads, the loop ends and we call end to finalize the object.
^ vec = quat * vec; } obj.end(); obj.convertToMesh("GrassBladesMesh");
The last line converts our ManualObject into an actual mesh. Meshes are a bit more optimized than directly rendering a ManualObject.
We are now finished creating the grass mesh. If you use this method to create a complex mesh, then you may save it to a file which can be reloaded instead of rebuilding the mesh each time. To do this, you would save the MeshPtr that is returned by convertToMesh. Then you would use a MeshSerializer to export the mesh to a file.
Here is an example. Do not add this code to our current project.
Ogre::MeshPtr ptr = obj.convertToMesh("GrassBladesMesh"); Ogre::MeshSerializer ser; ser.exportMesh(ptr.getPointer(), "user_grass.mesh");
Adding Static Geometry
We are now going to build our StaticGeometry object, but we need a basic scene set up first. Add the following to createScene:
mSceneMgr->setAmbientLight(Ogre::ColourValue(1.0, 1.0, 1.0)); createGrassMesh(); mCamera->setPosition(150, 50, 150); mCamera->lookAt(0, 0, 0); Ogre::Entity* robot = mSceneMgr->createEntity("robot", "robot.mesh"); mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(robot); Ogre::Plane plane; plane.normal = Ogre::Vector3::UNIT_Y; plane.d = 0; Ogre::MeshManager::getSingleton().createPlane( "floor", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane, 450.0, 450.0, 10, 10, true, 1, 50.0, 50.0, Ogre::Vector3::UNIT_Z); Ogre::Entity* planeEntity = mSceneMgr->createEntity("floor"); planeEntity->setMaterialName("Examples/GrassFloor"); planeEntity->setCastShadows(false); mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(planeEntity);
We've covered all of this before. Look into the previous tutorials if you are confused by any of this (notice we've also called createGrassMesh in our setup.
Now we get to creation of our StaticGeometry. The first we do is create an Entity from the grass mesh we constructed. Then we ask the SceneManager to give us a pointer to a new StaticGeometry object.
Ogre::Entity* grass = mSceneMgr->createEntity("GrassBladesMesh"); Ogre::StaticGeometry* sg = mSceneMgr->createStaticGeometry("GrassArea");
Then we define two constants. The first will be used as the size of our StaticGeometry region, and the second will be the number of grass patches we will create in that region.
const int size = 375; const int amount = 20;
Now we'll set a few options for StaticGeometry.
sg->setRegionDimensions(Ogre::Vector3(size, size, size)); sg->setOrigin(Ogre::Vector3(-size/2, 0, -size/2));
This sets the bounding volume for our object and defines the origin of our region to be the top-left point in that region.
Now we will prepare the actual build. We are going to loop through points on the floor of our region and place a grass patch with a random offset at each point.
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));
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:
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:
// 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:
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:
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
- Exercise
Intermediate
- Exercise
Difficult
- Exercise
Advanced
- Exercise
Conclusion
TODO
Full Source
The full source for this tutorial is here.
Next
Alias: Intermediate_Tutorial_5