
As some of you might have noticed - I love coming back to old projects and doing them right (see Dragging in 3D, various Sound/particle projects, and so on). Well, shadows in no different. One of my first posts EVER was Papervision Shadow Casting - it gave developers the ability to shade faces of an object material with "casters" - which obviously cast shadows. It was slow, looked decent if you knew how to hack it, and overall it sucked, at least I think so.
Well, tons of people have written me in response to that - wondering if I would EVER convert it to 2.0. The answer - not yet. But I made something very similar, and what I think is much cooler - and runs much better - and looks much better. The concept with this class is you can cast shadows - PRECISE shadows - onto a plane. Why only a plane? Because if we do anything OTHER than a plane it will be much heavier on the CPU. This technique is very fast - since we know the dimensions of a plane, we can easily determine the UV coordinates of a plane/ray intersection - without having to riddle through faces and checking sides of triangles.
So, lets talk about the class itself. It is a stand-alone class that will let you cast the shadow of any object onto any plane that has a MovieMaterial. You can set your MovieMaterial to animated and it will always be live - or you can call "drawBitmap" after you cast a shadow - which can save on performance if you dont need to update every frame. I require a MovieMaterial because it makes it very easy to do cool stuff with the shadow - Blend/Alpha/Filters - you can apply ALL these things to your shadow. Here's how:
Actionscript:
-
var shadowCaster:ShadowCaster = new ShadowCaster("shadow1", 0, BlendMode.NORMAL, 0.75, [new BlurFilter(4, 4, 2)]);
Here are the parameters:
- uid : String - a unique identifier that tells the shadowCaster what layer to draw on. It will create this layer in your MovieMaterial on its first cast.
- color : uint - Color of the shadow
- blend : String - BlendMode to use. Defaults to Multiply for nice shading.
- alpha: alpha of the shadow
- filters: an array of filters to apply to the shadow. If you pass null a default BlurFilter will be used.
Thats it. The last thing you have to do is cast your shadow!
Actionscript:
-
shadowCaster.castModel(dae, light, plane, castPerFace, cull);
Here, you can cast an entire model - all of its children included. The parameters should be self-explanatory, but i will go over them quickly. The first is the object you want to cast shadows off of. The second is the LightObject3D you wish to use. The third is the Plane you wish your shadows to be cast onto. The fourth parameter takes a boolean. if you specify true, you will get a precise per-face shadow. It will look like you imagine a shadow should. If you pass false, the shadow will be cast off the bounding sphere of the object. Much faster processor wise, but the shadow will only be a circle, so its not as impressive visually. The last parameter is backface culling. This means it will only render faces that look towards the light. It will cut your projections in half most times - but I have found it to be unreliable. Feel free to take a look at the code and fix it for me.
You can also call castFaces or castBoundingSphere - if you wish to cast per object rather than an entire do3d and its children. They take similar paremeters, so you should be good to go. You can toggle between these two modes in the demo with the "B" key.
Another to note about the ShadowCaster class is that you can change the type of shadow that is drawn. The two types are DIRECTIONAL and SPOTLIGHT. DIRECTIONAL light takes the direction of the light rays from the light source to the object center. All shadows are cast in that direction. This will give you a uniform look - like you would imagine if the sun were lighting something. SPOTLIGHT casts a ray through each vertex - making the light cast long - similar to something you might see in the movies. Play with both. You can set them via the shadowCaster.setType() function - passing in either ShadowCaster.DIRECTIONAL or ShadowCaster.SPOTLIGHT. In the demo you can toggle between these with the "T" key.
The last thing to note about the ShadowCaster class is it stores references to all objects that are cast - this greatly increases render time. You can "flush" the memory so to speak, if you plane or object are moving, by using the invalidate() function. Alternatively, you can simply call invalidateModel, invalidatePlane, or invalidateTarget. These will only invalidate specific parts, saving on re-processing hits. You can take a look in the code to see which each does - but the name will hopefully help you understand.
So thats it! You can cast shadows from anything you want now!
View the Cow Demo
Here is another quick demo I threw together just to show how easy it is - even with animated models:

View the Monster Demo
I simply call shadowCaster.invalidateModels() after every castModel() call. It handles the rest.
Hope you enjoy it. Also give thanks to everyone who helped me debug the Gouraud problem - especially Seb, Benoit, and Tim. The bug has been resolved and you can all now use GouraudMaterial with a smile.
Get the Cow Demo Source and ShadowCaster
Enjoy!
33 Comments | In: general | | #