Sign in to follow this  

[Shader 3.0, SlimDX] Instancing

This topic is 2660 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello!

In order to reduce the number render passes ive read that it may help to use instancing for repeatable types of objects. As in my application a lot of models are exact duplicates of each-other (which may only vary in scale) i think that this technique is pretty good.

Now my problem is that i didnt really understand how this works. Ive looked through the SDK-Sample but its a bit awkward they merged several different ways into one smaple/shader/project. So i didnt really get all the points of it.

It would be really nice if someone could give me some hints where to start, what the concept behind it is and maybe some simple examples with code.

Little summary what i plan:
I have a class that handles caching of the same objects (they may be used bout 100 times in the same scene). From there id like to have a class that batch-renders all the instances of that object. The number of instances may vary depending on the players position and the instances may vary in size (that should be a pretty easy thing to do in the shader).

My ideas so far:
I use the function ID3DXEffect9::SetVectorArray to set all the positions of the instances in the shader (and maybe another for the scales). I render an object that is aligned to the 0-point and move it for each instance.

Greetings and big thanks
Plerion

Share this post


Link to post
Share on other sites
SM3.0-style hardware instancing (where you put the instance data in a second vertex stream) is preferable over shader constant instancing (where you put the instance data in an array of shader constants that you index into). It's more flexible, there's less overhead in locking a resource than in setting shader constants, you're not limited in the number of instances you can draw, the shader performance is better, and you don't have to modify the geometry you're rendering. The only reason to use shader constant instancing is if you're supporting SM2.0 GPU's.

Here's some basic steps you can go through to get instancing implemented:

1. Start with your vertex shader, and figure out exactly what data you need. Usually a world matrix is all you need at first. Add the instance data to your vertex shader inputs, and assign it a semantic you don't need (remember that a float4x4 will require 4 adjacent semantic slots). If you have a lot of instance data, it can help to declare the instance data in a separate struct. Like this:

struct VertexData
{
float4 Position : POSITION;
float2 TexCoord : TEXCOORD0;
};

struct InstanceData
{
float4x4 World : TEXCOORD1; // Will occupy TEXCOORD1 - TEXCOORD4
float4 Color : TEXCOORD5;
};

VSOutput VSMain(in VertexData vertexData, in InstanceData instanceData)
{
....
}


2. Based on the instance data you need come up with a vertex declaration for your geometry that includes all of your geometry elements as well as the instance data elements. Make sure that you put the instance data elements in stream 1 instead of stream 0, and make sure that you start the offset at 0 again when you start listing them.

3. Simplest way to start things off at runtime is to just make a single large dynamic vertex buffer to use for your instance data. Pick some max number of instances and multiply it with the size of your instance data, and use that for the size. Make sure you put it in the Default pool, with the Dynamic and WriteOnly usage flags set.

4. Declare a struct in your app code that matches the layout of your instance data. So in my example above, give it a Matrix and Vector4. Then for any geometry you want instanced keep a List of that struct, and each frame remove all of the elements and then add in the instance data for each instance you want to render. Then when it's time to render all of those instances, lock your big dynamic VB and fill it with the instance data. This way the number of instances per frame can be dynamic.

5. After filling the vertex buffer, it's time to draw. First set stream 0 with your primary vertex buffer the same way you always would, and also set the indices (instanced geometry has to be indexed). Then set stream 1 with your instance data vertex buffer. Once you've done that, you need to specify how many instances you want to draw. This is done with the somewhat-cryptic SetStreamSourceFrequency method. What you do is you set the number of instances by specifying stream 0, the number of instances as the frequency, and IndexData as the source. Then you specify that stream 1 has instance data by calling it again with stream 1, 1 as the frequency, and InstanceData as the source. Then you just draw like your normally would. When you want to go back to non-instanced rendering, call ResetStreamSourceFrequency.

Share this post


Link to post
Share on other sites
Hey MJP!

Thank you for your explanation. I think the concept and also the basic usage is pretty clear, though sadly i cant see anything on the screen in my test project with just a cube that uses the following code:

