2
\$\begingroup\$

I'm stuck trying to implement instanced mesh rendering in my project. Particularly because I am failing to understand how certain components actually function - and how the drawing routine is actually associating an effect with the instanced geometry. Being new to 3D devlopment this is turning into a pain in the ass for me - all of my previous experience had never expanded beyond 2D until now.

Although I understand C# Syntax - VB.net is a bit of a sentimental preference for me , so anything written in VB below is my code, and C# are sample references.

Before I carry on - I have tried to use http://www.float4x4.net/index.php/2011/07/hardware-instancing-for-pc-in-xna-4-with-textures/ as reference. However I am stuck trying to understand the 'InstanceBuffer', Vertex Bindings, and how I pass the effect I want to use - as im trying to do the same thing - but not with an atlas texture. I only want the transforms to be passed to my shader - and not with the generated cube geometry from the example - but instead copied from a model.

As seen in the above link - the author demonstrates instancing using generated cubes and "atlas' textures. I am trying to generate instanced geometry by first initializing from an existing model.

Firstly: Most examples I have found - always use a custom model importer , including Microsoft's sample - which in my case - I don't need or want - I instead have a class named InstancedModel - which contains a list of InstancedMeshPart objects - and is initialized from a Microsoft.XNA.Framework.Graphics.Model using the following methods:

    Friend Shared Function G_GET_MODEL_VERTICES_AND_INDICES(Model As Model, ByRef Out_InstancedMeshParts As List(Of InstancedMeshPart)) As Integer
        Dim Parts As Integer = 0

        Out_InstancedMeshParts = New List(Of InstancedMeshPart)

        For Each Mesh As ModelMesh In Model.Meshes
            For Each Part As ModelMeshPart In Mesh.MeshParts

                Dim Part_Vertices As List(Of VertexPositionTexture),
                    Part_Indices As List(Of Integer)

                G_GET_PART_VERTICES_AND_INDICES(Part, Part_Vertices, Part_Indices)

                Out_InstancedMeshParts.Add(
                    New InstancedMeshPart With {
                        .m_VERTICES = Part_Vertices.ToArray,
                        .m_INDICES = Part_Indices.ToArray,
                        .m_OriginalVertexDeclaration = Part.VertexBuffer.VertexDeclaration
                    }
                 )

                Parts += 1
            Next
        Next

        Return Parts
    End Function

    Friend Shared Sub G_GET_PART_VERTICES_AND_INDICES(Part As ModelMeshPart, ByRef Out_Vertices As List(Of VertexPositionTexture), ByRef Out_Indices As List(Of Integer))
        Dim verticeslist As New List(Of VertexPositionTexture)
        Dim indiceslist As New List(Of Integer)

        Dim OffsetInBytes = Part.VertexBuffer.VertexDeclaration.VertexStride * Part.VertexOffset

        Dim PartVertices(Part.NumVertices) As VertexPositionNormalTexture
        Dim PartIndices(Part.PrimitiveCount * 3) As UShort

        Part.VertexBuffer.GetData(OffsetInBytes, PartVertices, 0, Part.NumVertices, Part.VertexBuffer.VertexDeclaration.VertexStride)

        OffsetInBytes = Part.StartIndex * SizeOfShort

        Part.IndexBuffer.GetData(OffsetInBytes, PartIndices, 0, Part.PrimitiveCount * 3)

        indiceslist.AddRange(PartVertices)
        verticeslist.AddRange(PartVertices)

        Out_Vertices = verticeslist
        Out_Indices = indiceslist
    End Sub

After Vertices and Indices are copied from a model , in my InstancedMeshPart object I then initialize my Vertex buffer, Index Buffer and my Vertex Declaration

    Friend Sub InitializeGeometry(GraphicsDevice As GraphicsDevice)
        m_VertexCount = m_VERTICES.Length
        m_IndexCount = m_INDICES.Length

        m_GeometryBuffer = New VertexBuffer(GraphicsDevice, VertexPositionTexture.VertexDeclaration, m_VertexCount, BufferUsage.WriteOnly)
        m_GeometryBuffer.SetData(m_VERTICES)

        m_IndexBuffer = New IndexBuffer(GraphicsDevice, New Integer().GetType, m_IndexCount, BufferUsage.WriteOnly)
        m_IndexBuffer.SetData(m_INDICES)

        Const SizeOfFloat = 4

        Dim StreamElements As VertexElement() = {
            New VertexElement(0, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1),
            New VertexElement(SizeOfFloat * 4, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 2),
            New VertexElement(SizeOfFloat * 8, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 3),
            New VertexElement(SizeOfFloat * 12, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 4),
            New VertexElement(SizeOfFloat * 16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 5)
        }

        m_VertexDeclaration = New VertexDeclaration(StreamElements)
    End Sub

