The Old Snow Trick – In the Third Dimension
While working on a little Valentine’s Day ecard for the most wonderful wife in the world, I suddenly had an idea…
Snow effects have been done in Flash since at least Flash 5 (maybe even Flash 4 if I think back hard enough), but I have yet to see a Flash snow storm in “true 3d”. Well, at least not until now. Seemed the new Particle API in Papervision3d was just the way to go for such a thing. Probably one of the oldest and nicest looking examples of snow in Flash, is the one presented on Kirupa.com. I really like the motion of that version, so borrowed a bit of the math for my own updated version. Also, I like the idea of using depth of field with papervision, so whilst borrowing, I boosted a bit of mrdoob’s “brute force” approach for depth of field. I didn’t go crazy with 200 textures though. I figured 4 were enough. No blur, a light blur, a medium blur, and a heavy blur. To be honest, you can’t really even notice the DOF anyway, but it’s there, and that’s all that matters. Add a little panorama and suddenly it’s snowing everywhere you look. Now, if I could just figure out how to keep the flakes from “sticking” to the camera, I’d have it made in the shade. I’m not sure if this is a clipping problem or what. If anyone knows why that happens, post a comment here and let me know.
The code for some snow:
/** * Snow in the Third Dimension (with some math from Kirupa.com) * @author Devon O. * @version 0.1 */ package { import flash.display.BitmapData; import flash.display.GradientType; import flash.display.Sprite; import flash.filters.BlurFilter; import flash.geom.Matrix; import flash.geom.Point; import org.papervision3d.cameras.FreeCamera3D; import org.papervision3d.materials.special.BitmapParticleMaterial; import org.papervision3d.view.BasicView; import flash.events.Event; import org.papervision3d.objects.DisplayObject3D; import org.papervision3d.core.geom.Particles; import org.papervision3d.core.geom.renderables.Particle; public class Snow3d extends BasicView { private var snowHolder:DisplayObject3D; private var flakes:Array; private const NUM_FLAKES:int = 300; private var cam:FreeCamera3D; private var noBlurFlake:BitmapData; private var ltBlurFlake:BitmapData; private var medBlurFlake:BitmapData; private var heavyBlurFlake:BitmapData; public function Snow3d() { super(stage.stageWidth, stage.stageHeight, true, false, FreeCamera3D.TYPE); init(); } private function init():void { cam = cameraAsFreeCamera3D; cam.zoom = 1; cam.focus = 400; cam.z = 10; createBitmaps(); snowHolder = new DisplayObject3D("snow_holder"); flakes = new Array(); for(var i:int = 0; i < NUM_FLAKES; i++) { var mat:BitmapParticleMaterial = new BitmapParticleMaterial(noBlurFlake); mat.smooth = true; var flakeHolder:Particles = new Particles("flake" + i); var flake:Particle = new Particle(mat, 10, 0, 0, 0); flakeHolder.addParticle(flake); flakeHolder.x = randRange(-2000, 2000); flakeHolder.y = randRange(-2000, 2000); flakeHolder.z = randRange( -2000, 2000); flakeHolder.extra = { radians: 0, I:randRange(2, 8), K: (-Math.PI + Math.random() * Math.PI), noblur: new BitmapParticleMaterial(noBlurFlake), blur1: new BitmapParticleMaterial(ltBlurFlake), blur2: new BitmapParticleMaterial(medBlurFlake), blur3: new BitmapParticleMaterial(heavyBlurFlake) }; snowHolder.addChild(flakeHolder); flakes.push(flakeHolder); } scene.addChild(snowHolder); singleRender(); addEventListener(Event.ENTER_FRAME, enterFrame); } private function createBitmaps():void { var lightBlur:BlurFilter = new BlurFilter(4, 4, 1); var medBlur:BlurFilter = new BlurFilter (8, 8, 1); var heavyBlur:BlurFilter = new BlurFilter(16, 16, 1); var type:String = GradientType.RADIAL; var colors:Array = [0xFFFFFF, 0xFFFFFF, 0xFFFFFF]; var alphas:Array = [1, .1, 0]; var ratios:Array = [0, 200, 255]; var mat:Matrix = new Matrix(); mat.createGradientBox(10, 10); var baseFlake:Sprite = new Sprite(); baseFlake.graphics.beginGradientFill(type, colors, alphas, ratios, mat); baseFlake.graphics.drawCircle(5, 5, 5); baseFlake.graphics.endFill(); noBlurFlake = new BitmapData(10, 10, true, 0x00000000); noBlurFlake.draw(baseFlake); ltBlurFlake = new BitmapData(10, 10, true, 0x00000000); ltBlurFlake.draw(baseFlake); ltBlurFlake.applyFilter(ltBlurFlake, baseFlake.getBounds(baseFlake), new Point(), lightBlur); medBlurFlake = new BitmapData(10, 10, true, 0x00000000); medBlurFlake.draw(baseFlake); medBlurFlake.applyFilter(medBlurFlake, baseFlake.getBounds(baseFlake), new Point(), medBlur); heavyBlurFlake = new BitmapData(10, 10, true, 0x00000000); heavyBlurFlake.draw(baseFlake); heavyBlurFlake.applyFilter(heavyBlurFlake, baseFlake.getBounds(baseFlake), new Point(), heavyBlur); } function enterFrame(e:Event):void { var pan:Number = cam.rotationY - 210 * stage.mouseX / (stage.stageWidth/2); pan = Math.max( -100, Math.min( pan, 100 ) ); cam.rotationY -= pan / 12; var len:int = flakes.length; for(var i:int = 0; i < len; i++) { var p:Particles = flakes[i]; if (Math.abs(p.sceneZ) <= 100) { p.particles[0].material = p.extra["noblur"] } if (Math.abs(p.sceneZ) >= 101 && Math.abs(p.z) <= 200) { p.particles[0].material = p.extra["blur1"] } if (Math.abs(p.sceneZ) >= 201 && Math.abs(p.z) <= 300) { p.particles[0].material = p.extra["blur2"] } if (Math.abs(p.sceneZ) >= 301) { p.particles[0].material = p.extra["blur3"] } p.extra["radians"] += (p.extra["K"] / 180) * Math.PI; p.x -= 3 * (Math.cos(p.extra["radians"])); p.z -= 3 * (Math.sin(p.extra["radians"])); p.y -= p.extra["I"]; if (p.y < -2000) { p.y = 2000; p.x = randRange(-2000, 2000); p.z = randRange( -2000, 2000); p.extra["I"] = randRange(2, 8); } } singleRender(); } private function randRange(min:Number, max:Number):Number { return Math.floor(Math.random() * (max - min + 1)) + min; } } }
And an example (with panorama):
[kml_flashembed movie=”../wp-content/uploads/2008/02/snow3d.swf” height=”400″ width=”500″ fversion=”9″ /]