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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

generate Thousands of gameObjects, and beyond

Discussion in 'Scripting' started by jomsa, Nov 16, 2015.

  1. jomsa

    jomsa

    Joined:
    Nov 10, 2013
    Posts:
    23
    So, i've written custom terrain generator and its all good. Now i am in the process of adding stuff on the ground. The good thing is, that my world size is limited to certain size, and i know the max ammount of objects that CAN be on the map (its tile-engine of sorts you see). However.. as this number is 300x300 (atm), making it 90000 possible objects. I am no way good at unity, but common sense says that thats quite a lot of objects. Unlikely scenario though, but hypothetical.

    So how should one go about it. I have mapdata layer indicating what object should appear on what cordinate, and i wrote simple instansiating system for trees, that is topped at 3000 objects, but it feels wrong (though works fine atm).

    I am guessing i need to create and delete game objects based on their distance to viewer (camera), but how should this be done?

    Also i played with the idea of instead of generating these things inside a single mesh (copy the object mesh data inside one, so instead of 143215 tree meshes, i have one mesh with all the trees in it (though i am aware of th 65k vert limit).. would that make sense?

    Ideas, suggestions?
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,744
    I'll start with one tip. No matter what you do, this is going to involve a lot of calculating distance. If you're comparing two distances (e.g. seeing whether the distance from tree to player is higher or lower than the max draw distance for that object), you can be a lot more efficient by using .sqrMagnitude. In other words, this:
    Code (csharp):
    1. if ( (position1 - position2).sqrMagnitude < cutoffDistance * cutoffDistance)
    is functionally identical, but an order of magnitude faster, than this:
    Code (csharp):
    1. if (Vector3.Distance(position1, position2) < cutoffDistance)
    Reason being, square roots are expensive calculations, and that's the final step of the distance formula. If you can skip that step, everything's faster.

    Let's start with the trees. You can just borrow the Unity terrain system's solution to this: Nearby trees get a mesh, faraway trees get billboarded. The billboarded trees are a single quad each, aligned towards the camera. (This isn't just more efficient; it actually looks better - less flickery - than having full meshes out that far, unless you have super-duper-high anti-aliasing settings.) The only trick is how to transition from billboard to full mesh as each tree gets close; however, as you're developing both the tree system and the game, this affords you opportunities to be clever; for example, when the player is behind a building, all the trees blocked by that building can take advantage of that time to change to whichever mode they'll be needing shortly. When the player gets hit and the screen flashes red, take advantage of that flash to make the trees snap to their state. That sort of thing.

    You will definitely want to have a pool of objects, and whether or not you have a hard "cap", you actually do want the number of objects that are fully rendered to hover around the same number; this will help keep your framerate stable. If these objects are of the same type, you will want to take advantage of object pooling so that you don't get frame hiccups as the player comes into and out of range of things.

    The most obvious way to handle switching things on and off would be to simply run through your database of object positions every frame, check the distance from player to everything, and for everything below a certain distance, make sure the object is activated, and vice versa. However, this distance calculation - even if you use the sqrMagnitude trick - is still not going to be cheap. It'll ease the strain on your graphics card, but you might just move the bottleneck to the CPU instead, and that's probably no good.

    Instead, there are two straightforward optimizations (you really only need to do one or the other):
    1) Gather your objects into either clusters or grid cells. If you take that 300x300 map, and split it into 900 cells that are each 10 by 10 units across and hold references to up to 100 objects, then you've reduced the number of distance checks you need to do by a factor of 100.

    1a) Getting a little more complex, you could actually have each cell pre-calculate a list of all the cells that are visible from that cell, and then you have to do (drum roll please) zero distance calculations per frame, at the cost of probably an extra second or two of startup time. (That's not to say zero processor time at all, but the processor time does become pretty negligible if you remove all distance calculations; the biggest CPU time sink would then become activating and deactivating objects.) In addition to the automatic calculations, you could have certain cells specifically include or exclude other cells manually. You know that cell 12A has a clear line of sight all across the map? Specifically include those cells in case the player has a sniper rifle. You know that cell 49J is blocked on two sides by a large wall? Don't need to activate those cells!

    2) Stagger these updates across frames. If you have 90,000 objects, for example, maybe you only run through 2,000 of them in a given frame. Each object still gets updated every second or two, but you've cut down the CPU usage by a factor of 50! This code be done pretty simply like so:
    Code (csharp):
    1. private int currentIndex = 0;
    2. private int updatesPerFrame = 2000;
    3. void Update() {
    4. int updatesThisFrame = 0;
    5. while (updatesThisFrame < updatesPerFrame) {
    6. currentIndex = (currentIndex + 1) % someObjectArray.Length;
    7. updatesThisFrame++;
    8. float thisDistanceSqr = (someObjectArray[currentIndex].yourPositionVariable - playerPosition).sqrMagnitude;
    9. bool toBeActive = (thisDistanceSqr < cutoffDistance);
    10. if (someObjectArray[currentIndex].isActiveInScene != toBeActive) someObjectArray.SetActivationState(toBeActive);
    11. }
    12. }
     
  3. jomsa

    jomsa

    Joined:
    Nov 10, 2013
    Posts:
    23
    Wow, thats a nice and good thing you wrote there. Thanks, lots to think about. But now i am thinking, that what if the said objects are behind the player (tree in front of player say 10m... has same distance than tree 10m behind him?). Say player is standing right next to a tree, but watching into another direction?
     
  4. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,744
    You can take advantage of this, but in this case you might not see much of an improvement. Unity's camera already does bounding box/view frustum culling, which is exactly what you're talking about doing here. You'd only really see improvement if your trees (or whatever) are doing a bunch of calculations on their own besides simply being rendered - and I sure hope trees aren't doing that ;)

    However, absolutely take advantage of this for billboarded trees.
     
  5. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,848
    Things that are outside the view of the camera are already culled from rendering by the camera. And the player can (I presume) turn their head very quickly — much faster than they can move around in the world. So, you will probably find that it would cost you more to try and show/hide (or worse, recycle) those objects behind you than you save.

    Also, if you do need to do your own distance-based culling of these objects, you should probably not be doing distance calculations at all. Instead divide the world into bins, and simply operate on entire bins around the player.

    Finally, beware of premature optimization. Even with quite a lot of experience, our intuition about what's costly and what's not is usually wrong. Build your game, set up some stress tests, and then use the profiler (or careful one-variable-at-a-time experiments) to figure out what needs to be done. Till then, design and code in whatever way is easiest to write and maintain.
     
  6. jomsa

    jomsa

    Joined:
    Nov 10, 2013
    Posts:
    23
    Yes i assumed as much the camera has this culling, but what i was after is that if i am pooling objects, the objects behind the camera are consuming the pool reservoir No?

    edit: Oh Joe Strout actually makes sense why not to bother with this.

    Thanks for the tips. Keep them coming. Gotta read about pooling then.
     
    Last edited: Nov 16, 2015
  7. jomsa

    jomsa

    Joined:
    Nov 10, 2013
    Posts:
    23
    Did it. Got the pool system to run. Shows everything i need now. However, as i am using mock models atm, ... how does one go about LOD:ing it? :D
     
  8. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,744
    There are a few strategies, I'll just pick the most comprehensive one: Incorporate it into your pool system. If range < x1, full model is visible; if range < x2, secondary, plain, low-poly, non-animated, identically-material'd model is visible (those factors are important, as it'll allow Unity's dynamic mesh batcher work efficiently and merge them into one draw call); if range < x3, render them as billboards or particles; else, don't render them.

    #1 and #2 each have object pools associated with them. It's important to note that anything in between those levels of detail isn't really going to gain you much; Unity's batching is the main way you'd gain performance from this. Particles/billboards, of course, are another level of efficiency, though if you're not really good at making mesh generation code efficient it's possible that Unity's batching might simply be faster than that anyway.