I’m still trying to sort out the best way to get this cleaned up into a state where I can contribute it (or even keep it) but it was an interesting bit of code.
The idea was to have simple “Mario” style drop-shadows instead of full-up proper shadows. In a game like Mythruna, proper shadows are problematic for several reasons but objects also feel like they are floating without some indication of “presence”.
Here is what they look like:
The interesting thing is that the shadows are actually rendered as boxes during a post-processing pass. So when the box is rendered, the frag shader knows what the frag depth is and it has access to the scene’s depth texture. With these and some other parameters, I can (turns out pretty trivially) calculate where in the volume the existing scene pixel is.
In the above pics, the shadow volumes extend a bit above the base of the object and then well below it. I normalize the 3D volume coordinate into a 3D texture coordinate and then calculate a distance from the center (basically) to determine how much shading to provide. Thus, anything inside the volume will be darkened based on its proximity to the center of the box.
The shader is really simple at the moment but the rest of the code is a bit unorthodox and inefficient. When it’s cleaned up a bit more, I plan to contribute it.
This sort of 3D volume mapping could be used for other things, too, I guess. At some point, I want to see what it looks like to do the reverse and have a light volume done this way. It’s pretty trivial to bring back the sides of the box when calculating the color values and I think it would make a pretty cool “god rays” effect. Solid light beams that also light up what’s “inside” them.
I thought I would go ahead and highlight why this is better than just a simple sprite-splat shadow:
…contours to slopes. …hangs over edges.
That’s pretty neat. Nice effect.
One extra cube per object?
That's pretty neat. Nice effect.
One extra cube per object?
Yep. I hope to batch them, though.
The unorthodox method I use for convenience:
Objects are tagged as casters in the normal shadow bucket way.
I have a ShadowFilter in the post processor that doesn’t actual post-process but uses it to snag the postFrame() method and grabs the shadow casters, grabs their bounding shapes, and creates a represented box for the shadow volume… then renders it.
(This could also be done as a regular scene processor, I guess… but I had no familiarity with snagging the depth buffer and stuff so I just let FilterPostProcessor do that work for me. :))
The interesting thing is that if the casters are sorted front to back then I could put an arbitrary stop at a certain count. So you’d only get the n closest shadows rendered thus putting a cap on the performance costs. Also, this will be a convenient way to allocate a mesh once and reuse it.
As it stands, the frame rate really drops for me when I have 500+ shadows in the scene because I don’t cull them and I don’t batch them. Before I batch, I will need to strip the math down as far as I can to get rid of as many extra per-object parameters as I can and then embed the rest in vertex attributes. I had to get it working first, though.
mhh zzuegg makes a point. This could be a neat candidate for instancing…
mhh zzuegg makes a point. This could be a neat candidate for instancing...
Yeah, instancing has many uses.
…I just converted my shader over to work with batched geometry, though. I’m not batching yet… but my individual meshes now have everything they need without per-instance transforms. My .vert shader is ridiculously simple now.
The interesting thing is that now my meshes are actually smaller than they were before. Since all eight points are 100% shared, I don’t need 24 like a normal box. 8 * 12 floats versus 24 * 8.
Also, for those following along, I now cull shadows that are off screen and I front-to-back sort them (like all of the other buckets) and stop rendering them once I hit a “max”. This will let me keep reusing a fixed size mesh from frame to frame. I may also opt to scale the intensity based on distance and relative size… that will prevent most popping, I think… and give large far shadows a better shot at rendering than mid-range tiny ones.