Custom meshes in Ogre3D
Introduction
Recently I have been working on a small project using the Ogre3D rendering engine and needed to dynamically create a mesh at runtime. There is an example on the Ogre3D wiki but it assumes you have some underlying knowledge on the way meshes are represented by lower-level APIs such as OpenGL and Direct3D so I decided to write this blog post to give some more detail.
Vertices and triangles
Most of the time in a 3d game, you are looking at a bunch of triangles which make up more complicated shapes. Each of these triangles is defined by a set of three points - its vertices.
In order for the graphics card to render these shapes, all you need to do is send it the coordinates of the vertices and then tell it how to join them up. Really old graphics cards used to be sent the vertices they needed to render each frame, however, this isn’t the best way of doing it (nevertheless, you still see it in many OpenGL tutorials around the web). Your application becomes limited by the capacity of the bus for little reason, especially considering that most of the models in the scene are static.
This has been improved by the creation of “vertex buffers”. Vertex buffers allow you to send the coordinates of the vertices to the card once. They are then kept in video memory, so that you don’t need to clog up the bus each frame. Because you are no longer limited by the bus, you can also render many more models.
Vertex buffers in OGRE
In OGRE, the HardwareBuffer class represents a vertex buffer. The HardwareBufferManager singleton allows you to create new hardware buffers.
Creating a vertex buffer is quite simple, you call the createVertexBuffer
method and tell it the following pieces of information:
- How you plan to use the buffer
- The number of bytes that represent each vertex
- The number of vertices in the buffer
I’ll focus on the first point to begin with. You can either mark the buffer as
being HBU_STATIC
or HBU_DYNAMIC
. Static buffers are ones you don’t plan to
modify very often (you can still modify them, though). Dynamic buffers are ones
you plan to modify frequently.
You can also mark a buffer as being HBU_WRITE_ONLY
which means you don’t plan
to read data back from the buffer.
These allow the graphics card/driver to pick the best way to store your buffer.
The number of vertices in the buffer is fairly self explanatory. However, the number of bytes that represent each vertex requires more explaining. You need to pass this value because there is no fixed way to represent a vertex. As well as the position, your application might need normals, texture coordinates, colours, etc.
This leads us into the next section - vertex declarations.
Vertex declarations in OGRE
The VertexDeclaration class holds a list of VertexElements. These are used to describe the structure of each vertex.
For example, if you wanted each vertex to hold its position, a 2d texture coordinate and a normal, you would need to create three elements for each of these.
Each element has a type and semantic. The type is the data type - e.g. a 2d
texture coordinate is usually represented by two floating point numbers, so its
type would be VET_FLOAT2
. The semantic is VES_TEXCOORD
, as it is a texture
coordinate.
So for the example earlier, we’d need to have:
VET_FLOAT3
VES_POSITION
for the positionVET_FLOAT2
VES_TEXCOORD
for the texture coordinateVET_FLOAT3
VES_NORMAL
for the normal
Index buffers
The final thing we need is to do is to tell the graphics card how to connect
the buffers into triangles. There are several ways to do this, but the easiest
and most versatile is to use OT_TRIANGLE_LIST
. With this, you supply a list
of indices which point to the vertices from earlier. Each three indices
describe a triangle. You must specify the indices in anti-clockwise order in
order for the triangle to face you. The other side of the triangle will be
‘culled’ and invisible, so for two-sided triangles you must specify the indices
twice.
The code
The following code is a commented example on how to create a simple mesh, which should be fairly easy to understand if you read the theory above.
/* create the mesh and a single sub mesh */
Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().createManual("CustomMesh", "General")
Ogre::SubMesh *subMesh = mesh->createSubMesh();
/* create the vertex data structure */
mesh->sharedVertexData = new Ogre::VertexData;
mesh->sharedVertexData->vertexCount = 3;
/* declare how the vertices will be represented */
Ogre::VertexDeclaration *decl = mesh->sharedVertexData->vertexDeclaration;
size_t offset = 0;
/* the first three floats of each vertex represent the position */
decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
/* the second three floats of each vertex represent the colour */
decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_COLOUR);
offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
/* create the vertex buffer */
Ogre::HardwareBuffer *vertexBuffer = Ogre::HardwareBufferManager::getSingleton().
createVertexBuffer(offset, mesh->sharedVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
/* lock the buffer so we can get exclusive access to its data */
float *vertices = static_cast<float *>(vertexBuffer->lock(Ogre::HardwareBuffer::HBL_NORMAL));
/* populate the buffer with some data */
vertices[0] = 0; vertices[1] = 1; vertices[2] = 0; /* position */
vertices[3] = 1; vertices[4] = 0; vertices[5] = 0; /* colour */
vertices[6] = -1; vertices[7] = -1; vertices[8] = 0; /* position */
vertices[9] = 0; vertices[10] = 1; vertices[11] = 0; /* colour */
vertices[12] = 1; vertices[13] = -1; vertices[14] = 0; /* position */
vertices[15] = 0; vertices[16] = 0; vertices[17] = 1; /* colour */
/* unlock the buffer */
vertexBuffer->unlock();
/* create the index buffer */
Ogre::HardwareBuffer *indexBuffer = Ogre::HardwareBufferManager::getSingleton().
createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, mesh->sharedVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
/* lock the buffer so we can get exclusive access to its data */
uint16_t *indices = static_cast<uint16_t *>(indexBuffer->lock(Ogre::HardwareBuffer::HBL_NORMAL));
/* define our triangle */
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
/* unlock the buffer */
indexBuffer->unlock();
/* attach the buffers to the mesh */
mesh->sharedVertexData->vertexBufferBinding->setBinding(0, vertexBuffer);
subMesh->useSharedVertices = true;
subMesh->indexData->indexBuffer = indexBuffer;
subMesh->indexData->indexCount = mesh->sharedVertexData->vertexCount;
subMesh->indexData->indexStart = 0;
/* set the bounds of the mesh */
mesh->_setBounds(Ogre::AxisAlignedBox(-1, -1, -1, 1, 1, 1));
/* notify the mesh that we're all ready */
mesh->load();
/* you can now create an entity/scene node based on your mesh, e.g. */
Ogre::Entity *entity = sceneManager->createEntity("CustomEntity", "CustomMesh", "General");
entity->setMaterialName("YourMaterial", "General");
Ogre::SceneNode *node = rootNode->createChildSceneNode();
node->attachObject(entity);