Archive for May, 2008
FP10: Regarding The Pixel Bender / Flash Limitations
If you have done any serious development with Pixel Bender, you have no doubt come across the many limitations that currently exist when writing a kernel for use in Flash. If you have not, a quick skim through the spec will give you the basic idea. Though frustrating, there is a very good reason for these limitations.
First and foremost, you need to understand that Pixel Bender is designed to be used with other Adobe products (such as Photoshop and After Effects), not just Flash. In the case of those applications, the full feature set is available as long as you have compatible hardware.
Flash Player is a unique case because it needs to be compatible with a very wide range of hardware, unlike a professional application which can be a little more picky. With that in mind, Adobe has targeted Shader Model 2.0 for Flash Player 10. Shader Model 2.0 was first released with DirectX 9.0 back in December of 2002. In order to support the full feature set, Shader Model 3.0 would need to be targeted instead. Shader Model 3.0 (DirectX 9.0c I believe) was released 20 months after 2.0, so it is safe to assume that we probably won’t have access to the 3.0 features until Flash Player 11.
A handful of issues also exist with the toolkit itself. For instance, as I noted in my previous post about generic number crunching, you cannot use an output type of pixel1 or pixel2 despite the fact that the spec claims otherwise. I have since learned that this limitation currently exists in the toolkit (not FP10) because the GPUs fail to handle them. At some point before the final release, this should be fixed; so for now you just need to work around it.
Limitations aside, there are still plenty of really cool things that you can accomplish with Pixel Bender in Flash. I will be posting some real world examples of practical filters in the weeks to come.
1 commentFP10: Generic Number Crunching Via Pixel Bender And ShaderJob
Though the use of Pixel Bender kernels is mainly designed for carrying out graphics-related tasks, it has another powerful use - generic number crunching. Since Pixel Bender kernels and ActionScript run on separate threads, you can use this to your advantage to a.) avoid sluggish UI performance and b.) increase the speed in which complex calculations are made. For instance, let's say that you have a large collection of numbers and you need to loop through the collection and perform a series of mathematical tasks to each one (for a 3D or audio engine perhaps). Passing the collection to a Pixel Bender kernel for processing can result in some pretty massive performance gains, even over heavily optimized ActionScript code.
Below is a very simple example of a Pixel Bender kernel which is designed to accept a collection of numbers and return a new collection that contains the corresponding square root of each number.
-
<languageVersion : 1.0;>
-
-
kernel NumberCruncher
-
<
-
namespace : "AIF";
-
vendor : "Ryan Taylor";
-
version : 1;
-
description : "Basic example of a generic number cruncher.";
-
>
-
{
-
input image1 src;
-
output pixel3 result;
-
-
void evaluatePixel()
-
{
-
pixel1 value = pixel1(sqrt(sample(src, outCoord())));
-
result = pixel3(value, 0.0, 0.0);
-
}
-
}
First of all, note that the result is being passed back as a three-element vector of pixel values. The current release of the Pixel Bender Toolkit throws an error and refuses to export byte code when the output has less than three channels. The Pixel Bender spec claims that it supports output types of pixel1, pixel2, pixel3, and pixel4, so I am assuming this is a bug. To temporarily get around this, I am simply using pixel3 and passing back values of 0 for the other two elements. Also note that the input type needs to be image1.
On the ActionScript side of things, the workflow is as follows:
-
1.) Create a 'ByteArray' and use the 'writeFloat' method to add each number that you would like to pass through the Pixel Bender kernel for processing. Make sure that the endian setting is set to 'little endian'. Documentation also states that a 'Vector.<Number>' collection can be used as the shader input, however it appears that there is currently a bug with this functionality (it throws an error saying that the parameter is invalid).
2.) Create a 'Shader' instance and pass it the Pixel Bender byte code. The width of the input needs to be set to the length of the collection that you are passing. In the case of a ByteArray, you need to divide the length by four since each float increases the length by four instead of one. The height of the input should be set to one. Last, but not least, the input's input needs to be set to the collection.
3.) Create a 'ByteArray' for storing the shader's resulting output. Again, make sure that the endian setting is set to 'little endian'.
4.) Create a 'ShaderJob' instance and pass it the shader, input 'ByteArray', and the width and height values that you set for the 'Shader'. Add an event listener that listens for the 'complete' event so that you can access the output 'ByteArray' once the shader has finished processing. Lastly, you must call the 'start' method to execute the shader.
Here is a basic example of this workflow in action:
-
package
-
{
-
import flash.display.Shader;
-
import flash.display.ShaderJob;
-
import flash.display.Sprite;
-
import flash.events.Event;
-
import flash.utils.ByteArray;
-
import flash.utils.Endian;
-
-
public class Main extends Sprite
-
{
-
protected var _shader:Shader;
-
-
protected var _shaderJob:ShaderJob;
-
-
protected var _input:ByteArray;
-
-
protected var _output:ByteArray;
-
-
[Embed(source="/../assets/filters/NumberCruncher.pbj", mimeType="application/octet-stream")]
-
protected var NumberCruncher:Class;
-
-
public function Main()
-
{
-
init();
-
}
-
-
protected function init():void
-
{
-
_input = new ByteArray();
-
_input.endian = Endian.LITTLE_ENDIAN;
-
_input.writeFloat(4);
-
_input.writeFloat(16);
-
_input.writeFloat(100);
-
_input.writeFloat(400);
-
_input.position = 0;
-
-
var width:int = _input.length >> 2;
-
var height:int = 1;
-
-
_shader = new Shader(new NumberCruncher());
-
_shader.data.src.width = width;
-
_shader.data.src.height = height;
-
_shader.data.src.input = _input;
-
-
_output = new ByteArray();
-
_output.endian = Endian.LITTLE_ENDIAN;
-
-
_shaderJob = new ShaderJob(_shader, _output, width, height);
-
_shaderJob.addEventListener(Event.COMPLETE, onShaderJobComplete, false, 0, true);
-
_shaderJob.start();
-
}
-
-
protected function onShaderJobComplete(event:Event):void
-
{
-
_output.position = 0;
-
-
var length:int = _output.length;
-
-
for(var i:int = 0; i < length; i += 4)
-
{
-
var output:Number = _output.readFloat();
-
-
if(i % 3 == 0)
-
trace("value -> " + output);
-
}
-
}
-
}
-
}
In order to work around that three channel output bug that I mentioned earlier, I am filtering out the values that are irrelevant using a modulus operation in the 'onShaderJobComplete' event handler. The resulting values should each be the square root of the original value that was written to the input 'ByteArray'.
So that is pretty much it. As you can imagine, once the little bugs are worked out, this is going to be extremely useful stuff.
16 commentsFP10: The ‘drawTriangles’ Method
One of the major additions to the 'Graphics' API in Flash Player 10 is the 'drawTriangles' method. The syntax is as follows:
-
drawTriangles(vertices:Vector, indices:Vector = null, uvtData:Vector = null, culling:String = "none"):*
Before exploring each parameter, you need to understand why this method is such a huge addition to the Flash 'Graphics' API. Since the early days of Flash, developers have always toyed with implementations of 3D frameworks. It wasn't until the birth of AVM2 and AS3 that 3D really took off in Flash, thanks to the huge leap in performance. We have since seen the rise of a handful of many well-known and successful open-source Flash libraries such as Papervision3D, Sandy, Away3D, and Alternativa 3D. A lot of award-winning sites have resulted from these libraries and it has truly grabbed the attention of Adobe and become a major focus of Flash Player 10. The 'drawTriangles' method is one of the many additions that the Flash Player team has made to help increase the quality and performance of these open-source libraries. Having said all of that, let's begin examining the 'drawTriangles' method and its parameters.
The first parameter, verticies, is a 'Vector' containing numbers that correspond to points of triangles. If you are new to the concept of the type 'Vector' in Flash Player 10, then you need to understand that 'Vector' is simply a typed array, not a geometric vector. The 'verticies' Vector can contain an infinite amount of triangle points and should always be a factor of six (three sets of x,y points). The even indices will always contain the x values and the odd indices will always contain the y values. For instance, a single triangle could be defined as follows:
-
var point1:Point = new Point(0, 0);
-
var point2:Point = new Point(256, 0);
-
var point3:Point = new Point(0, 256);
-
-
var verticies:Vector.<Number> = Vector.<Number>([point1.x, point1.y, point2.x, point2.y, point3.x, point3.y]);
Simple enough. Now suppose that you have two triangles and they share some common verticies:
-
var triangle1Point1:Point = new Point(0, 0);
-
var triangle1Point2:Point = new Point(256, 0);
-
var triangle1Point3:Point = new Point(0, 256);
-
-
var triangle2Point1:Point = new Point(256, 0);
-
var triangle2Point2:Point = new Point(0, 256);
-
var triangle2Point3:Point = new Point(256, 256);
-
-
var verticies:Vector.<Number> = Vector.<Number>([triangle1Point1.x, triangle1Point1.y, triangle1Point2.x, triangle1Point2.y, triangle1Point3.x, triangle1Point3.y, triangle2Point1.x, triangle2Point1.y, triangle2Point2.x, triangle2Point2.y, triangle2Point3.x, triangle2Point3.y]);
This correctly describes two triangles, however it is inefficient since is using six verticies when only four are necessary. The second parameter, incidies, allows you to specify sets of indicies in the 'verticies' Vector which make up triangles. When the 'indicies' parameter passed as a Vector rather than null, the assumption that every six indicies in the 'verticies' Vector represents a triangle is no longer made. Instead, the 'verticies' Vector is treated as a random collection of verticies and the pairs of numbers in the 'indices' Vector are treated as pairs of three which specify which verticies represent a triangle. As an example of this, the same two triangles that were described in the example above will now be described using only four points by taking advantage of the 'indicies' parameter. Note that the indicies are zero-based, so if you are having problems picturing this, mentally add one to each index to correctly relate it to the corresponding point number.
-
var point1:Point = new Point(0, 0);
-
var point2:Point = new Point(256, 0);
-
var point3:Point = new Point(0, 256);
-
var point4:Point = new Point(256, 256);
-
-
var verticies:Vector.<Number> = Vector.<Number>([point1.x, point1.y, point2.x, point2.y, point3.x, point3.y, point4.x, point4.y]);
-
var indices:Vector.<int> = Vector.<int>([0, 1, 2, 1, 3, 2]);
Before moving on, if you are interested in actually seeing this, the example below shows the entire process for drawing the triangles that we have described thus far.
-
var point1:Point = new Point(0, 0);
-
var point2:Point = new Point(256, 0);
-
var point3:Point = new Point(0, 256);
-
var point4:Point = new Point(256, 256);
-
-
var verticies:Vector.<Number> = Vector.<Number>([point1.x, point1.y, point2.x, point2.y, point3.x, point3.y, point4.x, point4.y]);
-
var indices:Vector.<int> = Vector.<int>([0, 1, 2, 1, 3, 2]);
-
-
var scene:Sprite = new Sprite();
-
-
scene.graphics.beginFill(0x990000, 1);
-
scene.graphics.drawTriangles(verticies, indices);
-
scene.graphics.endFill();
-
-
addChild(scene);
This is great and all, but what if you want your triangles to contain bitmaps rather than just color fills? No problem - the next parameter, uvtData, is for handling UV mapping of a bitmap texture to a triangle. If you are unfamiliar with the process of UV mapping in a 3D application such as Maya, basically the top left corner of the bitmap is represented as (0, 0) and the bottom right corner is represented as (1, 1). Using this normalized coordinate system, you can specify a UV coordinate that corresponds to each vertex of a triangle. Moving forward with the example that I have been building upon, let's say that you want to map a given image to the two triangles. The triangles form a rectangle, however by default a bitmap texture is going to fill each triangle rather than properly being distributed between the two to correctly form the full rectangular image. UV mapping can be is used to correct this as seen in the example below.
-
var point1:Point = new Point(0, 0);
-
var point2:Point = new Point(256, 0);
-
var point3:Point = new Point(0, 256);
-
var point4:Point = new Point(256, 256);
-
-
var uvPoint1:Point = new Point(0, 0);
-
var uvPoint2:Point = new Point(1, 0);
-
var uvPoint3:Point = new Point(0, 1);
-
var uvPoint4:Point = new Point(1, 1);
-
-
var verticies:Vector.<Number> = Vector.<Number>([point1.x, point1.y, point2.x, point2.y, point3.x, point3.y, point4.x, point4.y]);
-
var indices:Vector.<int> = Vector.<int>([0, 1, 2, 1, 3, 2]);
-
var uvtData:Vector.<Number> = Vector.<Number>([uvPoint1.x, uvPoint1.y, uvPoint2.x, uvPoint2.y, uvPoint3.x, uvPoint3.y, uvPoint4.x, uvPoint4.y]);
-
-
var scene:Sprite = new Sprite();
-
var texture:Bitmap = new MyImage() as Bitmap;
-
-
scene.graphics.beginBitmapFill(texture.bitmapData);
-
scene.graphics.drawTriangles(verticies, indices, uvtData);
-
scene.graphics.endFill();
-
-
addChild(scene);
Up until now, I have only been passing pairs of U and V values in the 'uvtData' Vector. The optional T value is used for calculating the distance from the eye to the texture in z space so that the renderer can correctly apply an accurate perspective of the texture in 3D. T is calculated as focalLength / (focalLength + z), where focalLength is the distance from the eye to the screen and z is th distance from the screen to the texture. The focal length should be defined as a constant value for use in all of your projections. A lower focal length will result in a more dramatically stretched perspective, so you should experiment with the value and settle on one that you feel works best for the cinematographic look that you are after. In the example that follows, I used a value of 400 and found satisfactory results. In addition to including each T value with the 'uvtData' Vector, you will need to apply each T scalar to the corresponding vertex. Here is a demonstration of the rotation of the verticies and the application of the T scalars:
-
var vertex1:Vector3D = new Vector3D(-128, -128, 0);
-
var vertex2:Vector3D = new Vector3D(128, -128, 0);
-
var vertex3:Vector3D = new Vector3D(-128, 128, 0);
-
var vertex4:Vector3D = new Vector3D(128, 128, 0);
-
-
var uvPoint1:Point = new Point(0, 0);
-
var uvPoint2:Point = new Point(1, 0);
-
var uvPoint3:Point = new Point(0, 1);
-
var uvPoint4:Point = new Point(1, 1);
-
-
var angle:Number = 0.9;
-
var cos:Number = Math.cos(angle) * 128;
-
var sin:Number = Math.sin(angle) * 128;
-
-
vertex1.x = -cos;
-
vertex2.x = cos;
-
vertex3.x = -cos;
-
vertex4.x = cos;
-
-
vertex1.z = -sin;
-
vertex2.z = sin;
-
vertex3.z = -sin;
-
vertex4.z = sin;
-
-
var focalLength:Number = 400;
-
-
var t1:Number = focalLength / (focalLength + vertex1.z);
-
var t2:Number = focalLength / (focalLength + vertex2.z);
-
var t3:Number = focalLength / (focalLength + vertex3.z);
-
var t4:Number = focalLength / (focalLength + vertex4.z);
-
-
vertex1.scaleBy(t1);
-
vertex2.scaleBy(t2);
-
vertex3.scaleBy(t3);
-
vertex4.scaleBy(t4);
-
-
var verticies:Vector.<Number> = Vector.<Number>([vertex1.x, vertex1.y, vertex2.x, vertex2.y, vertex3.x, vertex3.y, vertex4.x, vertex4.y]);
-
var indices:Vector.<int> = Vector.<int>([0, 1, 2, 1, 3, 2]);
-
var uvtData:Vector.<Number> = Vector.<Number>([uvPoint1.x, uvPoint1.y, t1, uvPoint2.x, uvPoint2.y, t2, uvPoint3.x, uvPoint3.y, t3, uvPoint4.x, uvPoint4.y, t4]);
-
-
var scene:Sprite = new Sprite();
-
-
var plane:Sprite = new Sprite();
-
plane.x = 250;
-
plane.y = 200;
-
-
var texture:Bitmap = new MyImage() as Bitmap;
-
-
plane.graphics.beginBitmapFill(texture.bitmapData);
-
plane.graphics.drawTriangles(verticies, indices, uvtData);
-
plane.graphics.endFill();
-
-
scene.addChild(plane);
-
-
addChild(scene);
Here is a screen shot of the result:

