XNA Light Pre-Pass: Instancing

Almost anyone out there wants to output as many meshes/triangles/details as possible. We know that individual draw calls can hurt performance, so it would be nice if there was a way to draw lots of meshes using a single draw call. Well, that is possible, and it’s named geometry instancing. A good starting point can be found here.

Basically, we chose our mesh to be rendered (hundred, thousands of copies), and we fill an additional array to hold the per-instance data. This data usually is the world transform of each individual instance of that mesh, and some other information like color, texture offset (to access a different part of a texture atlas, i.e.) etc. In the rendering step, we set this additional array as a secondary vertex buffer and call a single draw call using the number of copies as an argument. It’s REALLY easy (more than I thought it would be). Take a look at this code snippet:

// Create a vertex declaration according to the per-instance data layout.
// In my case, I’m only using a world transform, so 4 vector4 (or a float4x4) is enough
VertexDeclaration _instanceVertexDeclaration = new VertexDeclaration
        (            
newVertexElement(0,  VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 0),            
newVertexElement(16, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1),            
newVertexElement(32, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 2),            
newVertexElement(48, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 3)
        );
// Set our per-instance data to the additional vertex buffer. In this example, it’s just an array of matrixes
_instanceVertexBuffer.SetData(_instanceTransforms, 0, _subMeshes.Count, SetDataOptions.Discard);
// Bind both mesh vertex buffer and our per-instance data
graphicsDevice.SetVertexBuffers
newVertexBufferBinding(meshPart.VertexBuffer, meshPart.VertexOffset, 0),
newVertexBufferBinding(_instanceVertexBuffer, 0, 1)
);
// Use a different Draw* method
graphicsDevice.DrawInstancedPrimitives(
PrimitiveType.TriangleList, 0, 0,
meshPart.NumVertices, meshPart.StartIndex,
meshPart.PrimitiveCount, _subMeshes.Count);
 

In my renderer, all you need to do is call “mesh.SetInstancingEnabled(true)”, and the code will take care of grouping the visible meshes (I named this instancing groups) according to their sub-meshes. The instancing technique is used in all the 3 stages: shadow generation, render to GBuffer and reconstruct lighting stage. The main shader was changed because when we use instancing, we get the world transform (and any other additional per-instance data) from an input and not from the default shader parameter.

Instancing offers a huge improvement in speed, and you can enable/disable the instancing in the code to check the difference. By the way, the code is here. Use it at your own risk!

That is it, see you next time!

-J.Coluna

About jcoluna

Game developer and musician
This entry was posted in XNA and tagged , , , , , . Bookmark the permalink.

