Shadow volumes are interesting things. For everything that casts a shadow, you need to have a fully enclosed solid. Because of the way our shader works, for every triangle we need to know what its 3 adjacent neighbors are. And when I say “need” I don’t mean “ought to” I mean it’s impossible to do otherwise. You can’t supply a triangle to the shader without neighbors for the same reason you can’t draw a triangle with less than three vertices. It wouldn’t make any sense. (And if there aren’t 3 neighbors? Then this isn’t an enclosed solid.)
Let me bring up this diagram again:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*----------------------------------------------------------------------------- This shader takes a triangle of type GL_TRIANGLES_ADJACENCY. It takes the following form: 1-----2-----3 \ / \ / \ / \ / 0-----4 \ / \ / 5 Points 0, 2, and 4 are the points of the triangle actually being drawn. Points 1, 3, and 5 are corners of adjacent triangles, provided by OpenGL for the purposes of being able to analyze the topology here in a geometry shader. -----------------------------------------------------------------------------*/
This is no big deal when you’re using models made by artists. Maybe the artist has some kind of tool or conversion utility for identifying all the triangle relationships. But this becomes tricky when we’re building objects on the fly. When I’m building a particular cube, I can’t hook its triangles up to its neighbors, because at least half of them haven’t been built yet. I could peek at the next-door cube to see what I will eventually build there, but I won’t know what vertices it will use until I get there. So what you have to do is just build a bunch of triangles and then stitch them together when you’re done.
This is actually really time consuming. Like, 90% of the time spent waiting for chunks to appear is waiting for it to thrash through these huge lists of triangles and figure out which ones are neighborsThis is the major reason I’m not using larger chunk sizes. Larger chunks make this triangle-matching less efficient. There are ways to improve this, but I haven’t gotten around to it yet.. Eventually I’m going to have to fix that. Chunks take about a quarter second to form. A given scene with even a modest view distance will have hundreds and hundreds of chunks. Which means filling in a scene can take a few minutes. While this doesn’t matter from a gameplay standpoint (this isn’t a game, and nobody is ever going to play it, so who cares?) it does matter from a testing perspective because I do a lot of tests and I’m really impatient. But this is a challenge for a future post. In the meantime…
It turns out that we have a problem:
Those dark streaks in the center are shadows, reaching off to infinity. We don’t want that.
Let’s flatten the problem out to 2 dimensions. So instead of building triangles around cubes we’re building lines around squares. Imagine the dark squares are solid and the white ones are empty space:
Blue line A has 2 neighbors, lines B and C. You can look at this diagram and see that no matter what sorts of squares I make or where I put them, every group of squares would be surrounded by line segments and every line segment would have exactly 2 neighbors.
Well, ALMOST. It turns out there is an exception to this rule:
These two squares share a corner, so they’re “kissing”. This means that the purple line now has 4 neighbors. Sort of. Four lines all converge on the same point, and it’s not at all clear who should be neighbors. If we add a cube at point X, then purple’s neighbors would be red and cyan. If X is empty, then purple should be neighbors with red and blue.
The crazy shadows I showed you earlier happen when a couple of kissing cubes confuse the triangle-stitching code. It puts the wrong neighbors together, which results in holes in our shadow volume, which results in the broken shadows you see above.
Visually, it’s pretty clear that red and blue are the “real” adjacent lines in the above diagram, and the other two belong to a different cube. But it’s only obvious because you’re a human being with a fabulous human brain and I’ve drawn you this handy diagram. To the computer:
It doesn’t know what you’re trying to build. It can’t “see” the squares and intuit what it’s supposed to do. It just sees this list of points and these arbitrary relationships between them. There’s nothing here to suggest that blue is a more correct neighbor for purple than cyan or green. We need some way to explain it to the computer.
Unfortunately, this is harder than it sounds. When we’re building the lines the information isn’t ready yet, and when we’re hooking up neighbors later the information about the ownership of these polygons is gone. We could probably sort it out by doing some kind of complex spatial analysis. But that would be…
- A major pain in the ass to write.
- Super slow, so that it takes EVEN LONGER for this dang scenery to fill in when I run the program.
So I am not crazy about this solution. In fact, I’d be happy if I could just hack around this problem. I’ve kind of had my fill of cube logic for now and I want to be working on other stuff. But it’s really annoying to have these massive bogus shadows cutting across my view while I’m trying to test.
I’ve been trying to ignore this for a while now. Usually when a bad dice roll results in a couple of kissing cubes I’ve been just hunting them down manually and deleting one of them. As far as solutions go, that’s kind of pathetic. Also time consuming. These shadows can originate from anywhere in the scene, so often I have to fly a long way to reach the culprit.
So the quick-and-easy solution seems to be having it detect and automatically delete one of the offending cubes. Actually, I don’t even need it to delete the cube itself, it just needs to leave the cube out of the shadow volume. What this means is that when we have a pair of kissing cubes, one of them won’t cast a shadow. In the words of the great philosopher Strong Bad, “Who cares?”
Fine. Easy. When it’s making the model, it looks for kissing cubes and then leaves one of them out of the shadow volume.
I get back to what I was doing. I’m messing around with generating caves and hills when…
Wait what? Oh, right. Duh:
A and B are kissing. But if I remove A then C and D become kissing cubes. This is probably what we see in the previous screenshot: It tried to fix one set of kissing cubes and ended up creating another. I could ADD a cube at point E (or just force a shadow there) but that would create a new connection between E and F.
And remember that while this looks simple and clean in these diagrams, I’m actually dealing with the problem in 3 dimensions. A 3D cube has 12 edges, which means there are 12 possible relationships to worry about and 12 places where we can inadvertently create kissing pairs by adding or removing cubes.
This is a silly little problem.
It turns out the most expedient answer is to just cut the entire chunk in half:
If we find a kissing pair, we whip out our lightsaber and slice the whole chunk right down through the problem edge. This will result in slightly more total faces. Before the slice, D and G were part of the same volume. After the cut they’re isolated from each other, and they have (wasted) polygons facing one another.
I try to feel bad about this, but I have other stuff I’d rather be working on.
We’ll try and do some Cool Stuff next time.
 This is the major reason I’m not using larger chunk sizes. Larger chunks make this triangle-matching less efficient. There are ways to improve this, but I haven’t gotten around to it yet.
PC Hardware is Toast
This is why shopping for graphics cards is so stupid and miserable.
Artless in Alderaan
People were so worried about the boring gameplay of The Old Republic they overlooked just how boring and amateur the art is.
Fixing Match 3
For one of the most popular casual games in existence, Match 3 is actually really broken. Until one developer fixed it.
Video Compression Gone Wrong
How does image compression work, and why does it create those ugly spots all over some videos and not others?
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.