Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

GPU Instancing

Discussion in '5.6 Beta' started by MrEastwood, Jan 21, 2017.

  1. MrEastwood

    MrEastwood

    Joined:
    Dec 9, 2013
    Posts:
    19
    We're trying to squeeze all the performance we can out of instancing, but we've noticed the system misses a lot of chances to instance meshes together. It's already much improved lately, and the new workflow on b4 looks simpler. However, currently we're trying to use MaterialPropertyBlocks to introduce color variation, and when we do instancing breaks.

    Below i have made a test scene that spawns 120 instances of 2 meshes (so about 60 instances of each). We set material properties on all of them. The only difference is that in the first screenshot the proprties are identical, whereas in the second screenshot the properties are unique to each object.

    tmp_12191-GoodInstance-1325429275.png

    tmp_12191-BadInstance301860031.png


    In the first case there are 6 drawcalls. The objects seem to he grouped into 3 distance bands, which makes sense because opaque geometry is sorted front to back. So with 2 meshes in 3 bands, 6 calls are normal.

    In the second case, there are 60 drawcalls, sometimes more. When meshes do end up getting instanced together, they typically form groups of 2 or 3 meshes, sometimes up to 5 and many times they don't instance at all. Also the front to back sorting seems to have gone out the window. As an example i encircled a group of meshes that are instanced together but should be in different distance bands.

    I want to stress again that literaly the only difference is the uniqueness of the properties. The top example uses the same shader, the same meshes, and its material property blocks have the same properties, only the uniqueness of the values differ. So is this a bug or is there something at play i'm not aware of? Can i get the color variations to work with instancing?
     
  2. ShilohGames

    ShilohGames

    Joined:
    Mar 24, 2014
    Posts:
    3,021
    What I have found with Unity GPU Instancing is that you cannot rely on it to automatically choose what to instance. I have scenes with thousands of identical objects (with the same material and an instanced shader) and Unity could not reliably instance them. You cannot trust Unity's automatically instancing any more than you can trust Unity's dynamic batching. Sometimes the automatic instancing will group hundreds of items, while other times it might only group a couple items.

    Instead of letting Unity automatically choose what to instance, you need to use the Graphics.DrawMeshInstanced method that was added in Unity 5.5. You can force Unity to instance things this way, and it works absolutely awesome. I have gotten massive (6x to 10x) performance gains in my own game using Graphics.DrawMeshInstanced instead of trying to rely on automatic instancing.

    Here is a video of my game with thousands of projectiles handled using Graphics.DrawMeshInstanced and I am getting about 100-200 fps while playing at 1080p. There is a frame rate counter in the upper left in the video.

     
    Last edited: Jan 21, 2017
    laurentlavigne likes this.
  3. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,609
    That would probably be too simple, but is the color defined as an instanced property, using the
    UNITY_DEFINE_INSTANCED_PROP macro?
    See GPU Instancing for details.

    Depending on how many ínstanced objects you plan to have in the scene, it can be from advantage to drop the idea that each individual grass thingy exists as a GameObject. I did some editor performance tests recently and the amount of GameObject's in a scene has a significant impact on the time the editor requires to enter and exit playmode. If you like to iterate on game features in the editor often, you most likely want to keep this time to a minimum. Adding a few thousand GameObject's can cause the editor to freeze several minutes every time you press play.
    See this post for details.

    It can be from advantage to issue Graphics.DrawMeshInstanced calls yourself, by-passing the GameObject overhead, as @ShilohGames suggested.

    Another approach can be to bake some bigger generic grass chunks and render those. I have had great success using that technique in the past for various purposes.
    See this post for details and an example implementation.
     
    Last edited: Jan 22, 2017
    laurentlavigne likes this.
  4. MrEastwood

    MrEastwood

    Joined:
    Dec 9, 2013
    Posts:
    19
    A guy can hope, can't he? :p


    Yes, it is. This is of course why i had the first example, so you know the fault should not be in the shader.

    That is basically batching, right?

    Anyway, i took both your advice and went with Graphics.DrawMeshInstanced, and this is more like it!
    ManualInstance.PNG
    Only one drawcall per mesh type. Now that i know that this works and how it works, i can get to work on integrating this into the main project. We're going to have to do a lot of administration keeping on the array of matrices, material properties and culling. Luckily we were already using the culling group API, but i'm still hoping the extra cpu work won't negate the gpu savings :oops:

    Anyway, thank you both for pointing me in the right direction!
     
  5. ShilohGames

    ShilohGames

    Joined:
    Mar 24, 2014
    Posts:
    3,021
    Excellent. Good luck with your project.

    The Graphics.DrawMeshInstanced method is fantastic. What I do in my project is create a fixed size array that I reuse for every Graphics.DrawMeshInstanced call. I use an array of 500 Matrix4x4. Then I have a loop that feeds the information about my objects (laser projectiles in my games) from an array of a custom struct (with details about each laser projectile) into the Matrix4x4 array.

    Since my game has thousands of projectiles at a time, my loop fills the 500 unit Matrix4x4 multiple times and submits each group of 500 objects using Graphics.DrawMeshInstanced. Then I have code handle the remaining objects using the same 500 size Matrix4x4 array but using the 'count' parameter in the Graphics.DrawMeshInstanced method to limit the call to only the remaining items.

    That way I can keep re-using my 500 unit array over and over, even when there are less than that many objects to draw. This way I avoid the need to create or re-size an array per frame. I just create the array at startup and then re-use the existing array each frame. The performance is awesome this way.
     
    laurentlavigne likes this.
  6. yuanxing_cai

    yuanxing_cai

    Unity Technologies

    Joined:
    Sep 26, 2014
    Posts:
    335
    Setting color variations this way definitely should work. Can you please file a bug and attach your repro project?
     
    Peter77 likes this.
  7. MrEastwood

    MrEastwood

    Joined:
    Dec 9, 2013
    Posts:
    19
    Well now.... i started isolating the scene that produced the screenshots, and everything worked as it should. I then went back to the test scene in the full project and i couldn't reproduce it there either. I did upgrade to b5 in the meantime but the issue also did not show up on my other computer running b3. So... i guess it's solved then? :) Anyway my main project now uses "manual" instancing, and not having to instantiate and manage a bunch of gameobjects is a very nice to have, so im sticking with this approach. Thanks again to ShilohGames and Peter77 for suggesting this :-D
     
    Peter77 likes this.