Soft 2D shadows

BlitzMax Forums/BlitzMax Programming/Soft 2D shadows

Grey Alien(Posted 2011) [#1]
Hi all, has anyone successfully made soft 2D shadows in a non GPU hungry way? Basically I can make a shadow on any sprite by using SetColor 0,0,0 and SetAlpha 0.4 say and then drawing at an offset, but it's a hard line shadow.

I can draw at a decimal offset for a slightly softer edge but if I want a lovely soft shadow I can't see a way to do it that doesn't involve: a) pre-drawing the shadow in a paint program b) constructing the shadow in many layers by drawing the sprite at different offsets/alpha to give that soft feel c) pre-calculating b.

Has anyone got any different techniques? Thx!


ziggy(Posted 2011) [#2]
Capturing the image of the shadow at half its size and draw it scaled up so it is blurer at its edges?


Gabriel(Posted 2011) [#3]
I've never actually used Max2D, so I'm not really sure what it's capable of. Does it give you control over mipmap bias? If so, a positive mipmap bias would force the texture to be blurry.


TartanTangerine (was Indiepath)(Posted 2011) [#4]
Hmm, sswift did a nifty one for Blitz3D back in the day... Roughly, it was pasting grey-scale versions of the image *around* (offset) at varying alpha.

I do acually have the Blitz Source but I'm not really sure if it's open source.

Tim.


TartanTangerine (was Indiepath)(Posted 2011) [#5]
@Gabriel - I would certainly go the Mip-map route also, but I can't remember if you can force it with BMAX either :)


Grey Alien(Posted 2011) [#6]
@Ziggy, that's a good quick solution that may work...

@Gabiel: I'm a noob at low level stuff, so I wouldn't know. Sounds cool if it worked.

@Indiepath: I have his sprite library but not the shadow one. Maybe he'd give me a copy ;-) Yeah I figured I have to do something like he is doing with my option b) above, several versions pasted at different alphas/offsets. Sounds a bit slow for rendering so I was thinking about constructing an entire shadow layer beforehand (it's for my static game grid) and then just chucking it all out at once.


AdamRedwoods(Posted 2011) [#7]
Does it need to be real-time? If not:

FastBlur (correct code at the bottom of thread):
http://blitzmax.com/Community/posts.php?topic=84109

You could black out your object, then run the blur on it.


Kryzon(Posted 2011) [#8]
Hi,
there's a 2D engine with realtime GPU shadows and other effects, the Ethanon Engine. I don't know if it's open-source, but at least the videos and screenshots can give you some idea of what to do.
From what I've seen, it's not only doable but gives much depth and realism when realtime.



EDIT: That might be a bit too much with your "non-GPU-hungry" requirement, but... oh well.

Last edited 2011


tonyg(Posted 2011) [#9]
Not sure how Rene managed it...
2D Realtime Shadow Module
but it worked well back then.
You can still download it to try out :

2D Realtime Shadow Module

and it links to Orangy Tang's original articled (now archived)

Last edited 2011


ImaginaryHuman(Posted 2011) [#10]
You apparently need to do a blur. You can render the shadows to a texture or draw them and grab the backbuffer into an existing texture (e.g. glCopyTexSubImage2D()), the draw them scaled up. But you'll probably want to do a gaussean blur to make it rounded rather than squary/blocky. You can do that in two passes by doing a horizontal stretch followed by a vertical stretch.


Grey Alien(Posted 2011) [#11]
Thanks for the ideas everyone! Probably pre-rendering all the shapes that need shadows as black, then blurring them will get me the result I need. Those other modules look cool but are overkill. I just need a blurry shadow round my match-3 grid is all.

@ImaginaryHuman: How to you get the backbuffer in DX? Do you know? I know that you normally do everything in GL ;-)

Last edited 2011


ImaginaryHuman(Posted 2011) [#12]
No idea in DX, sorry. I'm sure there must be a way that goes straight from buffer to texture.


col(Posted 2011) [#13]
Hey Grey Alien,
I'm playing with DirectD3D9 and Direct3DX9. You can get to the backbuffer using the normal Direct3D ,but you'll need access to the Direct3DDevice and use the raw DirectX commands. This isn't a problem but its 'non standard' BMax. You can easily modify one of BRLs files ( 1 single line function ) then its all yours. I'm not yet working on it, but I will create a new MaxD3DGraphicsDriver etc so I can create and encapsulate everything properly. for others to use.


Anyway should you want to know how to do it once you have the D3DDevice, you can use something like..

local pBackBuffer:IDirect3DSurface9
D3DDevice.GetRenderTarget( 0 , pBackBuffer)

This will store the existing backbuffer into the pBackBuffer.

You can then create a new IDirect3DTexture9 ( local pRenderTexture:IDirect3DTexture9 ) to use as a 'Render Target' with D3DDevice.SetRenderTarget.

In effect this becomes the new backbuffer as far as rendering goes. After using the BeginScene().....EndScene(), ( you dont use D3DDevice.Present() !!! yet ), then you flip back to using the original pBackBuffer by using

D3DDevice.SetRenderTarget( 0 , pBackBuffer )

before rendering as normal then using D3DDevice.Present() to display the original backbuffer.

Easy eh :0)

As I say I'm working on exposing the complete Direct3D9 and DirectD3DX9 interfaces and functions so we can use it in the same way as OpenGL is exposed to BMax in its 'raw' state.

Of course I'm sure someone else will have played with existing BMax commands and created a 'workaround'.

[Edited for grammar]

Last edited 2011


Grey Alien(Posted 2011) [#14]
Hi col, yeah that sounds fairly logical. good luck exposing the interfaces. I'm still using DX7 :-)


HrdNutz(Posted 2011) [#15]
yo Grey,

you could just, as ziggy suggested, rescale the pixmaps to like 10th of the original size and save as image copies, then draw with 0 color and 10x scale to get a softer version of the sprites.

another way would be drawing the scene at 10th of the original size, grabbing that to an image, and rendering stretched as shadow map.

or you might create your own low-res shadow map and filling it manually. shadeblend can be of service.

render-targets or shaders would obviously be a blessing, but max2d doesnt support.

either a shadow map or drawing the scene twice at different resolutions will achieve the effect, depends on the scene i guess to get best performance.

cheers,
Dima


SLotman(Posted 2011) [#16]
Why not just draw the same sprite, all black with different sizes and different alpha values? That would make the 'hard shadow' into a real-time 'soft shadow', with little to no costs (after all, you're using the same texture, just changing color/scale/alpha)

Here's a simulation of what I'm saying:



If done well, it can look really good :)


Oddball(Posted 2011) [#17]
Not read the whole thread so this may have been suggested.


I used this image to test


And here's what it looks like



SLotman(Posted 2011) [#18]
Using the same code/image above, here whats my 'idea' looks like:



Of course the method above is fine - but if you want to save RAM you can use this code - and the results are pretty good too :)

Oh, yeah: the actual 'result':



Last edited 2011


Grey Alien(Posted 2011) [#19]
Hey thanks for the suggestions everyone!

@Slotman, yeah I had thought to do it that way but I think it will need quite a lot of draws, maybe 4-5 to look smooth as you can obviously see the double shadow in your one.

I quite like the idea of making a smaller pixmap and scaling it up, that looks pretty good but I'd need to do more tests.

What I really need to do is draw the whole level first onto a texture and then turn that into the smaller pixmap, that should give me the perfect result. But how can I do that in DX without having to change the modules?


col(Posted 2011) [#20]
Hi Grey Alien,
I don't use the 2D functions but after briefly looking through the docs, would something like this work

Use CreateImage to create an image and make it smaller than you need. Maybe a 1/4 or even smaller of the original size. Experiment to get it looking good.

Use LockImage to allow drawing directly to the Image - in effect drawing to the texture.

Just an idea, if may be too 'low level' for what your after?
[EDIT] I'm not sure on the speed impact this would have ?[/EDIT] I'm just throwing ideas around for you :-)

[EDIT 2] Dont worry, I can see this wouldn't work at all!! You want to use BMax commands to draw to the texture.[/EDIT 2]

GrabImage goes from backbuffer to texture?

Last edited 2011


matibee(Posted 2011) [#21]
What I really need to do is draw the whole level first onto a texture and then turn that into the smaller pixmap, that should give me the perfect result. But how can I do that in DX without having to change the modules?


I assume this is simple level geometry for a single screen, as this will be too slow to do per-frame but it's all plain bmax and nothing driver specific...

1) Make a pixmap large enough to cover your level (screen size?)

2) Manually draw the level into that pixmap by retrieving a pixmap from each level tile and plotting it using this function..

Rem
bbdoc: Alpha blends two 32 bit ARGB pixels together.
about: Useful when overlaying one alpha'd image onto another.
end rem 
Function mbm_AlphaBlendPixels:Int( dest:Int, src:Int )
	Local _dest:Int[] = [ (dest Shr 16) & $FF, (dest Shr 8) & $FF, dest & $FF ]
	Local _src:Int[] = [ (src Shr 16) & $FF, (src Shr 8) & $FF, src & $FF ]
	Local alpha:Int = ((src Shr 24) * 100) / 255
	Local invAlpha:Int = 100 - alpha
	Local v:Int = dest & $FF000000
	For Local t:Int = 0 To 2
		v :+ (( _src[t] * alpha + _dest[t] * invAlpha ) / 100 ) Shl ((2-t)*8)
	Next 
	Return v
End Function 


That function could be modified to drop the rgb altogether and make a completely grey image with the alpha for each pixel representing the combined alpha of the source(s). Untested...

Function mbm_AlphaBlendAsShadow:Int( dest:Int, src:Int, grey:Int = 32 )
	Local _dest:Int[] = [ (dest Shr 16) & $FF, (dest Shr 8) & $FF, dest & $FF ]
	Local _src:Int[] = [ grey, grey, grey ]
	Local alpha:Int = ((src Shr 24) * 100) / 255
	Local invAlpha:Int = 100 - alpha
	Local v:Int = dest & $FF000000
	For Local t:Int = 0 To 2
		v :+ (( _src[t] * alpha + _dest[t] * invAlpha ) / 100 ) Shl ((2-t)*8)
	Next 
	Return v
End Function 
End Function 


3) Scale down your shadow pixmap and make an image from it to use in real-time.

Sorry I don't have time to test this morning, but I might try it out later.

Cheers


Grey Alien(Posted 2011) [#22]
Thanks Matibee, yes that should work if it's not too slow. May try it out if time (otherwise I'll have to wait until the next game).

Ideally I was trying to avoid low level pixel pushing as it would be much easier to just draw what I have using existing code then grab it. But I'm wondering if I can use GrabImage after a load of draws without using Flip (I don't want the player to see it). And if that's always reliable. Plus if it'll grab alpha too.


col(Posted 2011) [#23]
looking in the source - Yep, it grabs the alpha too. everything is in 32 bit.


HrdNutz(Posted 2011) [#24]
G,

this is purely speculative, and hasn't been tested, but any who;

regarding drawing whole scene at smaller scale:

cls to white,
draw scene scaled down with black color and maybe some alpha,
grab back buffer into the smaller image to match the scale,
draw background and anything that goes below shadow layer (or cls),
draw the new shadow map image stretched using SHADEBLEND mode,
render scene normal,
flip.

can play with stretching, for instance stretch to larger area than screen so shadows have offsets.

performance culprit here is the GrabImage;
few things can be done to increase performance;
find a better/faster version of GrabImage,
or use render targets if supported

some quick search shows there are ways and resources out there to accomplish this, there are many places online to find useful information regarding dx7, so render target implementation should not be as difficult as it looks, I know DX9 and OGL have better support, but 7 can do it too.
http://www.gamasutra.com/view/feature/3399/rendering_to_texture_surfaces_.php?print=1
http://developer.download.nvidia.com/assets/gamedev/docs/top5perfkill.pdf
http://gpwiki.org/index.php/DirectX:Direct3D:Tutorials:VB:DX7:Hybrid_Engine

considering the fact that you redraw the scene at least twice in any implementation, might be a lot easier/faster to just use the first option and render small versions of sprites as shadows behind actual sprites;

scaled sprites feature: better performance using max2d implementation, shadow sprites are smaller than originals resulting in faster rendering due to low resolution footprint, softness and detail of the shadows can be easily adjusted using different pixmap scales - smaller versions are softer and less detailed, variable blend modes and setcolor commands can achieve unique effects.

shadeblend shadowmaps feature: ultimate control over the shadowmap since the whole map is baked into single pixmap or image, custom filters or modifications can be applied to the whole map to achieve unique effects - such as messing with individual pixels or doing projection shadows like visibility occlusion, can make completely custom shadows not restricted to sprites but any variation of geometry, shadows do not have to follow traditional philosophy and can be manipulated in any way imaginable as you are modifying a single surface that shadows the whole screen.

i think depending on the game/scene/situation different techniques can be applied and even combined for best results.

shadeblend mode is also useful for static or dynamic lighting over 2D scene by composing the shadowmap accordingly with as many lights as required using different blend modes and light colors to complete the final shadowmap. GrabImage is the culprit of bad performance here also while RenderTargets deal with this problem quiet nicely.


Cheers,
Dima

P.S.
where is Indiepath hiding these days? o yeah, playing with his Monkey probably. But if i recall correctly, he has achieved render targets in DX7 with BMAX long time ago, before dinosaurs extinction! Maybe Indie could share his secret render target code used in the module? In any case the info is out these and it's not as difficult as it may appear.

Last edited 2011

Last edited 2011

Last edited 2011


Grey Alien(Posted 2011) [#25]
@col Also looks like GrabImage DOES work without having to call Flip first so I'll probably do that.

@HrdNutz I've never tried SHADEBLEND, sounds like it could work.

Yes Indiepath made a RenderToTexture mod, but it broken with certain BMax Updates, but I think he fixed it. If I could render to that it may be quicker than GrabImage.