Before moving on, I also want to note that you can use a 'Shader' object for representing a Pixel Bender filter and then apply the shader to your triangles as a fill. The example below demonstrates this practice. Note that you cannot use UVT mapping with shaders; UVT support is strictly used for improving texture quality when a bitmap fill is used.
-
_shader = new Shader(new ColorFilter());
-
_shader.data.color.value[0] = Math.random();
-
_shader.data.color.value[1] = Math.random();
-
_shader.data.color.value[2] = Math.random();
-
-
plane.graphics.beginShaderFill(_shader);
-
plane.graphics.drawTriangles(verticies, indices);
-
plane.graphics.endFill();
The forth and final parameter of the 'drawTriangles' method is for culling. Culling is the process of determining what triangles are visible to a camera (view point) in a 3D scene. To summarize the process, first you need to calculate the vector normal of a given triangle. This is done by finding the cross product of two of the sides. Once you have the vector normal of the triangle, you need to find the vector of the camera when pointed towards the triangle. This is simply a matter of picking a point on the triangle, then subtracting the point of the camera from that point. Once you have these two vectors, the dot product will reveal the angle between them. Examining the sign of this angle is how you determine whether or not the triangle should be visible. Fortunately, culling support is built into Flash Player 10, so if you hate math - you are off the hook. The three options that exist for the culling parameter are defined as constants of the 'flash.display.TriangleCulling' class. They are as follows:
- TriangleCulling.NONE - This is the default value. When this setting is used, no culling is performed. Though this is often not desired, there are some situations in which this is necessary to correctly display an object. For instance, say you had a sphere and it's material has some transparency - meaning you can see through the triangles to some extent. In this case, you do not want to perform any culling on the object. Culling would prevent some of the triangles from being rendered and, in this case, the desired result would be to see all of the triangles.
TriangleCulling.POSITIVE - This is the more common culling method and is known as backface culling. When this approach is used, only the triangles that are facing towards the view point will be rendered. Any triangles that are pointing away from the view point will not be rendered. In the case of an object with a solid material applied, this works perfectly in rendering only what is visible to the camera.
TriangleCulling.NEGATIVE - The less common culling method, but still useful for some situations. In this case, triangles that are facing away from the view point will be rendered, while triangles that are facing the view point will not. A good example of when you would want to perform this type of culling is for an object being used for an environment map. Let's say that you have a giant cube and the camera is placed inside of it. With the right material mapped correctly to the cube, the illusion that you are in an environment will result. In order to render the internal triangles, but not the ones outside of the cube, this culling method should be used.
To see culling in action, modify the previous example by passing the 'positive' culling value as the forth parameter.
-
plane.graphics.drawTriangles(verticies, indices, uvtData, TriangleCulling.POSITIVE);
Experimenting with various angles of rotation will result in the culling of the triangles when appropriate. Note that the order of the indicies in the 'indicies' Vector directly affects the direction of the triangle's vector normal. If you ever experience undesired culling results for some triangles but not others, changing the order of the indicies will likely resolve the issues.
That concludes my foundational exploration of Flash Player 10's new 'drawTriangles' method. In the future, I will be posting some more advanced examples of how 3D frameworks can evolve from all of this.
8 commentsFP10: A Note About Pixel Bender And Procedural Shaders
There is something that I wanted to note about my previous post in case it caused any confusion. I demonstrated the creation and testing of a very basic procedural shader in the Pixel Bender Toolkit using a technique where you temporarily supply an input image for the sake of defining a testable region. This technique is a workaround for dealing with a current limitation in which the only function that is supported in Flash is the 'evaluatePixel' function. The way that this should actually be handled is by using the 'generated' function. This function is used to define the region of output even when all inputs are empty. The 'everywhere' method that is being used to generate the return value simply specifies output over the entire infinite plane. Below is an example of the 'ColorFilter' that I previously demonstrated, but using this proper approach that isn't currently supported in Flash.
-
<languageVersion : 1.0;>
-
-
kernel ColorFilter
-
<
-
namespace : "AIF";
-
vendor : "Ryan Taylor";
-
version : 1;
-
description : "Simple color filter example.";
-
>
-
{
-
output float4 result;
-
-
parameter float4 color
-
<
-
minValue:float4(0, 0, 0, 0);
-
maxValue:float4(1, 1, 1, 1);
-
defaultValue:float4(0, 0, 0, 1);
-
>;
-
-
void evaluatePixel()
-
{
-
result = color;
-
}
-
-
region generated()
-
{
-
return everywhere();
-
}
-
}
Upon running this script in the toolkit, you should be able to set the color using the slider that is generated from the parameter. Everything should be rendered correctly without the need to specify an input image. I suppose a better alternative to the temporary input image might be to use this function, but comment it out before exporting the byte code. Just food for thought.
No commentsFP10: Setup, Pixel Bender, and Shaders
The beta of Flash Player 10 has recently arrived and if you haven't been experimenting with it, you are definitely missing out. In order to start publishing for FP10, you will need to start by downloading a recent Flex 3 SDK build (May 15th, 2008 or later). Once you have downloaded a recent nightly build of the SDK, go ahead and extract it and copy the folder to the Flex Builder 'sdks' directory. Now you are ready to get setup in Flex Builder; here is the way that I have had success doing so:
1.) Create a new ActionScript project.
2.) In the area that allows you to select the SDK version that you would like to use, click the 'Configure Flex SDKs...' link.
3.) Add a listing for the nightly build that you recently copied into the Flex Builder 'sdks' directory.
4.) Back at the previous window, go ahead and select that SDK as the one that you would like to target for your project.
5.) In the build path window, toggle over to the 'Library path' tab and remove the 'playerglobal.swc' listing under the SDK if one exists.
6.) Click the 'Add SWC' button and browse to the nightly build SDK directory. From there, browse to 'frameworks/libs/player/10/' and select the 'playerglobal.swc' file.
7.) Once added, click the arrow next to 'playerglobal.swc' so that you can see it's settings. Double-click 'Link Type' and set it to 'External' if it isn't already.
8.) Finish setting up your new project.
9.) Once the project is setup and ready to go, navigate to the 'Project > Properties > ActionScript Compiler' window and make sure that you are targeting Flash Player version '10.0.0'.
10.) Last, but not least, make sure that your launcher settings are set to launch your SWF file directly rather than the HTML wrapper.
If you have a hex editor handy, you can confirm that your SWF file is being compiled for FP10 by examining the fourth byte (it should be '0A'). The big problem that currently exists at the moment is that code completion works for new classes (such as 'Shader' for instance), but not new properties of existing classes from FP9 (such as the 'z' property of 'Sprite'). What is strange is that I was able to create a hybrid SDK using some of the lib files from an early release of ***** *** and have full code completion and everything. With the latest builds, this trick no longer works. Interestingly enough, Matt Chotin notes that their recent changes may prevent full code completion in Flex Builder 3 for FP10 from ever working from here forward, but will undoubtedly be resolved in Flex Builder 4. Hopefully they will either further revise the SDK or release a patch for Flex Builder 3 that resolves this issue. That aside, you can still develop for FP10 using the downloadable documentation that Adobe has made public.
Assuming that you are setup and ready to go at this point, one of the first things that you might be interested in trying out is the use of a Pixel Bender file in ActionScript. Make sure that you have the latest version of the Pixel Bender Toolkit from Adobe Labs since a lot has changed since the early AIF Toolkit release. Go ahead and launch the toolkit. Before getting started, there are a couple of very nice documents that I highly recommend you read under the help menu (the spec and tutorial). If you are new to programming pixel shaders, those documents will help you wrap your head around the concepts and get you started with the new language.
If you have been experimenting with Pixel Bender since the early days when it was known as Hydra, one of the first things that you will need to note is the now mandatory meta data after the kernel defintion as seen in my simple color filter example below.
-
<languageVersion : 1.0;>
-
kernel ColorFilter
-
<
-
namespace : "AIF";
-
vendor : "Ryan Taylor";
-
version : 1;
-
description : "Simple color filter example.";
-
>
-
{
-
// Uncomment this for testing in the toolkit using an image file.
-
//input image4 image;
-
-
output float4 result;
-
-
parameter float4 color
-
<
-
minValue:float4(0, 0, 0, 0);
-
maxValue:float4(1, 1, 1, 1);
-
defaultValue:float4(0, 0, 0, 1);
-
>;
-
-
void evaluatePixel()
-
{
-
result = color;
-
}
-
}
What this shader does is simply set all output pixels to a given R, G, B, and A value based on a float of 0.0 to 1.0 for channel (later interpolated to 0 to 255). If you were to run and test this script in the toolkit, you would want to uncomment that input line that I have commented out and load an image in. Otherwise, no pixel region will be defined and you won't be able to see your changes to the sliders in the right panel. By the way, if you are new to this stuff, notice how you define a parameter with a minimum, maximum, and default value, then the corresponding slider is automatically created for you when you run script so that you can test changes to the value in real time. Pretty cool stuff.
Go ahead and save the script as a 'pbk' file to a sample project; something like './src/filters/ColorFilter.pbk' will do nicely. Now, to get the filter out of the toolkit and into Flash, you need to export the byte code. To do this, first make sure that 'Turn on Flash Warnings and Errors' is enabled under the 'Build' menu. Next, under the 'File' menu select 'Export Pixel Bender Byte Code Filter for Flash' and save the file as something like './assets/filters/ColorFilter.pbj'. You are now ready to use the filter in ActionScript.
You can load or embed the file to get it into ActionScript. Note that the 'mimeType' property is mandatory when embedding as seen below.
-
[Embed(source="/../assets/filters/ColorFilter.pbj", mimeType="application/octet-stream")]
-
protected var ColorFilter:Class;
Since my example is designed to be used as a procedural shader, use of the 'Shader' class and drawing API to create a fill will work best. The workflow is as follows:
-
// Create a new shader for representing the embedded filter.
-
-
var shader:Shader = new Shader(new ColorFilter());
-
-
// The parameters that were defined in the Pixel Bender
-
// script can be dynamically accessed by name and index.
-
// For instance, to change the R channel to 100% (255) and
-
// G and B to 0% (0), you would do the following:
-
-
shader.data.color.value[0] = 1;
-
shader.data.color.value[1] = 0;
-
shader.data.color.value[2] = 0;
-
-
// The alpha channel is configured to be 1.0 by default in
-
// the script, however I am showing it here for the sake
-
// of example:
-
-
shader.data.color.value[3] = 1;
-
-
// Now create a display object for drawing the shader
-
// as a fill.
-
-
var texture:Sprite = new Sprite();
-
texture.graphics.beginShaderFill(shader);
-
texture.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
-
texture.graphics.endFill();
-
-
// Lastly, add the display object to the display list.
-
-
addChild(texture);
And here is a basic demonstration of the shader being animated. This example simply cycles through the color spectrum:
-
package
-
{
-
import flash.display.Bitmap;
-
import flash.display.BitmapData;
-
import flash.display.Shader;
-
import flash.display.Sprite;
-
import flash.events.Event;
-
-
public class Main extends Sprite
-
{
-
private const DELTA_OFFSET:Number = Math.PI * 0.5;
-
-
protected var _bitmapData:BitmapData;
-
-
protected var _shader:Shader;
-
-
protected var _texture:Sprite;
-
-
protected var _delta:Number = 0;
-
-
[Embed(source="/../assets/filters/ColorFilter.pbj", mimeType="application/octet-stream")]
-
protected var ColorFilter:Class;
-
-
public function Main()
-
{
-
init();
-
}
-
-
protected function init():void
-
{
-
_shader = new Shader(new ColorFilter());
-
_texture = new Sprite();
-
-
addChild(_texture);
-
-
addEventListener(Event.ENTER_FRAME, onEnterFrame);
-
}
-
-
protected function renderShader():void
-
{
-
_texture:graphics.clear();
-
_texture.graphics.beginShaderFill(_shader);
-
_texture.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
-
_texture.graphics.endFill();
-
}
-
-
protected function onEnterFrame(event:Event):void
-
{
-
_shader.data.color.value[0] = 0.5 + Math.cos(_delta - DELTA_OFFSET) * 0.5;
-
_shader.data.color.value[1] = 0.5 + Math.cos(_delta) * 0.5;
-
_shader.data.color.value[2] = 0.5 + Math.cos(_delta + DELTA_OFFSET) * 0.5;
-
-
_delta += 0.1;
-
-
renderShader();
-
}
-
}
-
}
This is only scratching the surface of Pixel Bender and it's usage. There are four ways total that you can use a Pixel Bender script.
a.) As a filter (using 'ShaderFilter' with 'BitmapData.applyFilter' or 'DisplayObject.filters')
b.) As a blend mode
c.) As a fill (as demonstrated)
d.) As a number cruncher (using 'ShaderJob')
Most of those use cases are obvious, however the last one is shaping up to be the most powerful as Tinic notes in his specifications and implementation post. Using the 'ShaderJob' class, you can execute a Pixel Bender script and listen for an event to be dispatched when it has completed.
In the weeks to come, I will post some more advanced examples of Pixel Bender and it's various uses, as well as demonstrate the psuedo 3D support and invaluable 'drawTriangles' method that has been added to the drawing API.
No comments
