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