I just finished uploading a new branch to the Papervision repository. If you hadn't noticed the slew of new files, take a look in /branches/Effects. Its an API built into Papervision3D that I have been developing which allows developers to apply filters and other effects to their 3D objects. I did a class on these techniques recently in NYC with John. It seemed to go well and teach the main points of the API, so here is an online version of what was taught.
This demo involves a Borg cube collada - the finished source of which you can get here or at the end of this tutorial:
You will notice that there is absolutely nothing special about this cube to begin with. It is simply a cube with a borg texture map applied to it. So, to start with, we will apply generic flash filters to the cube. To apply filters to the object, we need to create a new EffectLayer. EffectLayers are simply glorified Sprites giving you the ability to add/remove filters to any objects drawn inside them. Once we have created our EffectLayer - we add our filters to it and tell our objects which layer we want them to use.
Creating a regular EffectLayer
To start with, we must first create an effect layer and add it to our viewport:
var fx:EffectLayer = new EffectLayer();
Once you have created the layer and added it to the viewport, you need to add the effects you wish to apply to it. Regular effect layers are limited to having regular flash filters applied to the them. You can apply these filters by passing them into the LayerEffect class. You can add or remove each LayerEffect from the effect layer at anytime - making filter management seamless. To add some effects to your effect layer:
fx.addEffect(new LayerEffect(new BlurFilter(12, 12, 2)));
fx.addEffect(new LayerEffect(new GlowFilter()));
Once we have an effect layer with multiple effects passed to it, it still won't do anything to our scene. We have to tell our objects to use the effect layer to render with. This brings about a new property on both DisplayObject3D and MaterialObject3D - renderLayer. renderLayer tells the object what effect layer to draw too. For this example, we simply need to get the material of the Borg cube and set its renderLayer to the new EffectLayer we have created:
dae.getMaterialByName("shipMaterial").renderLayer = fx;
NOW you can see that the effects attached to the effectlayer are being applied to the object.
Although addding regular filters to our objects is fun - it doesn't really give you the ability to create *Effects*. To create an effect you need access to the pixels within and around the object. By having access to these pixels, you can apply filters, transforms, displacements - anything you want really - to your objects. Fortunately Flash 8+ has the BitmapData class - which gives us direct access to pixel data. To give Papervision developers this access, I created the BitmapEffectLayer. It is similar in concept of the EffectLayer - but now, all objects which use the layer are drawn to a BitmapData object - and all filters are applied to those pixels.
Creating a BitmapEffectLayer
You add a BitmapEffectLayer to your viewport the same you would a regular RenderLayer. However, BitmapEffectLayers do take a few more parameters in their constructor:
- width:int - width of the BitmapData to draw too
- height:int - height of the BitmapData to draw too
- transparent:Boolean - transparency flag of the BitmapData. Default is true.
- fillColor:int - ARGB color to fill BitmapData with initially. Default is 0 - fully transparent/black.
- clearMode:String - BitmapClearMode variable which specifies when to clear the drawLayer. The drawLayer holds all rendered content, and is what is drawn to the BitmapData canvas. Default is clear_pre - meaning you can see the drawLayer.
- renderAbove:Boolean - Specifies if the drawLayer should above or below the canvas. Default is true.
- clearBeforeRender:Boolean - Specifies if the bitmapData should be cleared before each renderloop (reseting any effects applied previously). Default is false.
For simplicity's sake, all you need to really specify for this example is the width and height:
var bfx:BitmapEffectLayer = new BitmapEffectLayer(stage.stageWidth, stage.stageHeight);
This creates a new BitmapEffectLayer with a BitmapData canvas the size of our stage. For performance reasons these are not always the best values to use, but in this case it suffices.
Once we have a BitmapEffectLayer, we need to add effects to it just like a regular EffectLayer. For BitmapEffectLayers, however, we use the BitmapLayerEffect instead of the LayerEffect class to pass filters into. For this demo, I simply add a BlurFilter to the layer:
bfx.addEffect(new BitmapLayerEffect(new BlurFilter(2, 2, 4)));
If you run the demo now, you will notice that there is a very slight blurry look behind our object.
The reason you can't see the effect at all is because our drawLayer is directly over the BitmapData canvas. Remember, the drawLayer is the layer in which all our Papervision content is rendered - and it is, by default, above the canvas. So, we either need to make the effect more pronounced or alter the drawLayer in some way. I like seeing effects, so I want to make the effect more pronounced. We can do this by changing how the drawLayer is drawn to the canvas. BitmapEffectLayers have a property called drawCommand. By assigning drawCommand a BitmapDrawCommand Object, we can alter how the drawLayer is drawn to our canvas.
BitmapDrawCommand takes a few parameters as well:
- transMat:Matrix - a transform matrix to apply to our draw operation. Default is null.
- colorTransform:ColorTransform - a ColorTransform object which specifies how the color of our drawLayer will be altered when drawn to the canvas. Default is null.
- blendMode:BlendMode - the BlendMode applied to the drawLayer pixels as they are drawn onto the canvas. Default is null.
- smooth:Boolean - whether or not to smooth the pixels in the draw operation. Default is null.
For this effect I decided to use BlendMode.ADD - giving a slowly brightining effect to the canvas.
bfx.drawCommand = new BitmapDrawCommand(null, new ColorTransform(0.1, 1, 0.1, 0.25),BlendMode.ADD);
You can see in the ColorTransform that 10% of the red , 100% of the Green, 10% of the Blue, and 25% of the Alpha is being drawn to the canvas. I do this to make sure our effect doesn't get too bright too fast.
To finish off the effect, I decide to pull the effect off the canvas and blend the drawLayer into the canvas. To "pull" the effect off the canvas, we can use the clippingPoint parameter of the BitmapEffectLayer. clippingPoint is a Point object which specifies the top left of the canvas in which to apply the effect on. By offsetting the clippingPoint, you can actually push or pull your effect across the canvas.
bfx.clippingPoint = new Point(0, -4);
bfx.drawLayer.blendMode = BlendMode.OVERLAY;
We now have a Borg cube spewing some sort of... vapor trail? Maybe not the most practical example, but it does show off a good number of the tools available in the new API. And now, you can create post-render effects directly onto/into your objects:
This was just some of the basics of the effect layers API that you can use in your projects. It is a pretty complex process, and I'm probably not the best writer - so if you have any questions just let me know (either in comments or on the list)!
Get the source for this tutorial here.