I have come to inquire about instancing in XNA
I am a beginning XNA developer, only recently stepping up from 2D to 3D games.
I'm trying to draw a large number of cubes made solely out of vertices in code. As one might suspect, drawing a large number of these cubes causes quite a bit of stress on my computer.
As I was looking for a way to increase performance I came across the term "instancing".
Not knowing how instancing works in XNA 4.0, I've looked around for a tutorial suitable for someone of my level.
However, the only tutorial I've come across (http://blogs.msdn.com/b/shawnhar/archive/2010/06/17/drawinstancedprimitives-in-xna-game-studio-4-0.aspx) is a bit too advanced for me. I think he's using models, meshes and whatnot instead of vertices, so I can't figure out which piece of code is actually relevant to what I'm after.
Which is why I come to you. If someone could give me a simple (if possible) tutorial or code snippets explaining how to use instancing with cubes (or any figures) drawn with vertices in XNA 4.0, I'd be much obliged.
This is the simplest code snippet i could come up with. Its an adaptation of a code I made a couple of months ago to display some cubes, just like what you need, no models nor nothing fancy.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
namespace HardwareInstancing
{
public class Instancing
{
Texture2D texture;
Effect effect;
VertexDeclaration instanceVertexDeclaration;
VertexBuffer instanceBuffer;
VertexBuffer geometryBuffer;
IndexBuffer indexBuffer;
VertexBufferBinding[] bindings;
InstanceInfo[] instances;
struct InstanceInfo
{
public Vector4 World;
public Vector2 AtlasCoordinate;
};
Int32 instanceCount = 10000;
public void Initialize(GraphicsDevice device)
{
GenerateInstanceVertexDeclaration();
GenerateGeometry(device);
GenerateInstanceInformation(device, instanceCount);
bindings = new VertexBufferBinding[2];
bindings[0] = new VertexBufferBinding(geometryBuffer);
bindings[1] = new VertexBufferBinding(instanceBuffer, 0, 1);
}
public void Load(ContentManager Content)
{
effect = Content.Load<Effect>("InstancingShader");
texture = Content.Load<Texture2D>("default_256");
}
private void GenerateInstanceVertexDeclaration()
{
VertexElement[] instanceStreamElements = new VertexElement[2];
instanceStreamElements[0] =
new VertexElement(0, VertexElementFormat.Vector4,
VertexElementUsage.Position, 1);
instanceStreamElements[1] =
new VertexElement(sizeof(float) * 4, VertexElementFormat.Vector2,
VertexElementUsage.TextureCoordinate, 1);
instanceVertexDeclaration = new VertexDeclaration(instanceStreamElements);
}
//This creates a cube!
public void GenerateGeometry(GraphicsDevice device)
{
VertexPositionTexture[] vertices = new VertexPositionTexture[24];
#region filling vertices
vertices[0].Position = new Vector3(-1, 1, -1);
vertices[0].TextureCoordinate = new Vector2(0, 0);
vertices[1].Position = new Vector3(1, 1, -1);
vertices[1].TextureCoordinate = new Vector2(1, 0);
vertices[2].Position = new Vector3(-1, 1, 1);
vertices[2].TextureCoordinate = new Vector2(0, 1);
vertices[3].Position = new Vector3(1, 1, 1);
vertices[3].TextureCoordinate = new Vector2(1, 1);
vertices[4].Position = new Vector3(-1, -1, 1);
vertices[4].TextureCoordinate = new Vector2(0, 0);
vertices[5].Position = new Vector3(1, -1, 1);
vertices[5].TextureCoordinate = new Vector2(1, 0);
vertices[6].Position = new Vector3(-1, -1, -1);
vertices[6].TextureCoordinate = new Vector2(0, 1);
vertices[7].Position = new Vector3(1, -1, -1);
vertices[7].TextureCoordinate = new Vector2(1, 1);
vertices[8].Position = new Vector3(-1, 1, -1);
vertices[8].TextureCoordinate = new Vector2(0, 0);
vertices[9].Position = new Vector3(-1, 1, 1);
vertices[9].TextureCoordinate = new Vector2(1, 0);
vertices[10].Position = new Vector3(-1, -1, -1);
vertices[10].TextureCoordinate = new Vector2(0, 1);
vertices[11].Position = new Vector3(-1, -1, 1);
vertices[11].TextureCoordinate = new Vector2(1, 1);
vertices[12].Position = new Vector3(-1, 1, 1);
vertices[12].TextureCoordinate = new Vector2(0, 0);
vertices[13].Position = new Vector3(1, 1, 1);
vertices[13].TextureCoordinate = new Vector2(1, 0);
vertices[14].Position = new Vector3(-1, -1, 1);
vertices[14].TextureCoordinate = new Vector2(0, 1);
vertices[15].Position = new Vector3(1, -1, 1);
vertices[15].TextureCoordinate = new Vector2(1, 1);
vertices[16].Position = new Vector3(1, 1, 1);
vertices[16].TextureCoordinate = new Vector2(0, 0);
vertices[17].Position = new Vector3(1, 1, -1);
vertices[17].TextureCoordinate = new Vector2(1, 0);
vertices[18].Position = new Vector3(1, -1, 1);
vertices[18].TextureCoordinate = new Vector2(0, 1);
vertices[19].Position = new Vector3(1, -1, -1);
vertices[19].TextureCoordinate = new Vector2(1, 1);
vertices[20].Position = new Vector3(1, 1, -1);
vertices[20].TextureCoordinate = new Vector2(0, 0);
vertices[21].Position = new Vector3(-1, 1, -1);
vertices[21].TextureCoordinate = new Vector2(1, 0);
vertices[22].Position = new Vector3(1, -1, -1);
vertices[22].TextureCoordinate = new Vector2(0, 1);
vertices[23].Position = new Vector3(-1, -1, -1);
vertices[23].TextureCoordinate = new Vector2(1, 1);
#endregion
geometryBuffer = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration,
24, BufferUsage.WriteOnly);
geometryBuffer.SetData(vertices);
#region filling indices
int[] indices = new int [36];
indices[0] = 0; indices[1] = 1; indices[2] = 2;
indices[3] = 1; indices[4] = 3; indices[5] = 2;
indices[6] = 4; indices[7] = 5; indices[8] = 6;
indices[9] = 5; indices[10] = 7; indices[11] = 6;
indices[12] = 8; indices[13] = 9; indices[14] = 10;
indices[15] = 9; indices[16] = 11; indices[17] = 10;
indices[18] = 12; indices[19] = 13; indices[20] = 14;
indices[21] = 13; indices[22] = 15; indices[23] = 14;
indices[24] = 16; indices[25] = 17; indices[26] = 18;
indices[27] = 17; indices[28] = 19; indices[29] = 18;
indices[30] = 20; indices[31] = 21; indices[32] = 22;
indices[33] = 21; indices[34] = 23; indices[35] = 22;
#endregion
indexBuffer = new IndexBuffer(device, typeof(int), 36, BufferUsage.WriteOnly);
indexBuffer.SetData(indices);
}
private void GenerateInstanceInformation(GraphicsDevice device, Int32 count)
{
instances = new InstanceInfo[count];
Random rnd = new Random();
for (int i = 0; i < count; i++)
{
//random position example
instances[i].World = new Vector4(-rnd.Next(400),
-rnd.Next(400),
-rnd.Next(400), 1);
instances[i].AtlasCoordinate = new Vector2(rnd.Next(0, 2), rnd.Next(0, 2));
}
instanceBuffer = new VertexBuffer(device, instanceVertexDeclaration,
count, BufferUsage.WriteOnly);
instanceBuffer.SetData(instances);
}
//view and projection should come from your camera
public void Draw(ref Matrix view, ref Matrix projection, GraphicsDevice device)
{
device.Clear(Color.CornflowerBlue);
effect.CurrentTechnique = effect.Techniques["Instancing"];
effect.Parameters["WVP"].SetValue(view * projection);
effect.Parameters["cubeTexture"].SetValue(texture);
device.Indices = indexBuffer;
effect.CurrentTechnique.Passes[0].Apply();
device.SetVertexBuffers(bindings);
device.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, 24, 0, 12, instanceCount);
}
}
}
I've used THIS texture alongside with this shader:
float4x4 WVP;
texture cubeTexture;
sampler TextureSampler = sampler_state
{
texture = <cubeTexture>;
mipfilter = LINEAR;
minfilter = LINEAR;
magfilter = LINEAR;
};
struct InstancingVSinput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
};
struct InstancingVSoutput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
};
InstancingVSoutput InstancingVS(InstancingVSinput input, float4 instanceTransform : POSITION1,
float2 atlasCoord : TEXCOORD1)
{
InstancingVSoutput output;
float4 pos = input.Position + instanceTransform;
pos = mul(pos, WVP);
output.Position = pos;
output.TexCoord = float2((input.TexCoord.x / 2.0f) + (1.0f / 2.0f * atlasCoord.x),
(input.TexCoord.y / 2.0f) + (1.0f / 2.0f * atlasCoord.y));
return output;
}
float4 InstancingPS(InstancingVSoutput input) : COLOR0
{
return tex2D(TextureSampler, input.TexCoord);
}
technique Instancing
{
pass Pass0
{
VertexShader = compile vs_3_0 InstancingVS();
PixelShader = compile ps_3_0 InstancingPS();
}
}
which should be named InstancingShader.fx and placed in your Content folder.
Using it from your Game1 is as simple as calling:
instancing = new Instancing();
instancing.Initialize(this.GraphicsDevice);
instancing.Load(Content);
and in your Draw method:
instancing.Draw(ref camera.View, ref camera.Projection, GraphicsDevice);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With