Shadows

Monkey Forums/Monkey Programming/Shadows

Raz(Posted 2014) [#1]
Shadows! Would love to hear everyones thoughts on this.

I am creating a general use 2D isometric world (Zelda 3 view) and I would like to automatically create shadows where required.



1 - Shows a 3x3 rock formation.
2 - Shows the tiles I use to draw said 3x3 rock formation. You can see that I draw columns for each cell.
3 - Shows the shadows I would like drawn for said formation

So, what would you do to implement such a thing? I'm sure there is a really elegant way of doing this, but I've not found it!

-- BONUS QUESTION --

4 - Shows a different type of column which is slightly taller than the others.
5 - Shows this column at the centre of a 3x3 formation. You will notice it is projecting a shadow on to the "roof" of the shorter items.

How would you implement this?

Thanks as always!
-Chris


Gerry Quinn(Posted 2014) [#2]
If you want shadows on the ground only, it's easy. Just pair every object with an image of its shadow, and draw all the shadows first.

If you want shadows falling on objects, it's a bit more difficult. Maybe you need to draw everything in layers from the ground up, and calculate where the shadow of each 'voxel object' hits a lower object. (E.g. your 3-high columns would each be drawn as 3 blocks, each with its own shadow, starting from the bottom.)

If the image is complicated with many voxels, I guess you need some clever math to analyse things.


Nobuyuki(Posted 2014) [#3]
In 3d applications, shadows are often created with the help of the stencil buffer and a few cast rays. For your game, you can use a variation of autotiles instead. As long as the height of things casting shadows is always constant, a simple mask can be generated from directly underneath all shadow casting objects, and blitted to a special shadow layer at an offset. Basically, a very low-res drop shadow, but with autotiles to 'pretty up' the hard edges.

You could also add height map data to the shadow cast objects to change the mask shape but be aware that shadows won't overlap the tiles that cast them. It might be better to just make static shadow layers yourself using autotiles biased in the direction you want them cast.


Raz(Posted 2014) [#4]
Gerry : This is what I had originally done except for the fact I if I "cast" a shadow from each item, there would be lots of overlaps. To combat this I only cast shadows from objects that had no neighbours below them and or to the right of them. This still caused some overlaps, so I had to get cleverer and have the lower items cast a shadow from their bottom edge and have the right hand objects cast a shadow to the right. Objects in the bottom right of the formation cast both sets of shadows.

While it worked, it didn't feel very elegant to me.

Nobu : So essentially, render out the shadows per different object and then rerender that shadow when required? I guess creating an image for the whole shadow map would be too much.


Gerry Quinn(Posted 2014) [#5]
Of course - I forgot that shadows will be semi-transparent so overlapping them will cause problems.


Nobuyuki(Posted 2014) [#6]
@Raz: I hadn't taken into account that you might've wanted dynamic shadows at all. That's another ball of wax entirely. If you're gonna do that, you might as well just directly use the stencil buffer to do this. As long as you stick to OpenGL targets, this is entirely possible with nDrawExts2. Use whatever technique you want to blit masking areas to the stencil buffer (heightmaps and all), then render a big black rect at the alpha you want underneath all your sprites inside the stencil mask. Note that the rendering of the stencil alpha only supports a 1 bit threshold (even though the buffer itself can be 8 bits) -- This is because the pass/fail test is 1-bit, kinda a limitation of the way you can handle the data in OpenGL 1.1, I think, but I'm no expert...... Anyway, the shadows will be "hard", but it's possible to make them completely dynamic this way..


Raz(Posted 2014) [#7]
Oh no, just to be clear, I don't want dynamic shadows at all. I'm only interested in casting the shadows on these static objects (in as elegant a way as possible).

Again, that said, the mask rendering does sound exactly like what I'm after! (tis a shame I had hoped to target XNA, but GLFW should work!). Regardless masking sounds like the most elegant way of doing this as it means I don't have to worry at all about shadows overlapping (will check out nDrawExts2 thank you!)

Note that the rendering of the stencil alpha only supports a 1 bit threshold

Sorry, are you able to explain what you mean by this? Stencil rendering is either on or off per pixel?


Raz(Posted 2014) [#8]
I need to work on the sizes and what not, but Nobu, you're a star :D Stencil shadow drawing works an absolute treat.



The great thing is, I can just edit the shadows as necessary without any major work. I'm doing the following to render the shadow layer, does this look correct?

ClearStencil()
EnableStencil()
DrawToStencil(True)
shadowLayer.RenderAll()
DrawToStencil(False)
SetColor(0, 0, 0)
SetAlpha(0.5)
DrawRect(0, 0, AppWidth, AppHeight)
DisableStencil()



Nobuyuki(Posted 2014) [#9]
Looks good to me. Glad it worked out for you :3


Paul - Taiphoz(Posted 2014) [#10]
Raz is that Stencil code the glStencil stuff or something you have written?


k.o.g.(Posted 2014) [#11]
@Raz

Can you post the Example code for the Community? To show, how you done that?


Raz(Posted 2014) [#12]
OK sure will do later on but really I'm not doing much personally, I'm using Nobu's nDrawExts2 - https://github.com/nobuyukinyuu/nDrawExts2 and building for the Desktop target. The code (taken pretty much directly from the nDrawExts2 example) below is doing all of the clever stuff.

ClearStencil()
EnableStencil()
DrawToStencil(True)
shadowLayer.RenderAll() ' <-- Drawing the shadows here
DrawToStencil(False)
SetColor(0, 0, 0)
SetAlpha(0.5)
DrawRect(0, 0, AppWidth, AppHeight)
DisableStencil()


I'm drawing all of the shadows to the stencil, and then rendering a black rectangle that fills the screen based on that stencil. shadowLayer.RenderAll() is just rendering the shadows cast by the objects (which are just images of solid shadows that I've drawn myself).

Will post again later.


Gerry Quinn(Posted 2014) [#13]
If the objects are static, you have extra options as you can use ReadPixels and WritePixels to make pre-generated images. So you could deal with overlapping shadows by drawing them as an opaque image, then reading the image and drawing it with some transparency.