I'm making a voxel game, and I want it to run fast, but not all optimizations are good; there are many popular optimizations that in reality some turn out to be a bad idea, like greedy meshing, so in this video I will show you three examples so you can learn to make better judgments about what optimizations to make. Starting with LODs, which are a very good optimization, but they still have a catch, so, how can a good optimization have a problem? Well, LODs basically simplify the geometry that is very far.
And it can be applied to any game, like traditional games that just have multiple models for various LODs, or you can just stop rendering grass or small details that are very far away. In my case, instead of rendering all the blocks, I merge a 2 by 2 region of blocks into a bigger one, reducing the geometry, as you can see here. And the difference in performance is pretty drastic.
So where is the problem then? It all seems good, and no, it’s not that gap there; I’ll fix that. Well, everyone talks about LODs, but they only show you the result; however, this is how loading the chunks on the GPU looks like.
And it still took hours, more hours than you’d like! Yeah, it is like extremely slow, so I added up to 10 extra worker threads to fix that, but we just fixed a symptom, not the problem. And the real problem is that this is not a voxel renderer but rather a full multiplayer game that has a voxel renderer.
And it is a huge difference; why? because I can spend all day here optimizing the rendering pipeline, but all of this is useless if the server can't possibly handle that many chunks. And sure, maybe it can deal with me loading 4000 chunks, but there's no way it can possibly deal with 10 players, all with insane view distances, at the same time.
So LODs are great but always be aware of the context, because while LODs can get me to 100 chunks view distance, other systems will hold me back. Similarly, many people sugested I optimize my crafting system in order to run well if hundreds of people join a server, but think about that for a moment; there’s no way I’m holding hundreds of people on the server. I need to optimize the server from the ground up before I can even think about holding 100 people.
So for you, whenever you see a cool shiny optimization, think of the bigger picture, because it’s not just that in a vacuum. But this tip is still useless if you don't also apply the next tips. Sot to solve this issue with the server and many players, I got non stop comments saying that I should generate chunks also on the player side and only send the small differences from the server and I know it seems like a very specific optimization but trust me the story I'm about to tell you will forever change how you see optimizations.
So everything that you implement always has costs and benefits. Including optimizations. Even if you make the best optimization possible with no downsides, there will always be at least 2 hidden costs.
Try to see if you can figure out yourself what they are before I reveal them. But let’s see what costs and benefits this optimization has, and the benefits will surprise you the most, I can tell you that. So the idea with this optimization is instead of generating the chunk on the server and sending it to the client, you also generate it both on the client and server, and send only potential small differences that were placed by other players.
And sending less data through the network makes it faster. The downside, however, is that it makes synchronizations extremely difficult. The world generator is a very slow and also difficult process because of the structures.
Imagine 2 structures overlapping; the order in which you generate them will matter so you can’t really be sure the client generated the exact same thing as the server. Not to mention that you now need to spend extra time on the client to generate chunks, meaning you need extra threads. So, on the downsides, it will make the networking synchronization extremely hard; trust me, it’s already extremely hard; making it even harder is not a good deal.
The upside, however, is that it makes sending chunks faster, or is it? Well, let’s test that, Hector here will ask for some chunks from my server. And this is how things are going to go: when he presses this button, all his chunks will be first dropped.
Then, a packer will start traveling 11000 kilometers from Chile, all the way to England where the Hamachi VPN servers are, and from there they will travel 2000 more kilometers all the way to Romania, where my server is. Then, the server will load the chunks, and they will start making their long 13000 kilometers back to Chile. So try to guess how long it will take for chunks to appear once Hector presses that button.
Ready, go, oh damn that was fast, so that to me looks like under one seccond to get the first chunk. And keep in mind that this time also takes into acount the first trip which is probably almost half the time, so unlike what everybody thought, it turns out sending some big packets is not that big of a deal, and they are not even big because I compress the chunk, and it ends up taking only a few kilobytes, we have zoom meetings after all that stream high quality film footage like w** that’s clearly way more data than what I send in my game. By the way, Hector makes the music and sounds for the game, like the one that you can also hear right now, so link in the description if you need that; he does very high-quality stuff.
So in the end we pay the price of adding synchronization bugs to the game for basically almost nothing, and here is the big mistake people make, so pay attention. Let’s imagine I can magically fix this issue; people will say, Well, almost nothing is more than nothing, and that’s true, but we are still paying hard for that almost nothing, that the thing; we still have the hidden costs that are always present, and those are time and code complexity. Whatever code you write will take you time to write it, and 99% of the cases also increase your overall code complexity, rigidity and decrease how maintainable it is.
And you can optimize an unoptimized game, but if your code is unmaintainable and broken, that’s where most people give up on that project. But wait, because we can actually tweak this optimization to make it work, and you might have already figured out how, and if so, congrats. So what we can do is we can generate only the far chunks on the client only to increase the view distance without giving extra work to the server.
So these chunks won’t be necessarily correct, but that’s ok because they are very far. And when you get close, the server will update you with the real content. Now I won’t do this optimization yet even though it is very good because of the time problem, like I need to add gameplay features to release a pre-alpha as soon as possible, so I don’t have time right now for something that really is an extra feature.
And if you learned something new from this good and half good optimizations, wait until you see the lessons from the bad optimization, greedy meshing. Greedy meshing is probably the most overrated optimization I have seen in the voxel games space, and I also have Gabe Rundlet to back me up on this. I won’t spend too much time on the details, but essentially, greedy meshing means merging faces of the same type into a bigger quad, essentially reducing the geometry.
And this is good, except that it is very dependent on how the geometry looks. Like in this case, you can’t do much. Or in this case, if you thought the result would look like this, you are wrong, because we have voxel ambient occlusion, so all of these faces actually need to be shaded differently.
And don’t forget about light values that usually are different for every block. In this case, you can’t greedily mesh anything, leading to a worse result than not doing it. Not to mention that the algorithm is expensive and difficult, and you make your rendering pipeline more complex, and you do all of this to optimize the vertex shader, but that maybe isn’t even the bottleneck, and there are even more downsides, like you also can get weird artifacts that people fix by inflating the geometry, so I’ll let you read this if you want, and if you still don’t believe me, here’s Gabe Rundlet’s voxel engine rendering 18 quadrillion tiny voxels running at 200 FPS with no greedy meshing!
So what’s the lesson? Well, just because everyone says the same thing, it doesn’t mean it is true. Greedy meshing seems to be the go-to optimization for voxel games, but it comes with many drawbacks, and it’s not even mandatory, while other optimizations, like vertex pooling, that I think is the most important optimization for a voxel-like game, people don’t even talk about it so I had to figure it out myself.
But you don’t have to, so watch this video from finding fortune, about how vertex pooling works, this video if you do too many optimizations or this video about other optimizations in my voxel game. See you! So if you still overdo too many optimizations watch this video, or maybe this video about other optimizations in my voxel game.
See you!