|
|
|
|

Defining our own vertex format |
Let’s have a look at the staring point in our flowchart: the big arrow starting in our XNA app, going to our vertex shader:

It represents the flow of vertex data from our XNA app to the vertex shader on the graphics card. This is triggered every time we issue some kind of draw command in XNA. When we pass the vertex data from our XNA app to our vertex shader, we need to pass some information with it, describing what kind of data is contained in the vertex stream. Using shaders, you need to specify exactly what information can be found in the stream, and where.
So what we need is: a structure that can hold the necessary data for each vertex and a definition of the data, so the vertex shader knows which data is included with every vertex.
In the starting code, we’ve been using the VertexPositionColor struct which satisfies both requirements. Here, we’re going to define a new struct, MyOwnVertexFormat, that will be exactly the same as the VertexPositionColor struct, to see what’s in there and why it is needed. This will allow us to expand it further on in this series.
For a far more detailed explanation on creating custom vertex formats, read Recipe 5-14.
To satisfy the 2 requirements, in the example of a simple colored triangle, we need our vertices to hold 3D Position data as well as Color data. So this is how we start our struct (you can put this at the top of our code):
struct MyOwnVertexFormat { private Vector3 position; private Color color; public MyOwnVertexFormat (Vector3 position, Color color) { this.position = position; this.color = color; } }
We simply defined a new structure, and defined it so it can hold a vector3 and a color. We also defined a constructor, so later in our program we can create and fill a new instance of this struct in one line. The first requirement has been satisfied.
Next, we need a way to tell our graphics card that the data we are sending actually contain Position and Color data. Although we gave the elements straightforward names (position, color), the graphics card needs to be told explicitly which data it will receive.
As seen in the previous series, this is done by means of setting a VertexDeclaration before rendering triangle. However, when we created this VertexDeclaration, we always used the VertexElements property of the type of vertices we were rendering from. Since we are defining our own kind of vertices, we also need to define its VertexElements.
The VertexElements property which will contain 1 entry for each type of data accompanying each vertex. Put this code inside, at the bottom of our struct:
public static VertexElement[] VertexElements = { new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0), new VertexElement(0, sizeof(float)*3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0), }; public static int SizeInBytes = sizeof(float) * (3 + 1);
For each type of data, we define how many bytes it occupies, what it is used for, and where it can be found. The following paragraph contains a brief summary of the explanation in Recipe 5-14.
The first argument is the number of the vertexstream we’ll be describing. Because we’ll only be using one, we indicate 0. The second argument is very important. It indicates at which offset IN BYTES the type of data can found in the vertex stream. So the first type of data, the 3D position, starts at offset 0.
Next, we indicate how that kind of data is stored, such as int, float1, short4 and more. A position comes in a Vector3 (which is composed of 3 floats). Concerning the next argument, you’ll almost always want to use DeclarationMethod.Default. The next argument is the most important one: it describes the kind of information, such as position, color, texture coordinate, tangent, etc. This is needed, so XNA can automatically link the right data from our vertex stream to the right variables in our vertex shaders.
For the last argument, suppose you would like to add 2 textures to a triangle. This implicates you would have to pass 2 sets of texture coordinates along with each vector. For this, you can use the last argument, which is the index of each info. Since we’ll only be passing 1 position and 1 color for each vertex, this will be 0 for both lines.
For our color, we do pretty much the same. It is still part of vertexstream 0, but because it is preceded by the position it can’t be found at offset 0. The position consists of 3 floats, this is what we need to indicate (a float occupies 4 bytes, so sizeof(float)*3 is 12, which is the offset in bytes to the color information). Although a color is in fact a Vector4 (4 floats: the RGBA values), we need to specify Color, because each value needs to be mapped within the range [0..1] before it can be passed to the vertex shader as a color. This is an exception.
We also supply our MyOwnVertexFormat with a SizeInBytes member, which stores the number of bytes 1 vertex occupies in memory. Because the position uses 3 floats and the color 1 float, it will take up the memory of 4 floats, which is 16 bytes.
Now let’s change our code so we’ll be using our MyOwnVertexFormat instead of the VertexPositionColor struct. Change our SetUpVertices to this:
private void SetUpVertices() { MyOwnVertexFormat[] vertices = new MyOwnVertexFormat[3]; vertices[0] = new MyOwnVertexFormat(new Vector3(-2, 2, 0), Color.Red); vertices[1] = new MyOwnVertexFormat(new Vector3(2, -2, -2), Color.Green); vertices[2] = new MyOwnVertexFormat(new Vector3(0, 0, 2), Color.Yellow); vertexBuffer = new VertexBuffer(device, vertices.Length * MyOwnVertexFormat.SizeInBytes, BufferUsage.WriteOnly); vertexBuffer.SetData(vertices); vertexDeclaration = new VertexDeclaration(device, MyOwnVertexFormat.VertexElements); }
Note that we have also adjusted our VertexDeclaration, so it corresponds to our newly defined type of vertex.
We also should update the size of one vertex in the Draw method:
device.Vertices[0].SetSource(vertexBuffer, 0, MyOwnVertexFormat.SizeInBytes);
OK, we have recreated the pre-built VertexPositionColor struct. When you run the code, you should see the same triangle. Only this time, you know how what the VertexDeclaration contains and, more important, you know how to extend a vertex format. We will practice this in a later chapter.