My StreamElements array is initialized the same way as the example in the link I mentioned above - however im not exactly sure if this is right since I don't want to pass the atlas texture stuff. I only need the transforms for the instances, so I assume I need to remove something - but im not sure how many elements I actually need.

Instead of using the "InstanceInfo" structure the article describes - I only want to pass the World Matrices in my instance buffer - so I am totally brainmashed on what I should be changing in my StreamElements arrray.

'  Positions is an array of matrices - not a structure.
m_InstanceBuffer = New VertexBuffer(GraphicsDevice, m_VertexDeclaration, Positions.Length, BufferUsage.WriteOnly)
m_InstanceBuffer.SetData(Positions)

I am also struggling to understand exactly how the VertexBufferBindings work :

// from sample:

VertexBufferBinding[] bindings;
bindings = new VertexBufferBinding[2];
bindings[0] = new VertexBufferBinding(geometryBuffer);
bindings[1] = new VertexBufferBinding(instanceBuffer, 0, 1);

Why am I passing the transormation matrices stored in the InstanceBuffer to the bindings and not through an EffectParameter? Were or How are the parameters being passed to my shader (eg the transforms for each mesh instance)?

In his example - im not sure how the graphics device knows that it has to use the effect specified below: I can't see any relation to the effect object and the DrawInstancedPrimitives. How is his code assigning this effect to the instance mesh he is rendering? Especially since there is no Meshpart.Effect property? Were am I telling the graphics device that I want to draw this instanced geometry with "MyEffect"?

// from sample: 

GraphicsDevice.Clear(Color.CornflowerBlue);

effect.CurrentTechnique = effect.Techniques["Instancing"];
effect.Parameters["WVP"].SetValue(view * projection);
effect.Parameters["cubeTexture"].SetValue(texture);

GraphicsDevice.Indices = indexBuffer;

effect.CurrentTechnique.Passes[0].Apply();

GraphicsDevice.SetVertexBuffers(bindings);

GraphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, 24, 0, 12, count);

and similarly from http://blogs.msdn.com/b/shawnhar/archive/2010/06/17/drawinstancedprimitives-in-xna-game-studio-4-0.aspx

// from sample: 


instanceVertexBuffer.SetData(instanceTransformMatrices, 0, numInstances, SetDataOptions.Discard);

graphicsDevice.SetVertexBuffers(modelVertexBuffer, new VertexBufferBinding(instanceVertexBuffer, 0, 1));
graphicsDevice.Indices = indexBuffer;
instancingEffect.CurrentTechnique.Passes[0].Apply();

graphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0,
                                       modelVertexBuffer.VertexCount, 0,
                                       indexBuffer.IndexCount / 3,
                                       numInstances);

In the above - It is obvious we are setting common EffectParameters for the effect and then applying it, but the issue being that I have no idea how or were the GraphicsDevice instance would know to use my effect.

Basic overview of what i cannot understand:

  1. How are my transforms ( in InstanceBuffer ) being passed to the shader?
  2. How or were does the graphics device know that I want to draw using a specific effect when trying to draw instanced geometry? It was easy when I was just looping ModelMeshParts before... but now my brain has been sucked into a black hole the moment I need to render several thousands of identical objects.
  3. Exactly what should I be removing from my 'VertexElements' variable - what happens to each element? Are they being passed to a shader? Am I right to assume I dont need the 5th element?

When I began trying to implement instancing I wanted to implement my instanced draw routine that accepts parameters like this. However because of what im stuck on - I cant fill this routine.

DrawInstanceModel(GraphicsDevice as GraphicsDevice, Model as InstancedModel, Effect as Effect , Positions as Matrix() )
\$\endgroup\$

2 Answers 2

0
\$\begingroup\$

Q: How are my transforms ( in InstanceBuffer ) being passed to the shader?

A: By binding an additional instance stream.

In the example you referred to it's these lines:

VertexBufferBinding[] bindings;
bindings = new VertexBufferBinding[2];
bindings[0] = new VertexBufferBinding(geometryBuffer);
bindings[1] = new VertexBufferBinding(instanceBuffer, 0, 1); // <- instance data
...
GraphicsDevice.SetVertexBuffers(bindings);

Q: How does the graphics device know that I want to draw using a specific effect when trying to draw instanced geometry?

A: By biding the effect to the device, setting up shared parameters before the draw call and passing per-instance effect parameters in the instance data stream.

