My project. Looking nice enough so far. But it has a problem. The problem is this:
Those are triangles.
A LOT of triangles.
See how sections of terrain are different colors? Each of those is a block of terrain, which is a grid of 32 x 32 squares. Each square is a pair of triangles. So, each terrain is 2,048 triangles. There are 729 of them in play right now, which gives us our grand total of 1,492,992 – one and a half million polygons.
If I go way up in the air and look down, I see this:
That seems like a ton of terrain data, and it is. Yet from the center to the edge is only 416 meters. Not even half a kilometer! Yeah. Filling in the world out to the horizon is tough. It takes a lot of data. Most games don’t even go this far. I think Minecraft lets you see about 256 meters. I think Oblivion, Fallout 3, and New Vegas stop well short of half a kilometer. (Okay, FUEL had a massive view distance, but that was made by a real team of guys, so sue me.)
Now, my graphics card can handle this. In fact, it’s not even really trying. But throwing polygons at the graphics card like this is wasteful. Horribly, horribly wasteful. It’s not affecting my machine, but if this was a low-end machine, I’d really be slowing down.
I don’t actually want to work on this right now. The real bottleneck is on the CPU side, where my machine struggles to churn out the pages of data needed to fill this terrain. By contrast, actually rendering it is no big deal. That’s all handled by the GPU, which, as far as I can tell, is bored right now. Having said that, I can’t leave things like this. I need to make sure the terrain system works right before I move on, which means taking some basic steps to reduce polygon count. I don’t want to be one of those developers who writes inefficient engines and expects you to buy new graphics hardware to make up for it.
This step is going to mean mucking about in the guts of my terrain engine, and it’s easier to do that now than later. Now the thing is fresh in my mind. Later I will be foggy on the details, and I’ll have a bunch of other systems built on top of it.
For polygon optimization, I’m going to go back to Peter Lindstrom and his magic method of polygon reduction, which I used way back in 2006, during my original terrain project. Rather than make you go back and re-read that series, I’ll reprint the relevant steps here:
|To use a quad tree, our grid must be a perfect square. It has to have the same width and height, and they must be a power of 2. In the terrain I’ve been using, the width and height is 32, which is 25. For our example here, let’s assume we’re working with an 8×8 grid, which is 23.|
|The main grid is divided into 4 quadrants. So, we have this sub-grid of 2 x 2. Then…|
…each of those squares is, in turn, divided into 4 quadrants. This yields a sub-grid of 4 x 4, which in turn can be divided further, and so on.
Anyway, this gives us a way to look at the grid at different levels of detail.
|My program starts at the largest blocks on the 2 x 2 grid. Let’s say it examines the four blocks in question and determines that the lower-left one is a bit hilly, and needs to be divided further. The other three quadrants are flat enough that they can be left alone. So, we divide the lower-left block.|
|Within that block, we have 4 smaller blocks, and of those 4, we determine that the upper-right has some detail that needs to be preserved, but the other 3 are again simple enough that they can be left alone. Once again, we divide the one block into 4 smaller ones.|
|As you mark individual squares for use, you’re actually flagging points on the grid for use. When you do so, there’s this chain of other points you mark as used. You step up to the next largest spot on the grid, and flag a point as in-use. That point, in turn, reaches up to the NEXT larger square on the quad tree and flags a point for use, and so on, until you’re talking about the corners of the entire grid itself.|
This is a really complicated system. The math behind it is trivial, but the rules controlling the points are confusing. Then turning those points into triangles is even worse. It took me a couple of days to figure it out the first time, and I don’t remember any of it five years later.
In my least entry on project Hex, I mentioned the importance of comments. Check out the comments I left in my old terrain project:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*----------------------------------------------------------------------------- North N *-------* *---+---* *---*---* *---+---* |\ | |\ /| |\Nl|Nr/| | | | | \ Sup | | \ / | | \ | / | | A | B | | \ | | \ / | |Wr\|/El| | | | | \ | West+ * +East W*---*---*E *---+---* | \ | | / \ | |Wl/|\Er| | | | | Inf \ | | / \ | | / | \ | | C | D | | \| |/ \| |/Sr|Sl\| | | | *-------* *---+---* *---*---* *---*---* South S Figure a Figure b Figure c Figure d This takes a single quadtree block and decides how to divide it for rendering. If the center point in not included in the mesh (or if there IS no center because we are at the lowest level of the tree), then the block is simply cut into two triangles. (Figure a) If the center point is active, but none of the edges, the block is cut into four triangles. (Fig. b) If the edges are active, then the block is cut into a combination of smaller triangles (Fig. c) and sub-blocks (Fig. d). -----------------------------------------------------------------------------*/
Thank you, thank you, Shamus of 2006. Your ten minutes writing those comments just saved me a day and a half. There were a lot of wonderful notes in that thing, which allowed me to take the Lindstrom system and drop it into this Frontier project in just a couple of hours.
Note to self: You should be putting more comments in THIS project.
So the polygon reduction is gone, and we are left with a NEW problem. This problem:
Gaps. Removing polygons naturally changes the shape of the terrain. These changes cause the squares to no longer line up with each other. There are now gaps, where you can see between the blocks of terrain. Here is an example:
There’s a terrain right under us. (Pink) And other in front of us. (Yellow) At A, you can see that Pink needed a point that yellow didn’t, and at B you can see there was a point that yellow needed, but not pink. The way to reconcile this is to “stitch” them together.
Sigh. And this is why I didn’t want to have to do this step. I HATE stitching. It’s not hard or complex, but it feels… inelegant. See, programmers talk about encapsulation. That’s the idea that you make a bit of code that does one thing, it does it well, and once it’s done you can treat it like a black box and not worry about how the insides work. The overall structure of the program should be as simple as possible, even if the individual parts are dauntingly complex. Each system should be connected to as few other systems as possible.
Up until now, terrains have been a lovely black box. I just make a terrain, tell it where it goes, and give it a few milliseconds between frames. The terrain block handles everything else. Now my terrain blocks have to interact with each other. Each one has to know about its four neighbors (if they exist!) and be able to negotiate with them to make sure their edges match. Which edge points is it using? Is that neighbor actually done building? Has it changed its edges recently, perhaps in response to some OTHER terrain block? Terrains are no longer just something you can create and forget about. Now you have to create them, and then help them find each other.
Now, this isn’t a horrible thing. It’s actually kind of trivial and not worth the two paragraphs I’m spending complaining about it. But when I have to add inter-connected stuff like this it makes me uneasy. It usually means I need to re-think my design. Not always, though. Some problems are hard, and there’s nothing you can do but suck it up and deal with the complexity.
118,353 polygons. Less than a tenth of what it was before. And it looks exactly the same.
Well, we fixed something that wasn’t a problem and added a bit of complexity that I dislike, but the important thing is that it’s built right. Somewhere there is a crappy laptop that will really appreciate the effort.
Next time we’ll add something cool.
Best. Plot Twist. Ever.
Few people remember BioWare's Jade Empire, but it had a unique setting and a really well-executed plot twist.
The Disappointment Engine
No Man's Sky is a game seemingly engineered to create a cycle of anticipation and disappointment.
WAY back in 2005, I wrote about a D&D campaign I was running. The campaign is still there, in the bottom-most strata of the archives.
The Best of 2012
My picks for what was important, awesome, or worth talking about in 2012.
Quakecon 2011 Keynote Annotated
An interesting but technically dense talk about gaming technology. I translate it for the non-coders.