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:
-
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!
-
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!
Here is another quick demo I threw together just to show how easy it is - even with animated models:
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!


82 Comments, Comment or Ping
I think i just had a geekspasm.
very VERY cool. awesome work!
July 10th, 2008
Nice, very nice
July 11th, 2008
Fantastic!!!
July 11th, 2008
Andy you rocks!
July 11th, 2008
Congrulations !
You rocks !
July 11th, 2008
Very cool, i wonder how easy it would be to do collision detection against shadows, to see if another 3d object was touching it.
July 11th, 2008
Lovely! Nicely done!
July 11th, 2008
You rock!
July 11th, 2008
You are great! And you rock!
July 11th, 2008
Very cool! Andy, you work is an inspiration to me.
July 11th, 2008
Sick.
July 11th, 2008
looks great and runs surprisingly smooth
July 11th, 2008
Nice. But why not use a ShadedMaterial? So you can get rid of the clone hack. Here is what I’ve done modifying your code:
//our main model.
dae = new DAE();
var mats:MaterialsList = new MaterialsList();
mats.addMaterial(new ShadedMaterial(movieMaterial, new GouraudShader(l, 0xffffff, 0×171717)), “mat0″);
dae.addEventListener(FileLoadEvent.LOAD_COMPLETE, onLoad);
dae.load(XML(new asset()), mats);
July 11th, 2008
Nice one! But I think 2.0 is a must.
July 11th, 2008
@ck - I probably should have expalined the hack in the post :).
I originally did use a shaded material - but it didn’t work for a couple reasons:
1) Using regular composite mode wouldn’t work since there was overlapping UV in the model. This made faces flicker incorrectly since they were being “shaded” more than once. I tried fixing the model but i suck at modeling so I stopped trying.
2) Using per-tri composite mode worked fine - but it was slow. Too slow for me to call acceptable for the demo.
In most cases you are correct - but this hack works if you have overlapping UV and per-tri just won’t cut it - it runs quite fast.
@Nate - this is for 2.0 - unless i misunderstand your comment
July 11th, 2008
Loved your older shadow post, it was te best implementation of shadows in PV3D that time. can’t wait to play around with this version
July 14th, 2008
Nice one! I always wondered how to make real shadows in Papervision 2. Thanks!
July 17th, 2008
Nice one! Thanks a lot for sharing your work.
July 17th, 2008
Hello, Andy. Good work. But can you put a Monster Demo source (with COLLADA) - I want to compile it with last version PV3D - I try to make animated model (witch work perfect with previous version of PV3D), but get a problems (it seems work not properly with layers).
Thanks.
Andrew.
July 18th, 2008
Hi.. AWESOME WORK.. I love 3d and I am curious about the animated Monster.
Was teh animation done in PV3D or in 3DsMax or a similar program?
Would appreciate a mail with a short Info.
Keep on going, you rock!!
July 23rd, 2008
Hi Andy
Does this also work voor Planes with a MovieMaterial with transparant parts in it?
I am struggling a bit with it…keep getting a square as shadown instead of the desirable result (exact shadow of the element WITH its transparant parts.)
Thx in advance..
Wink
July 23rd, 2008
Thank you! This is exactly what i was looking for
July 28th, 2008
Hi Andy
it’s AWESOME WORK.
i appreciate that you opend source.
i used your shadowcaster on my flex project but i had problem on release.
i can see properly on debug mode, but it something wrong with depth on release mode.
what i am missing ?
my devel invironment like below
flexbuilder version - 3.0.19*
pv3d - greatwhite, lastest
Thank you in advance..
July 31st, 2008
Hi Andy,
First of all, great work! This looks incredible! I am looking for something exactly like this for a project that I’m working on right now.
I looked at your source and had some questions:
- what is “containerSprite” when you set up your viewportLayerSortMode?
- Can a MovieAssetMaterial be used instead of a MovieMaterial?
- Is the cloning hack necessary or is that simply because you were using a DAE?
I am trying to cast a shadow from a Sphere primitive onto a plane (both shaded with a Movie Asset Material, but I keep getting this error:
TypeError: Error #1009: Cannot access a property or method of a null object reference…
Everything works except for my castModel method call. Any ideas? Sorry to bug you with Newb questions, but this has me stumped.
Thanks,
Andy
August 2nd, 2008
“…But can you put a Monster Demo source (with COLLADA) - I want to compile it with last version PV3D - I try to make animated model (witch work perfect with previous version of PV3D), but get a problems (it seems work not properly with layers)….”
Helllo, Andy! Please add “Monster Demo source” for downloads!
Thanks!
August 3rd, 2008
I’d just like to thank you for taking the time to create this internet website. It has been extremely helpful
August 4th, 2008
Good-looking site. Congratulations.
August 5th, 2008
hi
andy. awesome work - as always.
well i think you should add a small if - statement to your code. otherwise it causes erros when not working with collada objects.
line 80
————————————————————————————————
private function getChildMesh(do3d:DisplayObject3D, ar):void{
if(do3d)
{
if(do3d is TriangleMesh3D)
ar.push(do3d);
for each(var d:DisplayObject3D in do3d.children)
getChildMesh(d, ar);
}
}
————————————————————————————————
don’t know if this is the best way to solve the problem but it works for me
greets
alex
September 11th, 2008
Hi Andy
Thanks for sharing this! Incredible!
Finally got it working in Flash Develop.
September 12th, 2008
Andy,
what software did you use to export your lizard animations into collada?
thanks,
Shannon
October 15th, 2008
This is sooo cool! Really great class…but I can’t get it to work…
Any idea why I keep getting error : Error #1009: Cannot access a property or method of a null object reference.at ShadowCaster/projectVertex() ?
I have been checcking it out and tracing all possible properties used in the projectVertex method but don’t find the problem
Many thanks to help me out.
October 16th, 2008
Hello Andy! Nice class this shadowcaster is!
I was wondering though if it would be hard to also take the texture into account. I am doing a project in which we have a lot of textures on planes with alpha-pixels, but the shadows cast by shadowCaster are offcourse just the square of the geometry.
thanks,
Sander
October 24th, 2008
hey man … you ROCK.. this is awsome.. one question… do i have to use index sorting of layers and why? i’m trying to cast a shadow from a DO3D that has children on to a plane with the code from this example but i’m not getting a shadow.. no error.. i tried your example for reflections on the same code and it works.. but i ignored layer management code and i’m using scene3d not basicview… also the DO3D i’m trying to cast contains planes and text3D objects from vectorvision.. any suggestions?
October 31st, 2008
it’s working .. didn’t have shadowCaster.invalidateTargets(); in the ticker… thank you for this beautiful mind expansion and eye-opener … extra cool work
October 31st, 2008
Hello,
I try to have a shadow of a flat cube but it does not work:
cube = new Cube(new MaterialsList( { all: mat } ), 450, 290, 4, 10, 10, 2);
Well , there is a shadow and it moves while rotating the cube, but it is not correct. The shadow ignores the rotation of the cube around the z-axis completely.
The light is at (0,0,-800). The plane at (0,0,100); And the cube in between.
What can I do ?
November 29th, 2008
How would this code change if there were 2 monsters or 2 cows?
Reflection code works nice, but this?
.V
December 3rd, 2008
@Henning
Have you tried using shadowCaster.invalidate() in your ENTER_FRAME event?
December 13th, 2008
Andy,
This is awesome. I am definitely going to use this in some of my upcoming projects.
Quick note and question:
In the download in the ShadowCaster.as file, line 83 that begins with private function getChildMesh(do3d:DisplayObject3D, ar):void
It looks like you need to provide the Array data type to the ar parameter. Once I change ar to ar:Array, everything works.
And for my question…
Do you have any plans to dynamically change the alpha and blur of the shadow as it moves towards the light and further away from the surface. If you lift an object up off of your desk, you’ll notice that the shadow becomes lighter and blurrier.
December 13th, 2008
Looking great Zuppy,
I have to say that without using invalidate on my Shadow Class each processFrame() the shadows get all out of alignment.
function processFrame():void{
//updates the light
WORLD.light.copyPosition(WORLD.sun);
WORLD.light.lookAt(WORLD.floor);
//clears all shadows
WORLD.shadow.getCastClip(WORLD.floor).graphics.clear();
WORLD.shadow.invalidate();
for each (var plane3D:Plane in PAPERBASE.current_scene.objects){
if (plane3D != WORLD.floor && plane3D != WORLD.sun){
// allows all other objects besides the floor to be cast.
WORLD.shadow.castFaces(WORLD.light, plane3D, world.floor, false, false);
}
}
December 15th, 2008
hi guys, I could like to know how to have a shadow of my cube on planes… can you help me please? Have a nice year!!!
link: http://www.openspaces.it/test/cube_mk.html
my code:
//Test marco da sorgenti e tutorial pv3d
//importo i file necessari
import org.papervision3d.scenes.*;
import org.papervision3d.cameras.*;
import org.papervision3d.objects.*;
import org.papervision3d.objects.special.*;
import org.papervision3d.objects.primitives.*;
import org.papervision3d.materials.*;
import org.papervision3d.materials.special.*;
import org.papervision3d.materials.shaders.*;
import org.papervision3d.materials.utils.*;
import org.papervision3d.lights.*;
import org.papervision3d.render.*;
import org.papervision3d.view.*;
import org.papervision3d.events.*;
import org.papervision3d.core.utils.*;
import org.papervision3d.core.utils.virtualmouse.VirtualMouse;
import flash.filters.*;
import org.papervision3d.objects.primitives.Plane;
import org.papervision3d.lights.PointLight3D;
import org.papervision3d.materials.shadematerials.FlatShadeMaterial;
import org.papervision3d.lights.PointLight3D;
import org.papervision3d.render.BasicRenderEngine;
import org.papervision3d.materials.shadematerials.GouraudMaterial;
import com.everydayflash.pv3d.ShadowCaster;
// inizializza il viewport
// non specificando nessun parametro il viewport
//si adatta allo stage posizionando l’origine
//degli assi al centro dello stesso
var viewport:Viewport3D = new Viewport3D(0, 0, true, true);
addChild(viewport);
viewport.buttonMode = true;
//il mio pennello
var renderer:BasicRenderEngine = new BasicRenderEngine();
//la mia scena
var scene:Scene3D = new Scene3D();
//_root.scene.ambientLightIntensity = 1;
//vista camera
var camera:Camera3D = new Camera3D();
camera.zoom = 11;
camera.focus = 70;
//materiale x faccia
var mam:MovieMaterial = new MovieMaterial(face);
mam.interactive = true;
mam.smooth = true;
mam.animated = true;
var mam2:MovieMaterial = new MovieMaterial(face2);
mam2.interactive = true;
mam2.smooth = true;
mam2.animated = true;
var mam3:MovieMaterial = new MovieMaterial(face3);
mam3.interactive = true;
mam3.smooth = true;
mam3.animated = true;
var mam4:MovieMaterial = new MovieMaterial(face4);
mam4.interactive = true;
mam4.smooth = true;
mam4.animated = true;
var mam5:MovieMaterial = new MovieMaterial(face5);
mam5.interactive = true;
mam5.smooth = true;
mam5.animated = true;
var mam6:MovieMaterial = new MovieMaterial(face6);
mam6.interactive = true;
mam6.smooth = true;
mam6.animated = true;
var cube:Cube = new Cube(new MaterialsList({front:mam, back:mam2, left:mam3, right:mam4,top:mam5, bottom:mam6}), 200, 200, 200, 10, 10, 10);
scene.addChild(cube);
//PIANO ORIZZONTALE
//crea una maglia, ossia si visualizzano solo i contornivar materiale:WireframeMaterial = new WireframeMaterial();
//definiamo la larghezza e l’altezza
var w:Number = 800;
var h:Number = 800;
//il numero di segmenti
var sW:Number = 1;
var sH:Number = 1;
var plane:Plane = new Plane(mam5, 600, 600, 3, 3);
//inseriamo quindi alcuni valori a piacere
plane.x = 0;
plane.y = -200;
plane.z = 0;
plane.rotationX = 90;
plane.rotationY = 0;
plane.scaleZ = 1;
//attiviamo la doppia faccia, in modo da colmare
//anche la seconda faccia del piano con una texture
//materiale.doubleSided = true; >>A ME DA ERRORE
//ricordiamo sempre di aggiungerlo alla scena
scene.addChild(plane);
//PIANO VERTICALE
//crea una maglia, ossia si visualizzano solo i contorni
// NONVA>>var materiale:WireframeMaterial = new WireframeMaterial();
//definiamo la larghezza e l’altezza
var w2:Number = 800;
var h2:Number = 800;
//il numero di segmenti
var sW2:Number = 1;
var sH2:Number = 1;
var plane2:Plane = new Plane(mam5, 600, 600, 3, 3);
//inseriamo quindi alcuni valori a piacere
plane2.x = 0;
plane2.y = 0;
plane2.z = 300;
plane2.rotationX = 0;
plane2.rotationY = 0;
plane2.scaleZ = 1;
//attiviamo la doppia faccia, in modo da colmare
//anche la seconda faccia del piano con una texture
//materiale.doubleSided = true; >>A ME DA ERRORE
//ricordiamo sempre di aggiungerlo alla scena
scene.addChild(plane2);
//il movimento sul centro
addEventListener(Event.ENTER_FRAME, loop);
function loop(e:Event):void
{
var xDist:Number = mouseX - stage.stageWidth * 0.5;
var yDist:Number = mouseY - stage.stageHeight * 0.5;
cube.rotationY += xDist * 0.02;
cube.rotationX += -yDist * 0.02;
renderer.renderScene(scene, camera, viewport);
}
//link al clik diverso su ogni faccia
face.addEventListener(MouseEvent.CLICK, faceClick);
function faceClick(e:MouseEvent):void
{
navigateToURL(new URLRequest(”http://www.yahoo.com”));
}
face2.addEventListener(MouseEvent.CLICK, face2Click);
function face2Click(e:MouseEvent):void
{
navigateToURL(new URLRequest(”http://www.google.com”));
}
face3.addEventListener(MouseEvent.CLICK, face3Click);
function face3Click(e:MouseEvent):void
{
navigateToURL(new URLRequest(”http://www.flickr.com”));
}
face4.addEventListener(MouseEvent.CLICK, face4Click);
function face4Click(e:MouseEvent):void
{
navigateToURL(new URLRequest(”http://www.youtube.com”));
}
face5.addEventListener(MouseEvent.CLICK, face5Click);
function face5Click(e:MouseEvent):void
{
navigateToURL(new URLRequest(”http://www.facebook.com”));
}
face6.addEventListener(MouseEvent.CLICK, face6Click);
function face6Click(e:MouseEvent):void
{
navigateToURL(new URLRequest(”http://www.adobe.com”));
}
//PUNTOLUCE
var pointLight:PointLight3D = new PointLight3D(true);
pointLight.moveBackward(1000);
pointLight.x=500
pointLight.y=500
pointLight.z=400
scene.addChild(pointLight);
December 29th, 2008
Thanks for this. One question, what is “new texture()” in the line :
var bmp:BitmapData = Bitmap(new texture()).bitmapData;
from the “CastView1.as” example?
January 8th, 2009
Hi Andy,
thank you for this tutorial.
I am trying to mesh this one with the other concerning dragging.
I have a plane, a cube (instead of the cow) and a sun.
Drag & drop works fine. I can move both sun and cube.
While dragging the cube, the shadow ‘casted’ in the plane updates correctly (and the cloned material get updated according to corresponding distance to light).
However, when I move the sun (I use light.copyPosition(sun) to tie sun and light), only the shadow on the cube’s faces updates. The casted shadow disappers and doesn’t return anymore.
I tried every invalidating method I found in you class but without success.
Thank you very much for your work!
Veronica
January 29th, 2009
If you want to use this code to cast shadows against a translated mesh, check out the tutorial at http://hubpages.com/hub/Papvervision-3D-programming-tutorial—Casting-Shadows
February 1st, 2009
Great work !!
March 24th, 2009
Hi Andy, thanks for your hard work on this. Your libraries are amazing!
I’ve put together two versions of what I’m doing, one that works with the perFaceCasting and one that doesn’t without it. I would like to use the one without it to save some CPU juice but the shadows act weird. It’s like they bounce under the spheres (see demo at http://www.federicocalvo.com/marbles/7_bad.html), or see the my post about this at http://blog.federicocalvo.com/2009/04/casting-shadows-in-papervision3d.html
Could this be happening because the spheres are rotating?
April 2nd, 2009
Fixed (or hacked really) my problem with the non perFaceCasting, that is using the ShadowCaster.castBoundingSphere() method. My problem was that the shadow was being affected by the rotation of the sphere (see my previous post), so I changed the following in the ShadowCaster class:
FROM
var dx:Number = r2d.x-c2d.x;
var dy:Number = r2d.y-c2d.y;
var rad:Number = Math.sqrt(dx*dx+dy*dy);
TO
var dx:Number = r2d.x-c2d.x;
var dy:Number = r2d.y-c2d.y;
var rad:Number = b.radius/1.1
Divided by 1.1 to make the shadow a little smaller than the radius. It’s not accurate but it works and hopefully somebody will address this in a more professional/consistent way.
April 6th, 2009
I need a help… I am doing a project with involve shadows in 3D, but i dont have a moviematerial as a plane, because the floor is invisible…
I am placing the floor with normal flash 2d movie clip… and it cannot be in the 3d world…
I have solver that because my movie its too big and stay moving in the stage so i need a more simple and less cpu way…
the plane that i am using its a wireframematerial with alpha 0
Any tnx will be most wellcome,
Keepup with the good job Zupko!
May 15th, 2009
Hey - awesome demo - will this work in pv2.0, I can only get it to work in 1.5
July 21st, 2009
Hm, I can’t get it to work with an animated MD2 model. If I move the Light, the shadow changes, but it does not reflect the animation of the model itself. Did anyone try this?
July 31st, 2009
This is great, thanks again. Im curious , my object that is casting the shadow does not seem to be reacting to transparency ( which is a basic 3D spin of some text ), i get either the bounding box of that movie clip/material or its bounding sphere, is there a way to get it to rect to the shape of whats transparent and what isnt .
Again Thanks,
Chris
September 6th, 2009
This is a swell piece of code. However, what licensing do you intend people to use it under?
September 26th, 2009
Andy ( not Andy ) and Kevin,
I’ve find the (pretty simple ) solution for the 1009 error…
“TypeError: Error #1009:: Cannot access a property or method of a null object reference…
at ShadowCaster/projectVertex()
at ShadowCaster/castBoundingSphere()
at ShadowCaster/castModel()
at…”
If you look at the projectVertex(), you’ll see that it needs the _type const…
So we have to define it..
with :
shadowCaster.setType(ShadowCaster.DIRECTIONAL ); // or SPOTLIGHT
that solve the problem for me…
I know, 1 year year to late
have a nice day.
September 29th, 2009
hey all!
i’m trying to use a jsphere instead of a .dae model,
shadowCaster.castModel(jSphere, l, plane, !useSphere, cull);
but I get the error
cannot convert jiglib.geometry::JSphere@20c5e791 to org.papervision3d.objects.DisplayObject3D.
any help would be awesome!
~m
October 7th, 2009
fyi this is how i’m creating the jsphere
import jiglib.plugin.papervision3d.Papervision3DPhysics;
private var physics:Papervision3DPhysics;
sphere = physics.createSphere(mat, 50, 60, 60);
which gives me a jiglib rigid body sphere
October 7th, 2009
Hi,
I am very new to papervision3D and trying to create some shadow effect of globe. It would be great if anyone could tell me what do I need to do if I have to create same shadow effect of globe.
December 2nd, 2009
Hi All,
I am trying to run this code of shadow caster. It’s giving me error message “A conflict exist with inherited definition flash.display:DisplayObject.alpha” . I am very new to flash programming and papervision. It would be great if anyone can help me out with this.
December 3rd, 2009
i cant believe its so easy. papervision is kills any commercial project.
January 6th, 2010
Hi,
Somone asked a question earlier but no one gave an answer:
my object that is casting the shadow does not seem to be reacting to transparency ( which is a basic 3D spin of some text ), i get either the bounding box of that movie clip/material or its bounding sphere, is there a way to get it to rect to the shape of whats transparent and what isnt .
Anyone have any idea on this?
January 30th, 2010
Reply to “Casting Shadows In Papervision3D - Redux”