Archive

Archive for August, 2009

VertexDataAccessor, a helper to access data in a XNA VertexBuffer

August 4, 2009 Jendrik Illner 1 comment

On the XNA-forums somebody asked how-to extract the vertices from a XNA Model. Before answering I firstly searched if there was already a article on the Internet I could link to, but  to my amazement I couldn’t find a real solution which would work for all VertexBuffers.

So I thought that shouldn’t be hard to write a general and more easy way to solve the problem. The old way, which I also have written as answer to the question, involved knowing the vertex declaration and creating a struct based on this. But this isn’t the most elegant way.

A better way is quite simple to implement. I want to describe it in this post and provide my implementation as well.

The Result

Before starting, the Resulting API will look like this:

VertexDataAccessor vertexData = new VertexDataAccessor( mesh.VertexBuffer, mesh.MeshParts[0].VertexDeclaration);

Vector3[] positions = vertexData.GetData<Vector3>(VertexElementUsage.Position);


Introduction; The old way

Before we begin implementing the API shown above, I wanted to show what is necessary to retrieve data from a XNA VertexBuffer, when using the default XNA Model class. If you already know this or aren’t interested in it, feel free to skip this section.

Before we can retrieve any data from the VertexBuffer we firstly have to know what data is available. This is nicely stored in the VertexDeclaration which can be found in the ModelMeshPart contained in a mesh. From this VertexDeclaration we have the possibility to get the VertexElements. This contains information about the usage, the ordering and Types the data in the Buffer.

ModelMeshPart part = mesh.MeshParts[0];
VertexElement[] vertexElements=part.VertexDeclaration.GetVertexElements();

The Content of the VertexElements, we retrieve, could look like this.

[0]: {Stream:0 Offset:0 Format:Vector3 Method:Default Usage:Position UsageIndex:0} 
[1]: {Stream:0 Offset:12 Format:Vector3 Method:Default Usage:Normal UsageIndex:0} 
[2]: {Stream:0 Offset:24 Format:Vector2 Method:Default Usage:TextureCoordinate UsageIndex:0}

We also need the size of each vertex element. This information is also contained in the model Mesh Part.

int sizeInBytes = part.VertexStride;

We can now simply retrieve the data out of the vertex buffer:

VertexPositionNormalTexture[] vertices = new VertexPositionNormalTexture[mesh.VertexBuffer.SizeInBytes / part.VertexStride]; 
mesh.VertexBuffer.GetData<VertexPositionNormalTexture>(vertices);  

We now have the data, but maybe we just wanted the position data, all this work just to get the positions of the vertices? We are also bound to this specific VertexDeclaration. There must be a better way, as you already has seen this way exists and is quite easy to implement.

The better way

VertexDataAccessor vertexData = new VertexDataAccessor( mesh.VertexBuffer, mesh.MeshParts[0].VertexDeclaration);

Vector3[] positions = vertexData.GetData<Vector3>(VertexElementUsage.Position);

This how the result will look, as you have seen above. This is also independent of the actual VertexDeclaration, as long as the requested data is available in the VertexBuffer.

Lets begin implementing it:

Firstly we need a class that will contain the logic, this will be called VertexDataAccessor.
The Construct takes the VertexBuffer and the VertexDeclaration for the VertexBuffer.

public VertexDataAccessor(VertexBuffer vertexBuffer, VertexDeclaration declaration) 
{
    // create the array to hold the vertex buffer
    this.buffers = new VertexBuffer[1];          
    this.buffers[0] = vertexBuffer;              
    this.vertexDeclaration = declaration;         
}

As you can see we simply store the values. The Vertex Buffer will actually be stored in an array because multiple VertexBuffer’s could be used.

Now we have to implement the actual logic to retrieve the data from the Buffer. As you have seen the method for this is called GetData<T> and is a generic method. So it can handle all kinds of requests, but the generics has to be restricted to value types. Because only value types are supported as data inside a VertexBuffer.

public T[] GetData<T>(VertexElementUsage usage) where T:struct

Now to the implementation. The first step is to check if the requested usage is contained in the VertexBuffer. We check this by looking into the VertexDeclaration provided to the constructor.

// firstly check if requested data is available
VertexElement[] vertexElements = this.vertexDeclaration.GetVertexElements();
// this element will contains the requested element, if we can find it
// otherwise we will check the Stream if it is left -1 the vertexElement couldn't be found
VertexElement requestedElement = new VertexElement(-1, 0, VertexElementFormat.Unused, VertexElementMethod.Default, VertexElementUsage.Binormal, 0);

foreach (VertexElement element in vertexElements)
{
    if (element.VertexElementUsage == usage)
    {
        // we found the element describing the request data
        requestedElement = element;
    }
}

if (requestedElement.Stream == -1)
{
    throw new Exception("The requested data with the given VertexElementUsage is not present in the VertexBuffer");
}

The Rest is actually quite easy, as you will see.
We begin by collection the data for the request. The most important one is the offset. The offset define how many bytes at the beginning of each element should be skipped. XNA takes care of copying the right amount of memory into our element data.

We than handle the case that multiple vertex buffers could be used. This information is also in the VertexDeclaration structure, called Stream. So we just have to calculate the number of elements(vertices) in the buffer and create a array that for this number of elements.

Now only copy the data from the VertexBuffer using the VertexBuffer.Get(..) method defined by the XNA framework and were done.

// generate the values we need to get the data out of the vertex buffer
int offset = requestedElement.Offset;
int startIndex = 0; // always copy the complete buffer
int streamID = requestedElement.Stream; 
int vertexStride = this.vertexDeclaration.GetVertexStrideSize(streamID); // the Stream defined which vertex buffer contains the data
int elementCount = this.buffers[streamID].SizeInBytes / vertexStride;
T[] data = new T[elementCount];

// get the actual data out of the vertex buffer
this.buffers[requestedElement.Stream].GetData<T>(offset, data, startIndex, elementCount, vertexStride);
return data;

You can download my implementation, it contains some more methods than shown here, but the actual logical is nearly the same: download

Categories: all