A helping hand for bedroom coders throughout the land.
Modifying the vertices of a ModelMesh at runtime in XNA 4.0

I have an upcoming WP7 demo that requires some effects on a mesh that would normally be the preserve of the custom shader. To get around the problem of no custom effects on the phone, I wanted to manipulate the properties of my mesh’s vertices each update using the CPU instead. The structures needed to access a model mesh’s vertices directly have changed somewhat between XNA3.1 and 4.0, so I thought it might be of interest to post the code I have used to do this. It may not be the best way, but it works quite nicely in my demo.

In essence the approach has 3 parts:

  • Take a copy of the mesh’s vertex data from its VertexBuffer.
  • Modify the vertices.
  • Set the data back into the mesh’s VertexBuffer.

Since I am likely to be performance, rather than memory, bound on the phone. I decided to cache and modify a single copy of the original data. My ModelMesh is (as always!) embedded in a DrawableGameComponent, therefore I take a copy of the mesh’s vertex data in the drawable’s Initialize method thus:

public override void Initialize()
{
    // Must initialise my base class first in order to load the source model
    base.Initialize();
 
    // This class only supports a single mesh with one mesh part
    System.Diagnostics.Debug.Assert(m_model.Meshes.Count == 1);
    System.Diagnostics.Debug.Assert(m_model.Meshes[0].MeshParts.Count == 1);
 
    ModelMesh mesh = m_model.Meshes[0];
    ModelMeshPart part = mesh.MeshParts[0];
 
    // Work out how many floats in this model's vertices
    int data_size = part.VertexBuffer.VertexDeclaration.VertexStride;
    m_floats_per_vertex = data_size / sizeof(float);
 
    // Cache the vertex count & total number of floats as these will
    // needed repeatedly in the update cycle
    m_vertex_count = part.VertexBuffer.VertexCount;
    m_total_number_of_floats = m_vertex_count * m_floats_per_vertex;
 
    // Allocate internal cache of vertex data
    m_vbData = new float[m_total_number_of_floats];
 
    // Fill the cache from the part's vertexbuffer
    part.VertexBuffer.GetData<float>(m_vbData);
}

Here you can see that I encounter the first problem. What is the data format of the vertexbuffer? Well the short answer is: I don’t know for sure! However I do know how big it is, thanks to the VertexStride property. The model I am using in this example is compiled using the DualTextureEffect. By breaking on the call to the VertexStride (and assuming the data would comprise floats) I established that the vertex format for this model type comprised 10 floats. This was just what I was hoping because I had guessed that the format would probably be simply:

  • Position (3 floats)
  • Normal (3 floats)
  • TexCoord0 (2 floats)
  • TexCoord1 (2 floats)

A bit of tinkering with values seems to support this assumption. Do bear in mind, however, that this will be different for the other effect types and may be subject to change in later version of XNA I guess.

OK. So I think I know what the values are, so in the Update method I can now manipulate them:

public override void Update(GameTime gameTime)
{
    float wave_offset;
 
    // Iterate through the cached vertices 
    for (int i = 0; i < m_total_number_of_floats; i += m_floats_per_vertex)
    {
        // Calculate the wave offset using the vertex X position & the time
        wave_offset = (float)(Math.Sin(m_vbData[i] + gameTime.TotalGameTime.TotalSeconds));
 
        // Offset the X texture coords for both textures, 
        // but in opposite directions & with different scaling
        m_vbData[i + 7] -= wave_offset * 0.0005f;
        m_vbData[i + 9] += wave_offset * 0.0001f;
    }
    base.Update(gameTime);
}

In this example I tweak the texture coords a bit using a sine wave. My modelmesh is grid with some simple water textures so this makes the ‘water’ ebb and flow a bit. But essentially you should do whatever you need in this bit.

Then, finally, before each draw call I need to overwrite the ones in the mesh with my updated set:

public override void Draw(GameTime gameTime)
{
    // Set the vertex data into the MeshPart before
    // rendering
    ModelMesh mesh = m_model.Meshes[0];
    ModelMeshPart part = mesh.MeshParts[0];
    part.VertexBuffer.SetData<float>(m_vbData);
 
    base.Draw(gameTime);
}

Couple of tips

It is worth adding a couple of reminders here.

This method is appropriate when you when you need to do some dynamic manipulation of vertices for deployment to WP7. If you want to do some one-off manipulations of the vertices, you should probably use the Content Pipeline instead. If you aren’t deploying to the phone, then you’ll probably be better off using a shader and passing the work off onto the GPU.

You do really need to know (or be able to work out) what the vertex format is for you model – you can’t assume too much about the data structure, although the first 3 floats will *usually* be the position. *Definitely* don’t assume a consistent size of the vertex definition but this is easy to test for, once you know where to look!

Also if you plan to get really down and dirty with vertex manipulation, don’t forget that the vertices for individual triangles can only be found indirectly through a look-up table (the IndexBuffer) so if you want to do something fancier than just perturbing them for all connected triangles, you will need to parse this too (using a simpler, but similar method to one above).


Posted Thu, Feb 24 2011 5:34 PM by Edward Powell

Comments

LintfordPickle wrote re: Modifying the vertices of a ModelMesh at runtime in XNA 4.0
on Fri, Feb 25 2011 7:35 AM

Hi Edward,

SO what is the performance like for this application?  I assume from the introductory paragraph that you are performing 'SetData<>' per frame.

-John

Edward Powell wrote re: Modifying the vertices of a ModelMesh at runtime in XNA 4.0
on Fri, Feb 25 2011 2:05 PM

Hi John - Yep a SetData per frame, but only one GetData on Initialize. SetData is a relatively expensive operation, although my understanding is that GetData is the real stinger.

My example is using a mesh with 81 vertices (so I think this equates to 3240 bytes copied over per frame) and is running 30fps on the phone.