I keep getting distracted from the arguably more important work of, you know, re-implementing the features of the game.
Oh well.
THROWING SHADE
This time the distraction involved absentmindedly completely rebuilding how the planetary shadow system works. I never actually wrote about it the first time! But it evolved quickly from my previous shadow post for rings and became a system that would work for any object. The game would update an array of structs containing pertinent info for rendering the shadows, representing all bodies that would interact with the shadow system.
To sample the shadows, then, you would call a shader function that iterates through all those structs, performs the needed coordinate transformations, and displays the shadow value. It worked, quite well in fact, but... it always needed improvements. For one thing, it did not discriminate as to which objects could cast shadows upon which. If an object was visible, it was part of the pool. Obviously that's not very efficient since it's wasting multiple cycles of the loop for every pixel on bodies that can't even conceivably cast shadows on any other.
So, that was the first thing I went at. There's more to this story beyond what I talked about, but let's start by talking about how the caster test works, and we'll get back to the rest later. Hopefully this all makes sense. Explaining math and geometry is not my strongest skill...
What
is the best way to determine if a body can cast a shadow upon another? Let's consult this diagram again:
The antumbra is what we want to pay attention to. Considering it represents the maximum extent of the shadow cast, that's the right direction to be thinking in. And from that, if you look at the blue lines, the point at which they cross is the perfect position to run our test. From that vantage point, if object A (the further object) overlaps object B (the nearer object) in the sky at all, B can be considered as casting a shadow onto A.
So far so good. How do we actually find that position? The problem can be simplified quite a lot. It's perfectly acceptable to approximate it as such:
Two line segments, one representing the diameter of the sun, and one representing the diameter of the planet. Some basic triangle math can then be used to get how far away from the planet the viewpoint needs to be. From that viewpoint, you can fetch the angular radii of both objects in the sky, measure the angular distance between their centers, and if that distance is smaller than the sum of both radii, the objects can be considered overlapping. Here's what that looks like:
Step 1 complete! We now know which objects can cast/receive shadows at any given time, with a bit of basic math.
Step 2... step 2 is where things got different.
See, as I said, it previously worked in real-time, iterating through an array of objects to get a shadow value, blah blah. But it quickly came to me that there is a better, faster, cheaper way to go about it.
Take the planet you want to have shadows cast upon. Per the caster test from before, we already know what objects will cast shadows on it. From that information, we can render a texture containing the shadow data, store it in a texture array, and then suddenly the cost of getting a shadow value goes from the cost of iterating through this big array, to the cost of a some multiplication and addition to transform into the right coordinate space, and a single texture sample.
The actual implementation of it works out pretty much just like I described. Periodically (every few seconds), the lighting controller will run its caster tests and decide which objects can be shadowed. For anything that's receiving a shadow, it's assigned a slice of a pre-made texture array, and its associated caster objects have their shadows rendered into that slice. And then to retrieve that value, it only requires a matrix transformation to the right coordinates.
To better show this off, I made a function that spits out several planes and squares to actually visualize the individual texture slices:
You totally just scrolled past those last few paragraphs to look at the picture, didn't you? That's what I thought.
This method has advantages and disadvantages, but I think the advantages greatly win out. The basically-nonexistent render cost of getting a shadow value means I can apply them all over the place. I've already mated it with the terrain shader and local object shaders, allowing it to cast shadows onto the ship (in some situations anyway... I need to add several special cases). Integrating it with the atmospheres should be a...
breeze. get it
The disadvantages are that it can't update as fast (but considering I am not planning on having anything but 1:1 timescale, that's no big deal), that it has a limited resolution (shadows are soft and fuzzy though!), and lastly that the shadow plane represents an infinitely thin slice of the shadows at that position, rather than an actual projection of the shadows onto the surface. I think there's a way around that, though - if I render two shadow maps into the red and green channels of the same texture, one slightly ahead of the other, and then blend between them based on the sample position, depending on the... okay, I'm just rambling to myself out loud at this point. I'll probably give a better explanation of that later on, when/if I end up implementing it.
Here. Have picture. Still missing the atmosphere shadows, but I'll get there.
OK BYE