This is a class I made to handle creation of instanced geometry.
You'll need a couple of things for it before you start:
- instancing.material, instancing.cg, and instancing.glsl from the Ogre samples
- a model to make instances of
Start by making a singleton class, however you normally would do it in your game. In mine, I have something called an LKernel that manages all of the singletons.
using System.Collections.Generic; using Mogre; public class InstancedGeometryManager { // uses mesh name as a key IDictionary<string, InstancedGeometry> igeoms; // uses mesh name as a key IDictionary<string, IList<Transform>> transforms; // uses mesh name as a key IDictionary<string, Entity> ents; public InstancedGeometryManager() { igeoms = new Dictionary<string, InstancedGeometry>(); transforms = new Dictionary<string, IList<Transform>>(); ents = new Dictionary<string, Entity>(); } // change this method's arguments to whatever you use to notify objects that you're unloading a level void OnLevelUnload(LevelChangedEventArgs eventArgs) { } // This is the part where we'll be adding an entity to the instanced geometry. The three arguments I have here just hold data from my files as to where stuff should go, what mesh it should use, what material it should have, etc. Change them as appropriate public void Add(ModelComponent mc, ThingBlock template, ModelBlock def) { } // this should be called whenever we're finished loading the entire level public void Build() { } // and this is a little class to hold where all of the entities are going to go. class Transform { public Vector3 Position { get; set; } public Quaternion Orientation { get; set; } public Vector3 Scale { get; set; } } }
This is the template we'll be using. Change everything appropriately so it works with the rest of your game first, before you start adding any more code in!
We'll start with the level unloading since that's easiest. Paste this into the OnLevelUnload method or whatever you called it.
ents.Clear(); transforms.Clear(); // this is just my way of getting the scene manager var sceneMgr = LKernel.GetG<SceneManager>(); foreach (InstancedGeometry ig in igeoms.Values) { sceneMgr.DestroyInstancedGeometry(ig); ig.Dispose(); } igeoms.Clear();
Should be pretty straightforward to figure out what that does.
Next up is the part where we "add" an entity. Now what's actually going on here is first we check to see whether we've already created an entity using the mesh you want. If we haven't, we create one. If we have, we don't do anything.
Next we get the position, orientation, and scale that the object will have when it's placed. These are stored in that Transform class so we can use them later.
Lastly we just add this Transform to our dictionary.
var sceneMgr = LKernel.GetG<SceneManager>(); string meshName = def.GetStringProperty("mesh", null); // create our entity if it doesn't exist if (!ents.ContainsKey(meshName)) { Entity ent = sceneMgr.CreateEntity(mc.Name + mc.ID, meshName); ent.SetMaterialName(def.GetStringProperty("Material", "")); // then add it to our dictionary ents.Add(meshName, ent); } // get our transforms Vector3 pos = def.GetVectorProperty("position", Vector3.ZERO) + template.VectorTokens["position"]; Quaternion orient = def.GetQuatProperty("orientation", Quaternion.IDENTITY) * template.GetQuatProperty("orientation", Quaternion.IDENTITY); Vector3 sca = def.GetVectorProperty("scale", Vector3.UNIT_SCALE); // put them in one class Transform trans = new Transform { Position = pos, Orientation = orient, Scale = sca, }; // if the transforms dictionary doesn't contain the mesh yet, add a new one if (!transforms.ContainsKey(meshName)) { transforms.Add(meshName, new List<Transform>()); } // then put our transform into the dictionary transforms[meshName].Add(trans);
And the last bit of code is probably the most important - the part where everything comes together, the Build method!
So here's what's going on:
- Get the entity
- Create the instanced geometry
- Add the entity 80 times or less to the instanced geometry
- Build the instanced geometry
- Add as many batches as you need for the geometry
- Go through the instanced objects of the batches and adept the position, scale and orientation
- Release the entity
Remember that an instanced geometry's batch can only do a maximum of 80 objects! If you have more than 80 objects, we'll need multiple batches.
var sceneMgr = LKernel.GetG<SceneManager>(); // first create all of the InstancedGeometry objects foreach (var ent in ents) { // its name is the mesh name InstancedGeometry igeom = sceneMgr.CreateInstancedGeometry(ent.Key); igeom.CastShadows = false; // add the entities to our batch int numEnts = transforms[ent.Key].Count; // add the entity 80 times or less to the geometry for (int a = 0; a < (numEnts > 80 ? 80 : numEnts); a++) { igeom.AddEntity(ent.Value, Vector3.ZERO); } igeom.Origin = Vector3.ZERO; igeom.Build(); // number of batch instances we need is number of entities / 80, since that's the maximum number of instances a batch can do for (int a = 0; a < (int) (numEnts / 80f); a++) { igeom.AddBatchInstance(); } // now we need to go through each instanced object and update its transform int transformIndex = 0; var batchIterator = igeom.GetBatchInstanceIterator(); // first we go through each batch foreach (InstancedGeometry.BatchInstance batch in batchIterator) { IList<Transform> entTransforms = transforms[ent.Key]; // then go through each object in the batch var objectIterator = batch.GetObjectIterator(); foreach (InstancedGeometry.InstancedObject obj in objectIterator) { if (transformIndex >= entTransforms.Count) { obj.Dispose(); continue; } // get the transform we'll use for this object Transform t = entTransforms[transformIndex]; // update the object with the transform obj.Position = t.Position; obj.Orientation = t.Orientation; obj.Scale = t.Scale; // then increment the index we use to get transforms transformIndex++; } } sceneMgr.DestroyEntity(ent.Value); ent.Value.Dispose(); }
That's it for the code!
Now instancing itself doesn't work out of the box - you need to set up a shader to go with it. Luckily, ogre's already provided that for you, so all you need to do is point to the shader in your material.
Any entity that wants to use instancing needs to have this line in every pass of its material:
vertex_program_ref Instancing { }
That's it, you're done!