Click here to go to the forum on this chapter!
Or click on one of the topics on this chapter to go there: Question about SizeInBytes Equation Hi - I had a question on some ambiguity I encounte...
This is what the XNA code should look like, with the integration of our own vertex format:
using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Net; using Microsoft.Xna.Framework.Storage; namespace XNAseries3 { public class Game1 : Microsoft.Xna.Framework.Game {
struct MyOwnVertexFormat { private Vector3 position; private Color color; public MyOwnVertexFormat(Vector3 position, Color color) { this.position = position; this.color = color; } public static VertexElement[] VertexElements = { new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0), new VertexElement(0, sizeof(float)*3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0), }; public static int SizeInBytes = sizeof(float) * (3 + 1); }
GraphicsDeviceManager graphics; GraphicsDevice device; Effect effect; Matrix viewMatrix; Matrix projectionMatrix; VertexBuffer vertexBuffer; VertexDeclaration vertexDeclaration; Vector3 cameraPos; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } protected override void Initialize() { graphics.PreferredBackBufferWidth = 500; graphics.PreferredBackBufferHeight = 500; graphics.IsFullScreen = false; graphics.ApplyChanges(); Window.Title = "Riemer's XNA Tutorials -- Series 3"; base.Initialize(); } protected override void LoadContent() { device = GraphicsDevice;
effect = Content.Load<Effect>
("effects"); SetUpVertices(); SetUpCamera(); }
private void SetUpVertices() {
MyOwnVertexFormat[] vertices = new MyOwnVertexFormat[3]; vertices[0] = new MyOwnVertexFormat(new Vector3(-2, 2, 0), Color.Red); vertices[1] = new MyOwnVertexFormat(new Vector3(2, -2, -2), Color.Green); vertices[2] = new MyOwnVertexFormat(new Vector3(0, 0, 2), Color.Yellow); vertexBuffer = new VertexBuffer(device, vertices.Length * MyOwnVertexFormat.SizeInBytes, BufferUsage.WriteOnly); vertexBuffer.SetData(vertices); vertexDeclaration = new VertexDeclaration(device, MyOwnVertexFormat.VertexElements);
} private void SetUpCamera() { cameraPos = new Vector3(0, 5, 6); viewMatrix = Matrix.CreateLookAt(cameraPos, new Vector3(0, 0, 1), new Vector3(0, 1, 0)); projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 1.0f, 200.0f); } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); base.Update(gameTime); } protected override void Draw(GameTime gameTime) { device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); effect.CurrentTechnique = effect.Techniques["Colored"]; effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xWorld"].SetValue(Matrix.Identity); effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Begin(); device.VertexDeclaration = vertexDeclaration;
device.Vertices[0].SetSource(vertexBuffer, 0, MyOwnVertexFormat.SizeInBytes);
device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); pass.End(); } effect.End(); base.Draw(gameTime); } } }
- Website design & XNA + DirectX code : Riemer Grootjans - ©2003 - 2008 Riemer Grootjans
|
|
|
|
|