Q: Exactly what should I be removing from my 'VertexElements' variable - what happens to each element? Are they being passed to a shader? Am I right to assume I dont need the 5th element?

A:

Let's have a look how you would go about rendering the same mesh twice without instancing:

  1. Bind the vertex and index buffers to the device
  2. Bind the effect (shader) to the device
  3. Set the world matrix for the effect to the first instance's world matrix
  4. Draw the mesh
  5. Set the world matrix for the effect to the second instance's world matrix
  6. Draw the mesh

If you wanted to draw 100 instances of the same mesh at different world transforms, you would thus have to repeat steps 4 and 5 for each instance.

What's so bad about that?

Well, you have to issue at least one shader constant switch and a draw call per model and minimizing these numbers usually helps improving performance.

Now, wouldn't it be nice if we could just fill some buffer with the 100 world space matrices we'd like our meshes to be rendered to and just pass that buffer to a single draw call?

Instancing does just that.

It allows you to issue a single draw call that will repeat the rendered primitives a given number of times while advancing and addional vertex stream only every Nth primitive (once after each mesh).

In our sample case the code would go like this:

  1. Bind the vertex and index buffers to the device, containing the mesh geomtry
  2. Bind the effect (shader) to the device
  3. Bind an additional vertex buffer to the device, containing 100 world space matrices
  4. Draw the mesh 100 times with instancing

In the shader you then receive the geometry data (the mesh vertex in its local space) and the instance data (the world space matrix) and can use both to render the instance.

This means we can now render 100 identical meshes at different world positions in one go.

What if you'd like the models to use different textures?

We can do that as well by filling our additional (per-instance) vertex buffer with structs where the world space matrix is just one field and some other field might indicate which texture to use.

In the sample you refered to that's called InstanceInfo.

\$\endgroup\$
3
  • \$\begingroup\$ Thank you for the response. ok. So from my understanding also - and to clarify: - I would need to update the position instances ( matrix list containing the tranformed positions of each model instance ) before drawing on every frame ( on update() ) - instead of passing world transformations to my geometry shader using EffectParameter.Setvalue("world"), the instancebuffer can be used instead , which would be accessed as POSITION0 in the shader for each instance? \$\endgroup\$ Commented Jul 23, 2013 at 14:39
  • \$\begingroup\$ Yes, exactly that. And, of course, you have to modify the shader to read the matrix from the second vertex stream instead of the "world" constant. \$\endgroup\$
    – SmoCoder
    Commented Jul 25, 2013 at 16:26
  • \$\begingroup\$ Several Months later... : Thanks to the answer you supplied, my geometry pipeline if fully implemented and works perfectly, and am able to work with instancing and various other capabilities without any issues now. My game engine has evolved alot after this. \$\endgroup\$ Commented Jan 3, 2014 at 23:06
0
\$\begingroup\$

I don't know XNA, so I'm going to just talk like we're doing OpenGL. This all translates relatively easily into D3D, and should hopefully thus translate into XNA easily as well.

Instanced rendering is basically taking this:

glDrawArrays()

And turning it into this:

for i = 0 .. numInstances:
  __glSetInstanceId(i)
  glDrawArrays()

Except done in hardware/driver instead of the application, of course.

In your shader, you get a new uniform called glInstanceID which is the number of the instance being drawn. All of your other uniforms in the single case now need to be in an array of some kind that you can index with glInstanceID or be part of a new vertex stream that is automatically updated once per model.

When you call glVertexAttribDivisor you can specify that a particular vertex attribute in a bound VBO is only updates once every N instances (usually you set this to 0 for attributes shared by all instances and 1 for attributes that vary per-instance).

If you're rendering 10 models and need 10 positions, you create a VBO holding 10 positions and then bind it with glVertexAttribPointer. You can then use glVertexAttribDivisor to set its instance update rate to 1. Now this attribute in your vertex shader will be sourced per instance; that is, the attribute won't change for each vertex, but it will change whenever a different instance is being drawn.

An alternative, which is necessary for OpenGL implementations that don't yet support glVertexAttribDivisor, is to store your per-instance information in either a uniform buffer object or a texture buffer object. With a uniform buffer you just acess it like a regular array. With a texture buffer you use texelFetch in your shader to access them. Use gl_InstanceID as the index.

With D3D, you don't use a separate call to set the divisor, it's part of the D3D11_INPUT_ELEMENT_DESC. The shader calls to access texture buffers are different and you use constant buffers as the analog to a uniform buffer. Otherwise, same thing.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .