Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

GPU instancing vs Sending data through uv channels to reduce drawcalls

Discussion in 'General Graphics' started by mahdiii, Apr 12, 2020.

  1. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    Hi. To render objects efficiently and reduce overdraw, I decided to convert 2D rectangular sprites (with default sprite transparent shader) to 3D quads (with opaque shader). It is OK and I see they are rendered from front to back (zwrite on).
    I have spritesheets instead of textures but mesh renderers can not render sprites by default.
    Therefore, I wrote a custom shader to get sprite bounds and crop it correctly (Sending min and max rect point to the shader)
    How can I reduce drawcalls (batching) and draw all sprites in a spritesheet in one time?
    1- Using GPU instancing and material property block -> vector4 (min and max point)
    2- Send the vector4 to the shader using uv1 and uv2 meshes.
    I have implemented both of them and they have achieved good batching and reduced drawcalls.
    Which one is more efficient?
    And in batching (meshes with same opaque shaders), how are they rendered (again front to back and reduce overdraw)?
    I mean in batching, does it consider the rendering order and overdraw?
     
    Last edited: Apr 12, 2020
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Or just do what the Sprite Renderer does and modify the main UVs with the appropriate offsets before hand.

    Instancing requires all meshes that are being rendered are the same mesh, hence why you have to store the data in an extra Vector property.

    Batching just requires all of the meshes are using the same material. It doesn't care if they're completely different meshes. If you're making modifications to the extra UVs sets to store data on how to modify the main UVs, just modify the main UVs. Plus each UV set is a full Vector4 so there's no reason to pass the data with two different UV sets. If for some reason you needed the mesh's original 0.0 to 1.0 UVs and the sprite specific UVs, you can pass both of those in the first UV set as the xy and zw components of the Vector4.


    Now, which is more efficient? Neither, both ... it depends.

    If you need "a lot" of the same mesh & material to be rendered and move dynamically, instancing is almost always faster.
    If you need not as many as "a lot" of that same mesh, or you have several different simple meshes, or they don't need to move often or at all, then batching is faster.

    How many "a lot" is depends on the hardware and other things in the scene. Every platform and gpu / cpu combo you use the cutoff for which is faster for rendering instanced or batched is going to change. Could be a few hundred, could be a few thousand. Pick one solution and move on, or test them both and find out which is faster for you and just do that.

    For transparent objects it does seem to care about the draw order. For opaque objects it does not seem to care as much and for dynamically batched stuff the order seems almost random. For static batched meshes it does (mostly) draw them front to back, but Unity sorts meshes by several different metrics than means it's never purely front to back.
     
    mahdiii likes this.
  3. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    Thanks.
    I am confused. I send uv data or use GPU instancing only to reduce draw calls.
    The meshes are the same and they are quads. So I can apply gpu instancing or sending uv data to decrease drawcalls.
    You are right and I can set main uv data.
    My quads are not static and sometimes move (can be dragged and dropped). They are big and overlapped.
    I want to reduce overdraw as well so I would like to render them front to back (utilize z buffer). You explained when I have batching (dynamic), the order is random, hence, it can cause overdraw.
    The number of quads is not very much (almost 15-20 but they are big and occupy a big part of the screen)
    Do you agree with me not to use batching and for this reason, I will be sure that rendering is front to back?
    The platform is android.
     
    Last edited: Apr 14, 2020
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    I wouldn’t expect instancing of opaque objects to sort perfectly either btw.

    The only way I would expect it to work 100% of the time is to do the batching manually yourself in script.
     
    mahdiii likes this.
  5. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    It is weird why unity does not sort opaque batched objects front to back.
    Do you know the reason? Because sorting is time consuming but I do not have many objects.
    I think overdraw is more important in my scenario.
    Could you give me a link or other stuff to apply manual batching front to end rendering or withdraw batching?
    Thanks in advance
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Because Unity’s sorting metric is more complex than purely distance based. Overdraw isn’t always the most important thing on all platforms. Especially mobile btw. Opaque objects can get depth sorted by the GPU to remove overdraw regardless of the issued draw order on most android devices. If you’re really concerned about overdraw performance on mobile, the more important question is if you’re using alpha tested materials or not. Those are really bad for mobile perf, usually more so than opaque overdraw.

    I’d also suggest double checking if things are getting sorted or not rather than just going by my assumptions. You can test by using a transparent material and forcing the queue to geometry (2000) and seeing if the stuff rendered in the back are rendered on top. That means it is sorting them properly front to back and I’m wrong.

    If you want to do it manually you’d use this:
    https://docs.unity3d.com/ScriptReference/Mesh.CombineMeshes.html
     
  7. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    No I do not use alpha test (cutout shaders). As I mentioned before, I have used opaque shaders (and quads) for rectangular big shapes and for others, sprite default shaders
    but I have problems in performance with overdraw (They all were sprites before).

    I have seen it before. It only combines meshes like batching but my problem is about rendering order in dynamic batching.
    You mean I can spceify the order manually by using it?


    My problem is when it is batched dynamically, I can not see the order.
    When it is not batched, the order is correct and front to back for opaque shaders.
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    If I remember correctly, it batches in the order of the CombineInstance array. So manually pre-sort that array by depth and you'll have something working.

    Alternatively you could generate a mesh yourself in code if it really is all just quads. That's relatively straightforward too.

    That's why I said to use a transparent material with the queue overridden. That'll let you see what the order is.
     
    mahdiii likes this.
  9. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    I have another problem. I noticed if I add the code below, I will have problems in z ordering.
    I have used it to set uv of sprites in the spritesheet and assigned it to every mesh.
    When I change z coordinate of meshes in runtime, I see different ordering, completely wrong but if I disable it, it will be OK.
    Also I have used sorting group in the parent. I thought sorting group works on spriteRenderers not meshes but understood it can destroy standard z ordering (for opaque front to back) as well.
    Also, I tested dynamic batching with geometry queue but transparent (to evaluate) and it is true, they are rendered front to back.

    Code (CSharp):
    1.  
    2. [RequireComponent(typeof(MeshRenderer))]
    3. public class SpriteAtlasRenderer : MonoBehaviour
    4. {
    5.     [SerializeField] private Sprite _sprite;
    6.  
    7.     private MaterialPropertyBlock mt;
    8.     private MeshFilter _meshFilter;
    9.  
    10.     private void Awake()
    11.     {
    12.         _meshFilter = GetComponent<MeshFilter>();
    13.     }
    14. private void Start()
    15.     {
    16.         var mesh = _meshFilter.mesh;
    17.         var count = mesh.vertexCount;
    18.         mesh.uv = new Vector2[count];
    19.  
    20.         var v = new List<Vector2>();
    21.  
    22.         var minPoint = _sprite.textureRect.min / (new Vector2(_sprite.texture.width, _sprite.texture.height));
    23.         var maxPoint = _sprite.textureRect.max / (new Vector2(_sprite.texture.width, _sprite.texture.height));
    24.  
    25.         v.Add(minPoint);
    26.         v.Add(maxPoint);
    27.         v.Add(new Vector2(maxPoint.x, minPoint.y));
    28.         v.Add(new Vector2(minPoint.x, maxPoint.y));
    29.  
    30.  
    31.         mesh.SetUVs(0, v);
    32. }
    33. }
    34.  
     
    Last edited: Apr 17, 2020
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    No idea why that would affect sorting. Sorry.