It’s almost 2018, I plan to write a status report of my game development status soon. In short, I think I spent most of my dev time in 2017 on trees, and am still doing so even today. At least I learned a lot… still feels frustrating though.
For some time, I’ve ignored an issue where objects are in between my game camera and the focus object (in my case, the player). My past games had sparse environments, so this was never really a problem. But my current game will be a bit more diverse than that. When thinking about how to resolve this, I realized there is a simple solution that, while not perfect, gives acceptable results.
If you don’t think about it, what happens when some large object is in the way of the camera seeing the player is… well, it blocks your view. In some games, that’s not really an issue, and you can simply move the camera or the player to move on. But with games with fast-paced gameplay (or if you want the game to look a little more professional), that isn’t ideal. You could decide to make those objects blocking your view temporarily invisible, or partially transparent. You could also prevent the camera from going through or past objects in the first place, forcing it to move closer to the player if it finds something was in the way.
In Unity3D, to implement any of these solutions, the first thing that should come to mind is using the built-in physics engine or the Physics.RaycastAll api. With Physics.RaycastAll, you can draw an artificial line between the player and the camera at each frame, and it will return to you an array (GameObject) of all the objects it hit. You can then obtain a script to assigned to specific types of objects to either make those objects invisible, or swap them with a transparent mesh/material. Simple, right?
In my case, this relates mainly to trees. I have over a thousand trees in my world, and I have seen from experimenting that Physics.RaycastAll itself is not a cheap operation (although only performing it once per frame, between the player and the camera, is not too significant). Further, I am trying to not have the trees cast shadow (I was going to draw them myself on the ground) or have any physics whatsoever, hoping this will help reduce their effect on framerate. So I looked for an alternative solution.
I took out some paper and drew a circle around a dot representing my camera. “I could make all objects within a radius of my camera transparent,” I thought, “but how do I know which objects are actually in the way between the player and the camera?” Then it came to me: a venn diagram.
Remember venn diagrams? In math class, they were simple representations to show what properties between different things were shared and which were separate. You could draw two circles that connected with each other, and the shared properties would go in that shared space in the middle. In my case, I’m talking about using them for a different purpose: suppose you knew the distance between the camera and the player? Draw two circles, one with the player at the center, one with the camera at the center, the radius for each being the distance between the player and the camera. There should be a big shared space directly in between the player and the camera. If we can determine if any objects are in that space, then we’re set!
And how do we know what objects are in-between those two circles? Because they are circles, we can check simply the distance of the object from the player and from the camera. If these two distances are less than the radius’ of the circles, then it must disappear.
Generally, the method works well. This would in theory require being called for EVERY object in the game world, at every frame. I did test doing so on about 5,700 tree objects (arbitrary number) where every tree was being updated each frame, and this method took up less than 2.5 ms of CPU per frame. I’m using a quad-core 3rd-generation Intel i5 processor in a desktop computer. As a comparison, to reach 60fps, each frame in total would need to take less than 16 ms per frame of computation time. It’s not an insignificant amount of time being used (remember that many other things are being computed aside from this operation in my game), but in reality I wouldn’t have over 5,000 objects to update per frame like this. Even if I did, I could group them, then only update the group closest to the camera (ie. not updating objects very far away), or I could use a time manager to spread this logic across several frames. So controlling the efficiency isn’t difficult, and even at brute force, it runs pretty well.
How does it compare to using Physics.RaycastAll() and giving all static objects a physics collider? I wrote a past article about using RaycastAll(), I suspect it relies heavily on the amount of collisions being returned in each frame. In my case, I have a lot of things making up my player in the way, so there would be a lot. But in most games, you might only get a 2-3 collisions at a time. I tested a situation like this, and found the RaycastAll() method to use… less than 0.1 ms per frame. So even though I couldn’t really improve the performance more than that, it seems I don’t need to. On its own, its pretty darn fast, much faster than what I just suggested. And the amount of objects in the world doesn’t really matter, it would only care about the objects it hit. I could get my venn-diagram method to be as fast as that, or slightly faster, but it seems for most cases, it simply isn’t worth the trouble.
My venn-diagram method also has some limitations. It uses the Vector3 transform position of the object. If that position isn’t in the dead-center of the object, results might be wonky. If the object is large, it may be blocking the camera even if the position point is far away, which also ruins the effect. For objects that are very small, they would become invisible even if they weren’t blocking anything. This method can be updated by changing the radius of each circle to be a little bigger or smaller, but then it can cause some objects to disappear when they aren’t supposed to. Trial-and-error required.
It seems I have a bit more testing to do. My method has flaws, and typically isn’t better than the standard RaycastAll() method (although my past experience also showed that a single RaycastAll call could use as much as 7.8 ms per frame if there are a lot of collisions or operations, so I don’t think an alternative is completely worthless). I am surprised however that I haven’t come across this solution online before. A little creativity never hurt.
Speaking of trees: I was able to make my rendering process significantly faster as well as looking better for the amount of trees. In my testing (again testing with about 5,700 trees at once per test), it improved a 4 fps performance to about 30 fps. How did I do it? 1) I combined meshes such that one tree is a single mesh, rather than a trunk, branches, bushes of leaves, etc. 2) I set the combined mesh and all alternate meshes to use the same texture atlas instead of separate materials, 3) I added an extra LOD mesh that is a simplified 3D mesh of the original, simplified using Blender3D’s built in tools, and 4) I didn’t use any transparent materials this time. Step 4) seemed to have the greatest effect: having transparent materials on screen at all isn’t efficient, and having a few hundred at any given time can cause problems. For the simple style I’m using, being able to see through those leaf-bushes isn’t important anyway, and if it was, I could add holes in the mesh in Blender without relying on the material. All these steps are required anyhow to use external trees with Unity3D’s terrain properties… but I still had problems with size and orientation that I do not want to deal with. Anyway, I am finally starting to get away from just working on trees, hopefully I can have better news on progress soon (… I’ve been saying that for almost a year…).