[StructLayout(LayoutKind.Sequential)]
public struct Vertex3FC
{
public float x, y, z;
}

[StructLayout(LayoutKind.Sequential)]
public struct InstanceData
{
public Matrix matTranslate;
}

static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

Form1 f = new Form1();
Direct3D d3d = new Direct3D();

Device dev = new Device(d3d, 0, DeviceType.Hardware, f.Handle, CreateFlags.HardwareVertexProcessing, new PresentParameters
{
Windowed = true,
BackBufferFormat = Format.A8R8G8B8,
BackBufferHeight = f.ClientSize.Height,
BackBufferWidth = f.ClientSize.Width,
EnableAutoDepthStencil = true,
AutoDepthStencilFormat = Format.D16,
SwapEffect = SwapEffect.Discard,
}
);

#region Vertices
Vertex3FC[] Vertices = new Vertex3FC[]
{
...
};
#endregion

ushort[] Indices = new ushort[]
{
...
};

InstanceData[] instances = new InstanceData[]
{
new InstanceData() { matTranslate = Matrix.Translation(0.0f, 3.0f, 0.0f) },
new InstanceData() { matTranslate = Matrix.Translation(3.0f, 0.0f, 0.0f) },
};

VertexElement[] instDecl = new VertexElement[]
{
new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0),
new VertexElement(1, 0, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 0),
new VertexElement(1, 4, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 1),
new VertexElement(1, 8, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 2),
new VertexElement(1, 12, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 3),
VertexElement.VertexDeclarationEnd,
};

VertexBuffer buffer = new VertexBuffer(dev, 8 * Marshal.SizeOf(typeof(Vertex3FC)), Usage.None, VertexFormat.Position, Pool.Managed);
DataStream strm = buffer.Lock(0, 0, LockFlags.None);
strm.WriteRange(Vertices);
buffer.Unlock();

IndexBuffer ibuff = new IndexBuffer(dev, (12 * 3) * 2, Usage.None, Pool.Managed, true);
strm = ibuff.Lock(0, 0, LockFlags.None);
strm.WriteRange(Indices);
ibuff.Unlock();

VertexBuffer attrib = new VertexBuffer(dev, (16 * 4) * 2, Usage.None, VertexFormat.Texture4, Pool.Managed);
strm = attrib.Lock(0, 0, LockFlags.None);
strm.WriteRange(instances);
attrib.Unlock();

Matrix matLook = Matrix.LookAtLH(new Vector3(50, 0, 50), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
Matrix matProj = Matrix.PerspectiveFovLH((float)(45.0f * Math.PI / 180.0f), (float)f.ClientSize.Width / (float)f.ClientSize.Height, 0.1f, 500.0f);
dev.SetTransform(TransformState.Projection, matProj);
dev.SetTransform(TransformState.View, matLook);

Effect eff = Effect.FromMemory(dev, Resource1.CubeShader, ShaderFlags.OptimizationLevel3);
EffectHandle hdl = eff.GetParameter(null, "matViewProj");
eff.SetValue(hdl, matLook * matProj);

VertexDeclaration decl = new VertexDeclaration(dev, instDecl);
dev.VertexDeclaration = decl;
dev.SetRenderState(RenderState.ZEnable, true);
dev.SetRenderState(RenderState.CullMode, Cull.None);
dev.SetRenderState(RenderState.Lighting, false);

MessagePump.Run(f,
() =>
{
dev.BeginScene();
dev.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0);

dev.SetStreamSource(0, buffer, 0, 12);
dev.SetStreamSourceFrequency(0, 2, StreamSource.IndexedData);
dev.Indices = ibuff;

dev.SetStreamSource(1, attrib, 0, 16 * 4);
dev.SetStreamSourceFrequency(1, 1, StreamSource.InstanceData);

int passes = eff.Begin();
for (int i = 0; i < passes; ++i)
{
eff.BeginPass(i);
dev.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);
eff.EndPass();
}
eff.End();

dev.ResetStreamSourceFrequency(0);
dev.ResetStreamSourceFrequency(1);

dev.EndScene();
dev.Present();
}
);

