Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

This is somewhat linked to a question I previously asked concerning the drawing of indexed primitives.

My problem was that I was only drawing one cube when I wanted to draw many. I was told that the problem was that I was overwriting the vertex and index buffers with each new instantiation of Cube and that instead I should create one at the origin and then draw many, passing through a transformation matrix to the shader that makes it appear in different places. This worked beautifully.

I now have a new problem though: how would I draw many different types of primitives?

Here is my code from the previous question:

Cube::Cube(D3DXCOLOR colour, D3DXVECTOR3 min, D3DXVECTOR3 max)
{
// create eight vertices to represent the corners of the cube
VERTEX OurVertices[] =
{
    {D3DXVECTOR3(min.x, max.y, max.z), colour},
    {D3DXVECTOR3(min.x, max.y, min.z), colour},
    {D3DXVECTOR3(min.x, min.y, max.z), colour},
    {min, colour},
    {max, colour},
    {D3DXVECTOR3(max.x, max.y, min.z), colour},
    {D3DXVECTOR3(max.x, min.y, max.z), colour},
    {D3DXVECTOR3(max.x, min.y, min.z), colour},
};

// create the vertex buffer
D3D10_BUFFER_DESC bd;
bd.Usage = D3D10_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(VERTEX) * 8;
bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
bd.MiscFlags = 0;

device->CreateBuffer(&bd, NULL, &pBuffer);

void* pVoid;    // the void pointer

pBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid);    // map the vertex buffer
memcpy(pVoid, OurVertices, sizeof(OurVertices));    // copy the vertices to the buffer
pBuffer->Unmap();

// create the index buffer out of DWORDs
DWORD OurIndices[] = 
{
    0, 1, 2,    // side 1
    2, 1, 3,
    4, 0, 6,    // side 2
    6, 0, 2,
    7, 5, 6,    // side 3
    6, 5, 4,
    3, 1, 7,    // side 4
    7, 1, 5,
    4, 5, 0,    // side 5
    0, 5, 1,
    3, 7, 2,    // side 6
    2, 7, 6,
};

// create the index buffer
// D3D10_BUFFER_DESC bd;    // redefinition
bd.Usage = D3D10_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(DWORD) * 36;
bd.BindFlags = D3D10_BIND_INDEX_BUFFER;
bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
bd.MiscFlags = 0;

device->CreateBuffer(&bd, NULL, &iBuffer);

iBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid);    // map the index buffer
memcpy(pVoid, OurIndices, sizeof(OurIndices));    // copy the indices to the buffer
iBuffer->Unmap();

//this is simply a single call to the update method that sets up the scale, rotation
//and translation matrices, in case the cubes are static and you don't want to have to 
//call update every frame
Update(D3DXVECTOR3(1, 1, 1), D3DXVECTOR3(0, 0, 0), D3DXVECTOR3(0, 0, 0));
}

Clearly if I duplicated and modified the code to be a different object or shape, the last shape to be initialized would overwrite the vertex buffer, wouldn't it?

Do I use multiple vertex buffers? Do I append the new vertex buffer to the old one and use the appropriate indices to draw them? Can I do either? Both?

share|improve this question
add comment

1 Answer

up vote 12 down vote accepted

It's probably a bad idea to create new classes for each type of geometry you're going to support. It's not very scalable or maintainable. Additionally, the class design you appear to want now seems to conflate the tasks of managing the geometry itself and the instance data for that geometry.

Here's an approach you can take:

Create two classes, Mesh and MeshInstance. A Mesh contains all the properties of the shared geometry -- basically a vertex buffer and an index buffer. If you want, you can create helper functions that create meshes containing cube vertex data (or sphere vertex data, or whatever else you like). You should tailor the public interface of the Mesh class to allow such helper functions to be implemented as non-member, non-friend functions.

MeshInstance, on the other hand, must be constructed with a reference to a Mesh. The MeshInstance contains the properties of an individual object -- it's world transformation, and shader overrides used to render it, and so on.

This way, when you want to create a new cube, you first obtain the Mesh object representing a cube from some shared library of primitive mesh objects you created at startup. Then you create a new MeshInstance, assigning it that cube Mesh.

When you render, you build up a list of all the MeshInstances you want to draw, and submit them. If you group them by Mesh or texture, you can optimize for state change overhead (i.e., you draw all the mesh instances corresponding to the cube mesh at once, and then all the mesh instances corresponding to the sphere mesh, so you have fewer SetVertexBuffer calls on the D3D device). You can also group by other state, like texture and shader.

In this fashion, you avoid wasting memory duplicating vertex data, and you ensure that your rendering system can scale to any arbitrary set of primitives simply by implementing new (a) functions to create meshes programmatically or (b) functions to load meshes from files of a particular format.

Once your render pipeline works in terms of generalized Mesh objects, it's much easier to adapt it in a global fashion to new techniques or optimizations.

Specific comments:

Clearly if I duplicated and modified the code to be a different object or shape, the last shape to be initialised would overwrite the vertex buffer. wouldn't it?

No. In the code you posted, the only way pBuffer and the like would be overwritten was if it was a static member variable. If you copied the class wholesale to create (for example) a Sphere class, that would be a new static variable. This is still a bad idea.

Do I use multiple vertex buffers? Do I append the new vertex buffer to the old one and use the appropriate indices to draw them? Can I do either? Both?

The naive implementation of the technique I describe above involves multiple buffers (one for each set of shared geometry). If that geometry is static it is possible to store it all into one (or multiple, as there is a practical optimal limit to buffer size) buffer to minimize buffer state changes even further. That should be considered an optimization and is left as an exercise for the reader; first get it working, then worry about making it fast.

share|improve this answer
    
I know we're not supposed to post 'thanks' comments, but this is so helpful! Thank you! –  Asher Einhorn Oct 17 '11 at 16:56
    
at the moment pBuffer and iBuffer are extern. Should I make these instance members of each Mesh object? –  Asher Einhorn Oct 18 '11 at 9:03
1  
Yes, that's a good place to start. –  Josh Petrie Oct 18 '11 at 14:38
    
Now that I come to implement this I'm having a little trouble thinking about how to do it, just the part where you say "If you want, you can create helper functions that create meshes containing cube vertex data (or sphere vertex data, or whatever else you like). You should tailor the public interface of the Mesh class to allow such helper functions to be implemented as non-member, non-friend functions." what exactly do you mean by non-helper, non friend functions? –  Asher Einhorn Dec 1 '11 at 21:03
    
A function that is not a member of the class (so a global function) that is also not declared as a friend of the class. A "regular" function, in other words -- that manipulates the mesh object only through its public API. –  Josh Petrie Dec 1 '11 at 21:05
show 4 more comments

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.