27 Responses to XNA Light Pre-Pass: Instancing

  1. Neil Knight says:

    I heard that hardware instancing doesn’t work on the Xbox, is this still the case?

    • Chris says:

      From what I’ve read, it might still have a problem with meshes that have multiple mesh parts. However in practice, I rarely have come across a model whose meshes have more than one mesh part.

  2. Now we got a ton of small detail. Care to add FXAA next time?🙂

  3. Chris says:

    Sweet. Yeah, it’s pretty easy once you start once. You should try instancing to render hundreds of point lights in your scene.

  4. Pingback: Windows Client Developer Roundup 085 for 11/28/2011 - Pete Brown's 10rem.net

  5. gruddo says:

    Really like your blog. It’s one of the best. Well done.

    Minor tip: right click the using statements and remove and sort🙂

  6. gruddo says:

    Could you perhaps show an example of instanced skinned meshes. Maybe 100 dudes?

    • jcoluna says:

      I don’t like the solutions I’ve seen for that, so I think I’m not doing that soon. The solutions I’ve seen so far rely on rendering the animations to a texture (all the keyframes), and then reading the texture on the vertex shader. Take a look at this

      http://www.ionixxgames.com/Samples/SkinnedModelInstancing.pdf

      Feel free to try it and share, though =)
      -J.Coluna

      • Skinned Instancing can be done in several ways.
        in XNA, VFetch is easiest way which gives you direct access to vertices and information of instances, but unfortunately VFetch is only available on the XBox360.
        Xna 4.0 comes with hardware instancing may be there is solution in that case…but i think on xna you are so limited for doing this. may be native diretcX is better choice.

        Best regards.

  7. Simone says:

    Hi…i have question…would u help me plz?
    Your shadow is ok with First person Camera…its very amazing.
    but I want to test Chase Camera with your shadow, but i have problem!
    how could i get Transform parameter from camera for showing shadows.
    my update code of chase camera is here:

    public void UpdateChase(GameTime gameTime)
    {
    float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
    Vector3 chasePosition = BindTo != null ? BindTo.Position : Vector3.Zero;
    this._chaseDirection = new Vector3((float)Math.Cos(180 – Rotation), 1,(float)Math.Sin(180 – Rotation));

    Matrix transform = Matrix.Identity;
    transform.Forward = _chaseDirection;
    transform.Up = this.UpVec;
    transform.Right = Vector3.Cross(this.UpVec, _chaseDirection);

    _desiredPosition = chasePosition + Vector3.TransformNormal(DesiredPositionOffset, transform);
    this.target = chasePosition + Vector3.TransformNormal(_lookAtOffset, transform);

    Vector3 stretch = this.position – _desiredPosition;
    Vector3 force = -_stiffness * stretch – _damping * _velocity;
    Vector3 acceleration = force / _mass;
    _velocity += acceleration * elapsed;
    this.position += _velocity * elapsed;

    this.view = Matrix.CreateLookAt(this.position, this.target, Vector3.Up);
    this.frustum.Matrix = view * projection;

    this.Transform= ????? ////////////////any help????
    }

    I’m very thankful if u’ll guide me!…thanks alot man.

  8. Simone says:

    I forgot to say that if DesiredPositionOffset = Vector3.Zero
    It’s Ok and with
    this.Transform = Matrix.CreateTranslation(this.position)
    but if DesiredPositionOffset is any value except zero the shadows wont show
    Thank u for your consideration.

  9. Simone says:

    I Solved it…by the way thank u.
    heres the final code:
    public void UpdateChase(GameTime gameTime)
    {
    float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
    yaw = BindTo.Rotation.Y;//BindTo is the pointer to character position
    Matrix ypr_Matrix = Matrix.Identity * Matrix.CreateFromYawPitchRoll(yaw, pitch, roll);

    // Updates the camera position relative to the model Matrix
    _desiredPosition = BindTo.Position + Vector3.TransformNormal(DesiredPositionOffset, ypr_Matrix);
    Vector3 stretch = this.position – _desiredPosition;
    Vector3 force = -_stiffness * stretch – _damping * _velocity;
    Vector3 acceleration = force / _mass;
    _velocity += acceleration * elapsed;
    this.position += _velocity * elapsed;

    this.target = this.position + ypr_Matrix.Forward;
    this.world = ypr_Matrix * Matrix.CreateTranslation(this.position);
    this.view = Matrix.CreateLookAt(this.position, this.target, Vector3.Up);
    this.frustum.Matrix = view * projection;
    }

  10. Mick says:

    Really a magnificent job!! Thanks a lot!!
    Could you please try implementing Inverse Kinematics? pleeeeaseeee🙂

  11. Feedidy says:

    Can you think of a decent way to change mesh effect parameters at runtime?

    I’ve got a generic mesh and I apply level-specific diffuse maps on to it through code, but because it’s in a struct the write back is killing performance.

  12. robot97 says:

    Can you implement terrain rendering next time?

  13. angrywasp says:

    Nice work so far. The renderer is pretty easy to work with too. I got some water going with it. i uploaded a short video to

    just got to get the instanced meshes to render to the reflection/refraction maps properly now.

    Thanks for your hard work.

  14. Graf Seismo says:

    I really would recommend you to use StyleCop(http://stylecop.codeplex.com/) in combination with Resharper(http://www.jetbrains.com/resharper/) for your great tutorials to enhance read- and us-ability. For easier creating xml conmments you also can use GhostDoc(http://submain.com/products/ghostdoc.aspx).
    Please go on with this really nice series. *many thumb ups*

  15. Laszlo Fuleki says:

    Hello there! I`ve been messing around with this project for a day now, but when I try to add a point light, it doesn`t light my models at all for some reason. Directional lights and everything else (didn`t try spots) work fine though. I`m using this snippet to add the light, is there anything I`m missing here? (Already checked the cam`s position vector, it`s correct and passed correctly)

    l = new Light();
    lightpos = new Vector3(_cameraController.Position.X, _cameraController.Position.Y – 1.3f,
    _cameraController.Position.Z);
    l.Transform = Matrix.CreateTranslation(lightpos);
    l.Color = Color.White;
    l.Intensity = 3;
    l.Radius = 5;
    // l.CastShadows = false;
    _renderWorld.AddLight(l);

    • jcoluna says:

      Hmm can’t find the error, sorry! Try to increase the radius, and make sure you have the latest code. Thanks for visiting!
      -J.Coluna

      • Laszlo Fuleki says:

        I found the error.🙂 I had modified the content processor codepreviously and it messed up my normals really bad. By the way thank you very much for the released codes, I`m learning a lot of HLSL from them.🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s