dev.Dispose();
d3d.Dispose();
f.Dispose();
}
}



And the shader:

float4x4 matViewProj;

struct vsInput
{
float4 position : POSITION0;

float4 mat0 : TEXCOORD0;
float4 mat1 : TEXCOORD1;
float4 mat2 : TEXCOORD2;
float4 mat3 : TEXCOORD3;
};

struct vsOutput
{
float4 position : POSITION0;
float4 color : COLOR0;
};

vsOutput VertexShaderI(vsInput inp)
{
float4x4 modelMatrix =
{
inp.mat0,
inp.mat1,
inp.mat2,
inp.mat3
};

float4 instancePos = mul(inp.position, modelMatrix);
vsOutput outp = (vsOutput)0;
outp.position = mul(instancePos, matViewProj);
outp.color = float4(1, 1, 1, 1);
return outp;
}



I guess my problems could be either where i create the instance-data-buffer or with the vertex-declaration. But i dont really see what im doing wrong (used VertexFormat so far, so i dont know exactly if i understood the VertexElement/VertexDeclaration correct).

Greetings
Plerion

Share this post


Link to post
Share on other sites
Never mind about the visibility, just as i wrote the above post i had the idea that "offset" in the VertexElement could mean in bytes, not an index and now it works :D

Share this post


Link to post
Share on other sites
Arw, implementing it into my main project seems to be a bit harder than i thought :D

It seems that something with my ModelMatrix seems to be wrong. Here are two versions of the shader:

PixelInput MeshShader(VertexInput input)
{
PixelInput retVal = (PixelInput)0;

//float4 instancePos = mul(input.Position, input.ModelMatrix);

retVal.Position = mul(input.Position, matrixViewProj);
retVal.TextureCoords = input.TextureCoords;
retVal.Depth = 0.0f;//CalcDepth(instancePos.xyz);

return retVal;
}




PixelInput MeshShader(VertexInput input)
{
PixelInput retVal = (PixelInput)0;

float4 instancePos = mul(input.Position, input.ModelMatrix);

retVal.Position = mul(instancePos, matrixViewProj);
retVal.TextureCoords = input.TextureCoords;
retVal.Depth = 0.0f;//CalcDepth(instancePos.xyz);

return retVal;
}



Version 1 works perfectly and the mesh is rendered correctly (of course all instances are on the same point), but version 2 is a complete mess. Triangles range into infinite and have no real order!

Thats my declaration:

private static VertexElement[] ElemDecl = new VertexElement[]
{
new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0),
new VertexElement(0, 12, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Normal, 0),
new VertexElement(0, 24, DeclarationType.Float2, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 0),
new VertexElement(1, 0, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 1),
new VertexElement(1, 16, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 2),
new VertexElement(1, 32, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 3),
new VertexElement(1, 48, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 4),
new VertexElement(1, 64, DeclarationType.Float1, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 5),
VertexElement.VertexDeclarationEnd,
};



and the according structures:

[StructLayout(LayoutKind.Sequential)]
public struct MdxVertex
{
public float X, Y, Z;
public float NX, NY, NZ;
public float U, V;
}

[StructLayout(LayoutKind.Sequential)]
public struct MdxInstanceData
{
public SlimDX.Matrix ModelMatrix;
public float Scale;
}



And the structures in the shader:

struct PixelInput
{
float4 Position : POSITION0;
float2 TextureCoords : TEXCOORD0;
float Depth : TEXCOORD1;
};

struct VertexInput
{
float4 Position : POSITION0;
float3 Normal : NORMAL;

float2 TextureCoords : TEXCOORD0;
float4x4 ModelMatrix : TEXCOORD1;
float Scale : TEXCOORD5;
};



Is the error somewhere there?

Greetings
Plerion

Share this post


Link to post
Share on other sites

This topic is 2660 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this