Search Unity

Drawing 10000+ objects, many of which identical, in WebGL

Discussion in 'General Graphics' started by DeltaPiSystems, Sep 18, 2019.

  1. DeltaPiSystems

    DeltaPiSystems

    Joined:
    Jul 26, 2019
    Posts:
    30
    I have a lot of static objects that I need to draw, they need to be drawn relatively simple.
    Objects have multiple colors, no shadow, no reflection, just colors with basic lighting.
    These colors are stored in submeshes, they can change for specific objects.
    Many of my objects are identical, only with different colors, so instancing seems like the way to go.

    Issue is, Unity spends way too much CPU time for everything I've tried, as my objects are so simple the GPU sits idle.
    I don't need depth-sorting, alpha transparency sorting or frustum culling, as the GPU is idle anyway.

    I managed to solve this in DirectX 11 with huge instance buffers, easily reaching 1+MB of matrix,float4 values per instance. I can't find how to do something similar in Unity that supports WebGL...

    Object positions are generally static, only colors change. A framerate drop while updating buffers is acceptable.

    I've seen some DrawMeshInstancedIndirect samples which seem promising, but they use StructuredBuffer types in their shader which isn't supported in WebGL...

    Objects can have "weird" matrices, like a mirror along a certain axis...

    Example of what needs to be drawn, this uses 2 drawcalls (one for green submesh, one for beige submesh) for 17576 objects in my DirectX solution:
    upload_2019-9-18_9-56-50.png

    I tried DrawMeshInstanced, but this has a limit of 1023 and internally splits it into much smaller amounts.
    I tried DrawMeshInstancedIndirect, but this seems to need StructuredBuffer<> types in shader which aren't supported, the MaterialPropertyBlock is limited to 1023.

    I would love some directions to examples/articles/documentation that draws lots of objects and is compatible with WebGL.
     
  2. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    In Unity 2019.3, we added Graphics.DrawMeshProcedural, which is like the Indirect method, except it doesn't require StructuredBuffer<> .. I'm not sure if it is supported in WebGL, but if you're able to try that version, it may help you.

    Alternatively, if Graphics.DrawProcedural is supported on WebGL, you could maybe fill a texture with your vertex data and load the vertex data back out in the vertex shader from the texture.

    Otherwise, I don't know a way. (Sorry I'm not an expert on what WebGL can/cant do) :(
     
  3. DeltaPiSystems

    DeltaPiSystems

    Joined:
    Jul 26, 2019
    Posts:
    30
    It's not so much per-vertex data that I'm looking for.
    It is more per-instance data that I want to fill myself.
    Unity allows to set per-instance data with MaterialPropertyBlock but this has a weird and unusable 1023 limit
     
  4. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    Per-instance would be possible too.
     
  5. DeltaPiSystems

    DeltaPiSystems

    Joined:
    Jul 26, 2019
    Posts:
    30
    Interesting, I guess I'll have to look into it some more. Haven't found a simple example yet but I'm sure it's out there.

    Thank you for now
     
  6. N1warhead

    N1warhead

    Joined:
    Mar 12, 2014
    Posts:
    3,884
    If you don't mind shelling out a bit of money I can vouch that GPU instancer on the Asset Store is amazing! I had 3,000,000 (Cubes) with colliders, etc and still had 70+ FPS and only a few draw calls (when i saw few I mean like somewhere around 10)... (Now whether it works on WebGL) I'm not sure, but if it does it - it's literally the best money I've ever spent in my life on the asset store for anything. The performance gains are just (WoW).

    Hope that can help you, at least as a last resort.

    You can try getting in contact with the guy who made it
    https://forum.unity.com/threads/released-gpu-instancer.529746/
    The asset store link is: https://assetstore.unity.com/packages/tools/utilities/gpu-instancer-117566
     
  7. DeltaPiSystems

    DeltaPiSystems

    Joined:
    Jul 26, 2019
    Posts:
    30
    From what I could tell it uses compute shaders which aren't available in WebGL, but I asked in their forum just to be sure.
     
  8. N1warhead

    N1warhead

    Joined:
    Mar 12, 2014
    Posts:
    3,884
    Dang that sucks, sorry for giving you false hope then.
    I really hope you find your solution mate.
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    The problem is WebGL kind of sucks.

    Traditional instancing works by putting instanced variables into an array, then accessing those variables via the instance ID. The problem is WebGL 1.0 requirements for uniform array size is only 128. The API has no max, so some platforms do up to 4096, but many only support 512 or 1024 even if the hardware can do way more. That magic number of 1023 instances that Unity supports is due to so many platforms having that 4096 array size limit, as well as a total data size limit for cbuffers which just a little bit less than 1024 Matrix4x4 / float4x4 values.

    The usual solution to that is of course structure buffers, but WebGL doesn't support those. The solution to that is to use textures instead. WebGL usually supports 2048x2048 textures, which can hold over 4 million float4 values.

    So pack all your data into a giant texture, with matrix values in 4 RGBA pixels. Or 3 as you can usually get away with a float4x3 matrix. Or really just a two floats if all you need is position, rotation, and uniform scale.

    You can use instancing, or you can use a pre-generated mesh with all of your objects baked into it and the “instance ID” stored as in the texture UV. Instancing is often slower than using a pre-baked mesh of many objects. If the positions are static you can skip storing the position. Basically just store as little data as possible in those textures.

    A good example of applying this kind of technique is Keijiro’s Kvant. Like this:
    https://github.com/keijiro/KvantSpray
     
    xVergilx, hedgeh0g and DeltaPiSystems like this.
  10. ron-bohn

    ron-bohn

    Joined:
    Oct 5, 2015
    Posts:
    315
    It sounds like you are generating these static objects from script? The sure-fire way to lower your draw calls is to just combine the meshes and use 1 material for all of the objects. If you don't or can't combine the meshes, you can still take advantage of "static batching" which will help a lot. If you go non-combined meshes route, you could consider making sure your repeating objects are prefabs.

    You can combine, meshes manually or at runtime via script. I am more of a 3d artist and I prefer to do it by hand. There is an asset called Mesh Baker, that has this capability if the above or other approaches don't work.

    Anyways, this approach works for-sure on old devices, new devices, and even webgl 1.0. Dx9+ no problem...
     
  11. DeltaPiSystems

    DeltaPiSystems

    Joined:
    Jul 26, 2019
    Posts:
    30
    What about WebGL 2.0? I'm fine with not supporting WebGL 1.0, as it should mostly run on desktop computers anyway (can't use native binaries because security reasons).

    Using textures seems like a decent way to go, albeit more tedious to create/update when needed.

    The issue is that semi-frequent updates to the amount of objects takes place, if I were to manually transform all the object vertices, loading times would be too long (especially considering there's no multithreading)
     
  12. hedgeh0g

    hedgeh0g

    Joined:
    Jul 18, 2014
    Posts:
    102
    I may be out of place here, but I am interested on this topic. By any chance, is there a a resource of some sort about the limitations of WebGL or the best practices and suggestions on how to optimize a WebGL application?

    Thanks in advance.
     
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    WebGL 2.0 also kind of sucks. In fact all of the above issues still apply, apart from the uniform array length is now 4096. Instancing doesn't even work in WebGL 1.0.
     
  14. ron-bohn

    ron-bohn

    Joined:
    Oct 5, 2015
    Posts:
    315
    https://docs.unity3d.com/Manual/webgl-gettingstarted.html

    There is an entire section of the docs related to this. A lot of things go back to browser limitations and there is a sub section dedicated to that.
     
    PrimalCoder likes this.
  15. ron-bohn

    ron-bohn

    Joined:
    Oct 5, 2015
    Posts:
    315
    According to the docs webgl 2.0 supports gpu instancing (1.0 does not). https://docs.unity3d.com/Manual/GPUInstancing.html

    GPU Instancing is available on the following platforms and APIs:

    • DirectX 11 and DirectX 12 on Windows

    • OpenGL Core 4.1+/ES3.0+ on Windows, macOS, Linux, iOS
      and Android

    • Metal on macOS and iOS

    • Vulkan on Windows, Linux and Android

    • PlayStation 4
      and Xbox One

    • WebGL
      (requires WebGL 2.0 API)


     
  16. DeltaPiSystems

    DeltaPiSystems

    Joined:
    Jul 26, 2019
    Posts:
    30
    Graphics.DrawMeshInstancedProcedural seems supported in WebGL, and it does seem to come very close to what I'd like to use.

    Is there a way to have the SV_instanceID get an offset?
    The argument buffer used in Graphics.DrawMeshInstancedIndirect supports this but I haven't gotten that method to work in WebGL so far...
     
  17. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    Only by setting the offset as a material property.

    The argument in the indirect method doesn’t actually work on all platforms due to how the driver on some platforms implements it.. annoying :)
     
  18. FlyingKiwi

    FlyingKiwi

    Joined:
    Jun 14, 2013
    Posts:
    12
    I'm facing the same problem, did you find a way to make it work?