Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

200k dynamic animated sprites at 80fps

Discussion in 'Entity Component System' started by FabrizioSpadaro, Jun 16, 2019.

  1. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    @Sarkahn Is there any chance you can explain how UVCell work and how can I change it to use actual uv data or 2d position od cell instead of it's array index?

    I want to make proper spritesheet animation tool which allows me to define (row,column) and max frames of the animation. But with current solution I need to somehow find array index and count on unity that next frame is in next array index.
     
  2. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    This is shader based.
    - you store the uv's for the texture in a compute buffer (say you have a sprite sheet with 4x4 rows, thats 4x4x4 coordinates) - once
    - you then access each set of uv's via index (you flatten the 4x4 rows to a 16 1D array) - each frame (via compute buffer to shader)
    - the shader looks up the uv's via the index and renders this part of the texture

    I have not looked at @Sarkahn's implementation, but by the comments here, he uses the sprite editor from unity to calculate the initial uv's for the texture via the slice tool --- you don't have to do this, you can just use a texture and calculate the uv's. i.e. for 4x4 the top left is {(0,1), (1/4,1), (0, 3/4), (1/4, 3/4,...}
     
  3. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    Yeah, but I don't know if I can leave it to unity to number correctly sliced texture by sprite tool.
    I have a grasp of what's going on in @Sarkahn 's implementation, but I can't figure out moment of sending correct UV to the buffer. I see that he is caching sprites from resource folder (in CachedUVData) but I can't see moment when UVCell is used to get correct UVs and how that's ends up in the buffer.
     
  4. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    I am on mobile and don’t want to look at code.
    - if you found the place where the buffer data is prepared, you should be able to trace where it is send to the buffer (material.SetBuffer)
    - as explained earlier, each frame the index (UVcell) is send to the buffer
    - the lookup happens in the shader


    Edit - ok, I opened the source, here is the line in the shader
    o.uv = uvBuffer[uvCellsBuffer[instanceID] * 4 + vertexID];
     
  5. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    Yeah I looked into that and now I get it. UVCell work as index in ComputeBuffer so realy the only thing I need to do is change UVCell to int2 as x,y coordinates of sprite cell and then calculate 1D array index.

    EDIT. or just keep everything as is and calculate proper index in animation system
     
    Last edited: Oct 15, 2019
  6. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Sorry I don't check the forums much these days, looks like you figured it out though. UVCell is just the 1D index on the sprite sheet. The shader will update the sprite based on it's the UVCell in the buffer, so if you want to animate you just push a new UVCell to the shader and the sprite will change.

    My crappy test animation system just increments each sprite's UVCell over time and the changes get pushed into the compute buffer which will update the sprite in the shader.

    In retrospect "UVCell" was a bad choice for that, as usual I'm horrible at naming things.
     
    Last edited: Oct 15, 2019
  7. Zyblade

    Zyblade

    Joined:
    Jul 1, 2014
    Posts:
    139
    @Sarkahn
    thanks for your example, really appreciated.
    Do you know, why my uv's are somewhat offset/not correct, once I copy the 1bit-kennynl 8x8 spriteatlas, resize from 135x135 to 1350x1350px (just for example) and reslice it? It is basically an identical copy of the sprite atlas, but I can't figure out why the uv's won't match. It correctly calculates 64 cells when I debug it. The slices are match 1:1, I carefully compared it to the smaller atlas. Or maybe in the code, there is something, that relies on a 135px sprite-atlas^^

    Thanks in advance =)

    edit: oh, @Krajca already figured it out on page 2, heh :D Thanks for posting the solution!
     
  8. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    @GilCat did you check the code I posted here a while ago - This should be a similar buffering approach to yours?
     
    GilCat likes this.
  9. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    I've missed that and just looked at it now.
    Yes it is similar approach of mine. Pretty simple and clean code :)
    You can also look at mine as it is publicly available on GitHub. Mine looks a bit more complicated because I load the available buffers dynamically so one can add more instanced properties without much hassle (eg. uv and tile for animations). There's also plenty of optimisations and conversion workflows.
     
  10. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    I think I have looked at your solution when you posted it back in the days, but I usually read forums on mobile and all the solutions here, were at a complexity level that was hard to follow on mobile. What I posted is only the core system and should be extremely fast/optimized for performance.
     
  11. Zyblade

    Zyblade

    Joined:
    Jul 1, 2014
    Posts:
    139
    @sngdan Do you know why your example throws a "stride" on start?
    "Invalid stride 1 for Compute Buffer - must be greater than 0, less or equal to 2048 and a multiple of 4."

    I'm still learning dots, so I keep analysing those example from you guys here on the thread =)
    But I couldn't found the reason. It must be in the "simpleBuffer" or SimpleBufferUpdateSystem, where the stride size gets calculated for the ComputeBuffer. I'm getting this error twice for each entity. So when I spawn 3, I get 6 of those errors, once. Then "Init Buffers" appears in the logs and it runs fine.

    edit: This happens to unity 2019.1 and to the current 2019.3b11
     
    Last edited: Nov 16, 2019
  12. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    This never happened for me - I suspect it could be update order (buffers not initialized and render system tries to write to them, once they get initialized all goes fine) - does this happen for you with the unity package I attached & without any changes?

    another idea could be that I have a logic error somewhere, I never tested this with 3 entities only and I think I had some hard coded spawner logic in there for 3 separate materials...

    I am on mobile and can’t test, I can look into it, if you tell me how to reproduce.

    My system, is also for the little more involved/advanced user, it’s shows the concept that one would need to build upon for a specific use case. It should be the fastest of the different solutions (safe moving some calculations to the shader)
     
  13. Zyblade

    Zyblade

    Joined:
    Jul 1, 2014
    Posts:
    139
    It happens for me without any change. When I setup a new project for this package, I check "allow unsafe code" and install the entities package, which installs jobs, burst, mathematics etc. as dependencies. It's possible I have different versions of packages or I miss one. If the error doesn't happen to you, it must be a project setting.

    edit: I saw someone else had this error. Could be related, or not. Who knows. I still think it has to do with the project setup.
    https://issuetracker.unity3d.com/is...e-for-compute-buffer-error-on-playing-a-scene

    Could you upload a dummy project instead a unity package? If the error is gone then, I can search for myself which setting was different.
     
    Last edited: Nov 17, 2019
  14. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    That happens because the compute buffers are being initialized with stride 1 the very first time they are created.
    Just remove line 18 and 19 on file SimpleBuffers.cs because they are not necessary or just initialize the buffers with a stride of 4.
    Code (CSharp):
    1.         public SimpleComputeBuffers()
    2.         {
    3.             args = new uint[5] { 0, 0, 0, 0, 0 };
    4.             this.argBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
    5.             // Removed the lines bellow
    6.             //this.ltwBuffer = new ComputeBuffer(1,1);
    7.             //this.colBuffer = new ComputeBuffer(1,1);
    8.         }
     
    Zyblade likes this.
  15. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    Thanks @GilCat - yes, the highlighted code is wrong. Still have the feeling that there might be an update order issue, because I don’t recall getting this error (ie the buffers were resized elsewhere before writing to them)

    i also recall that the shader threw an warning by just clicking the material in the editor, but that seems to be a metal / Mac issue - based on other forum posts
     
    Zyblade and GilCat like this.
  16. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,068
    Hello guys,
    I have a low knowledge about graphic programming, shaders, materials and how the rendering pipeline works.
    I’m making a game using ECS targeting mobile devices, so I tried to use the URP for performance reasons but I ended up hiting a compatibility wall between URP and HybridRenderer.
    Is it possible to use this package as an alternative to the HybridRenderer or do you guys already know any other alternative to it ?
    This will be really awesome for a lot of people in this forum.
    Otherwise is it possible to use this package as a starting point to create my own renderingSystem?
     
    Last edited: Dec 9, 2020
  17. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    What is done here is very simple + low knowledge about rendering pipeline is enough. The solutions here do not use the Hybrid Renderer but DrawMeshInstancedIndirect (which has nothing to do with ECS, you can read about it in the Unity docs).

    Without knowing your specific use case it is hard to assess, if this approach makes sense for you. Maybe you explain a bit (are you 2D/3D, many similar meshes, lots of overdraw/culling, how many objects, etc.)
     
    GilCat likes this.
  18. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    Also since the solutions presented in this thread all use DrawMeshInstancedIndirect hence requiring compute buffers there might be some limitations in case you're targeting low end devices.

    What problems did you encounter there?
     
  19. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,068
    im making a 3D TOP-DOWN Game style.
    currently with URP + SRP we have about 100 draw calls, (which is not bad) but by disabling the URP and go back to the Built-In it become ~250 draw calls. 800k Tris and verts.

    and the big problem is URP is not Working correctly with hybrid renderer when building for Android and IOS. and with built-in it's working only on Performant Mobiles


    ScreenShot:
     
    Last edited: Dec 9, 2020
    lclemens likes this.
  20. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,068
    Last edited: Dec 9, 2020
  21. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    As Dan mentioned we're using DrawMeshInstancedIndirect, which has nothing to do with DOTS or ECS. The ECS side is just used to be able to manage the sprite data - uvs, colors, positions, etc, and then push that into compute buffers which in turn get pushed into material for the DrawMesh call.

    Unfortunately using the DrawMesh* calls require at least some basic knowledge of shaders and compute buffers. It's also worth mentioning if you're using a lot of different materials/textures then this approach gets much more complicated. It works well in the sprite demo since we're only using a single material with a single texture.

    I tried to write my version in a way that would support multiple materials but in the end the code ended up being a mess. At least with the way we did it there's just no good "simple" solution for any kind of general purpose use case in my opinion, you have to figure out what data you need and how you're going to manage the entity->buffers->computebuffers pipeline for your specific use case.
     
    Opeth001 likes this.
  22. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    the version i posted supports multiple materials

    not sure if what we did here works well for you, from your screenshot, you will need some culling (screen + z) - you could possibly use the hybrid renderer v1 culling or hybrid renderer v1 as a base (it was not perfect, but used drawmeshinstanced, if I recall correctly and material property blocks)

    ideally your timeline allows to wait for Unity to get URP + rendering in order

    good luck!
     
    Opeth001 likes this.
  23. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    Hybrid renderer v1 didn't have support for material property blocks but there are a few post around the forum with implementations on top of it.
     
    Opeth001 likes this.
  24. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    @Sarkahn any idea how to add some kind of sorting to your solution? os is there any way to sort it now?
     
  25. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    If you mean like sorting for transparency or z sorting you'd have to ask someone who knows more about shaders than I do
     
  26. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    To sort the sprites using DrawMeshInstancedIndirect you must sort the data supplied to the compute buffers. I've already did some digging on that matter.
    Basically you need to sort all those native arrays that are feed into the compute buffers based on the distance to the camera. The problem here is that sorting a large amount of data is expensive and doesn't scale well.
     
    Opeth001 and Sarkahn like this.
  27. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    You could certainly schedule a job between where the dynamic buffer gets pushed into the compute before to sort it. But you'd have to sort every buffer. And I don't know much about sorting algorithms but I'm guessing parallelism isn't much help there.

    This sounds like a reasonable way to go if it will work for you.
     
  28. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    Unity has a built-in parallel sorting solution in NativeSort.cs with this signature:
    Code (CSharp):
    1. public unsafe static JobHandle SortJob<T>(this NativeArray<T> array, JobHandle inputDeps = new JobHandle()) where T : unmanaged, IComparable<T>
    I can also give you a radix sort if that helps. I would at least try Unity's sort and see if it gives you acceptable performance.
     
    R0man likes this.
  29. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    Sorting should not be a huge performance cost (unity build in has to do the same + burst & parallel should be very close)

    maybe you could do the sorting on the gpu in a first pass - someone with better shader knowledge to advise

    if you mean something like sorting layer, this could be achieved via an int in the SCD render mesh (then you would need to pick a solution from here that supports different materials and extend it slightly)
     
  30. Zyblade

    Zyblade

    Joined:
    Jul 1, 2014
    Posts:
    139
    In Sarkahn's example, I didn't had to do any fancy sorting. Just changing the vert.z was enough for a sprite, for being rendered behind another. Or maybe I miss something?

    edit: Okay, I missed something :D This works for cutout sprites. But for pure transparent support, you would have to modify the order(in the actual spawner/rendersystem), in which the sprites get rendered, using (for example) the y position.
     
    Last edited: Dec 8, 2019
  31. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    For that one could try using Bitonic Sort with compute buffers.
    There's even some examples in Unity that can be work as a starting point.
     
  32. Zyblade

    Zyblade

    Joined:
    Jul 1, 2014
    Posts:
    139
    As long, as I only use only binary alpha values, my current demo works fine. I had no issues with ZWrite on (see example image). I moved those cubes using arrow keys in play mode and took screenshots. They vert.z is equals transform position y axis, for simple sorting.
    I have to think about, if I really need semi transparent pixels, for AA for example. If I do, I'd probably use either that bitonic approach or a multithreaded merge sort, which I tested before, was capable of sorting 100k items on the cpu (i7-7700HQ) at about 70-80fps in editor.

    cutout.jpg
     
  33. MustEat

    MustEat

    Joined:
    Jul 15, 2018
    Posts:
    18
    These are great implementations, but is anyone else having an issue where the camera stops rendering if you move the camera and spawners anywhere other than positioned at 0? For example go into one of @GilCat's example scenes and drag the camera and spawner up the Y together. The sprite is only visible if you pull the camera far back or widen the F0V perspective.

    How can we fix this if we want to use the systems in a game and have sprites visible at other locations? What am I missing?
     
  34. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    Thanks for bringing that into my attention.
    The problem was that the Bounds supplied to DrawMeshInstancedIndirect were not being correctly converted.
    All the bounds used are calculated from ChunkWorldRenderBounds.
    I have fixed and pushed into my repo.
     
  35. MustEat

    MustEat

    Joined:
    Jul 15, 2018
    Posts:
    18
    Very cool, I'll try it out. Thanks, @GilCat!
     
  36. MustEat

    MustEat

    Joined:
    Jul 15, 2018
    Posts:
    18
    This is working much better after your Bounds fix, @GilCat, but I'm getting an NRE on InputDeps in the SortSystem:180. I don't get the error in your sample scene, so this is possibly because I'm using a Main scene to load the game scene. I tried looking into the NativeList deallocations but haven't yet found the issue. Any thoughts?
     
    GilCat likes this.
  37. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    Thanks! But what about simple material sorting. Like material "x" should be backgound and "y" is for units above etc. It's just drawing order or something more?
     
  38. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    The idea here is to batch objects with same mesh and material. And we are talking here about sorting inside that batch.
     
  39. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    Yeah, but sometimes you will need more than one atlas (one material) so I wonder if draw order will be enough to achieve simple layers
     
  40. FabrizioSpadaro

    FabrizioSpadaro

    Joined:
    Jul 10, 2012
    Posts:
    287
    Hello guys, it's been a while since my last update, because I' ve been working on my game a lot lately.

    Today I have a big update, I wrote a super easy API to Create/Destroy/Update entities using my sprite sheet system, and I even got some fps improvement by better managing the strides on the UV Buffer.



    Check out this post to get more details
     
    NotaNaN, GilCat and Antypodish like this.
  41. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,574
    Fractals? :)
     
    FabrizioSpadaro likes this.
  42. FabrizioSpadaro

    FabrizioSpadaro

    Joined:
    Jul 10, 2012
    Posts:
    287
    Yeah, but a really basic one :D, my brain stopped working after the 1000th buffer I saw while coding
     
    Antypodish and GilCat like this.
  43. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    @FabrizioSpadaro - Nice to see you are still updating this! It got a bit silent here.

    PS: Did you by any chance check out the double buffer demo, I put up here? It works in tandem with the change filter and only pushes changed chunks to the compute buffer. It's a very simple example with l2w and color components only but easily extendable - a bit outdated (still uses componentsystem for the renderer, can be changed to jobcomponentsystem now)
     
  44. FabrizioSpadaro

    FabrizioSpadaro

    Joined:
    Jul 10, 2012
    Posts:
    287
    Yeah I did check it, and it was a brilliant idea, sadly it's not that easy(time-wise) to implement it, so, for now, I decided to skip it, but I'm more than happy to accept pull requests, and if any fearless coder wants to give it a try, I will cheer for him :D
     
  45. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,068
    is it possible to use Baked Lights data with DrawMeshInstancedIndirect ?
     
    MNNoxMortem likes this.
  46. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,068
    can anyone help please ?
    I really searched a lot on the internet, i went to the tenth page of each Google search i typed and nothing found about setting Lightmap data to the MaterialPropertyBlock.
    Thanks in advance!
     
    Last edited: Jan 20, 2020
  47. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    Do you want to set it up for DrawMeshInstancedIndirect or DrawMeshInstanced?
    I believe you can use baked lights for it this required an expert on the matter :|
     
    Opeth001 likes this.
  48. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,068
    hi @GilCat ,
    im trying to set it up with DrawMeshInstanced. from what i understood it's be better for low end devices when using the DrawMeshInstanced right ?
     
  49. FabrizioSpadaro

    FabrizioSpadaro

    Joined:
    Jul 10, 2012
    Posts:
    287
    I think you can't do it, but you can always pass the light data inside a buffer.
     
    Opeth001 likes this.
  50. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,068
    thanks @FabrizioSpadaro for this answer!
    im sorry i am not really getting what you are saying :p im new in all this rendering stuff.

    1) does this mean it's impossible to use lightmap Data in DrawMeshInstanced or in both DrawMeshInstanced +DrawMeshInstancedIndirect ?
    2) by "passing the light data inside a buffer" you mean even the lightmap texture or only lightprobes like below ?

    Code (CSharp):
    1.  
    2.         // Calculate probes at these positions
    3.         var lightprobes = new UnityEngine.Rendering.SphericalHarmonicsL2[positions.Length];
    4.         var occlusionprobes = new Vector4[positions.Length];
    5.         LightProbes.CalculateInterpolatedLightAndOcclusionProbes(positions, lightprobes, occlusionprobes);
    6.  
    7.         materialPropertyBlock = new MaterialPropertyBlock();
    8.  
    9.         materialPropertyBlock.CopySHCoefficientArraysFrom(lightprobes);
    10.         materialPropertyBlock.CopyProbeOcclusionArrayFrom(occlusionprobes);
    11. Graphics.DrawMeshInstanced(mesh, 0, renderers[0].sharedMaterial, Matrix4x4s, transforms.Length, (EnableMaterialProperty)? materialPropertyBlock:null, UnityEngine.Rendering.ShadowCastingMode.Off, true, (int)renderers[0].renderingLayerMask,null, UnityEngine.Rendering.LightProbeUsage.CustomProvided);
    im sorry i dont want to get you guys out of topic but I am desperately looking for details on this subject and can't find anything on the internet.