Introduction to AGAL: Part 3
Continuing from last time, in today’s article we’ll discuss the process of building and using basic shaders. This forms the basis of all Flash 11 Stage3D
engines, so you’ll be learning how hardware-accelerated 3D shaders are built from the ground up.
The first step in building the shader is to assemble the AGAL assembly source code into AGAL bytecode. This is usually accomplished with Adobe’s AGALMiniAssembler class. Here are the steps to assembling your AGAL assembly source code with AGALMiniAssembler
:
// Create an AGALMiniAssembler that will assemble AGAL assembly source code // into AGAL bytecode var assembler:AGALMiniAssembler = new AGALMiniAssembler(); // Assemble the vertex shader source (a String) assembler.assemble(Context3DProgramType.VERTEX, VERTEX_SHADER_SOURCE); // Check for errors that occurred during assembly if (assembler.error) { trace("Error assembling the vertex shader: " + assembler.error); return; } // If there were no errors, get the vertex shader bytecode var vertexShaderBytecode:ByteArray = assembler.agalcode; // Assemble the fragment shader source (a String). // You do NOT need to create a new AGALMiniAssembler to do this. // You can easily reuse any other AGALMiniAssembler. assembler.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER_SOURCE); // Check for errors that occurred during assembly if (assembler.error) { trace("Error assembling the fragment shader: " + assembler.error); return; } // If there were no errors, get the fragment shader bytecode var fragmentShaderBytecode:ByteArray = assembler.agalcode;
Next you must acquire a shader program resource from the Context3D
. As a refresher, here’s how you get the Context3D
:
// Get the Stage3D to use var stage3D:Stage3D = stage.stage3Ds[0]; // or 1 or 2 or 3 // Listen for when the Context3D is created for it stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreated); // Request the Context3D with either software or auto (hardware with software fallback) stage3D.requestContext3D(Context3DRenderMode.AUTO); // Callback for when the Context3D is created function onContext3DCreated(ev:Event): void { // Get the Context3D from the Stage3D now that it is created var context3D:Context3D = stage3D.context3D; // Setup the back buffer for the context context3D.configureBackBuffer( stage.stageWidth, // for full-stage width stage.stageHeight, // for full-stage height 0, // no antialiasing true // enable depth buffer and stencil buffer ); // ... use the Context3D }
Now that we have a Context3D
we can go about creating and uploading our shader bytecode to a Program3D
object which represents a shader program:
// Create a new shader program for our shader (vertex and fragment parts) var program:Program3D = context3D.createProgram(); // Upload the vertex and fragment shaders to the shader program try { program.upload(vertexShaderBytecode, fragmentShaderBytecode); } catch (err:Error) { // Lots of error can occur in uploading the program. Many of them // are simple error checking (e.g. null bytecode) but many more // can indicate invalid bytecode such as programs that have more // than 200 hardware instructions. trace("Couldn't upload shader program: " + err); return; }
Now that we’ve built the shader we can actually use it. That is pretty easy:
// Set the program as the shader program to use for all // drawTriangles calls context3D.setProgram(program); // Draw as many times as you'd like using the same program context3D.drawTriangles(myTriangles); context3D.drawTriangles(myOtherTriangles); // Finalize the drawing context3D.present();
Finally, let’s look at a trivial AGAL program. The vertex program does one thing: outputs whatever vector is stored in its first vertex attribute as the vertex’s clip space coordinate. The fragment shader also does only one thing: outputs whatever vector is stored in its first constant as the vertex’s color. These shaders both satisfy the basic requirement of all shaders: output a position (vertex shader) and a color (fragment shader). Here is the source code for the vertex shader:
mov op, va0
And here is the source code for the fragment shader:
mov oc, fc0
As discussed in the previous article, we need to pass this data from our AS3 program to the shader using the Context3D
. Here’s how we’ll do it:
//////////////////// // One time process: //////////////////// // Create the color constant as a Vector of RGBA values are // between 0 and 1, not 0 and 0xFF. var color:Vector.<Number> = new <Number>[0.9296875, 0.9140625, 0.84765625, 1]; // Create a vertex buffer of vertex positions for one // triangle with 3 attributes (X, Y, and Z) per vertex var positions:VertexBuffer3D = context3D.createVertexBuffer(3, 3); // Upload the triangle's positions to the vertices from 0 through 3 positions.uploadFromVector(new <Number>[ 0, 1, 0, // top-center -1, -1, 0, // bottom-left 1, -1, 0 // bottom-right ], 0, 3); // Create an index buffer for the triangle var tris:IndexBuffer3D = context3D.createIndexBuffer(3); // Upload the triangle's indices to the index buffer tris.uploadFromVector(new <uint>[0, 1, 2], 0, 3); ///////////////////////////////// // Drawing code done every frame: ///////////////////////////////// // Clear the back buffer of last frame's drawing context3D.clear(); // Set the program as the shader program to use for all // drawTriangles calls context3D.setProgram(program); // Use the already-uploaded vertex positions as va0 context3D.setVertexBufferAt(0, positions, 0, Context3DVertexBufferFormat.FLOAT_3); // Upload the color to the fragment shader constant fc0 context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, color); // Draw as many times as you'd like using the same program context3D.drawTriangles(tris); // Finalize the drawing context3D.present();
Here’s the same code snippet as a complete program:
package { import com.adobe.utils.*; import flash.display3D.*; import flash.display.*; import flash.events.*; import flash.utils.*; public class Stage3DColorTriangleTest extends Sprite { private static const VERTEX_SHADER_SOURCE:String = "mov op, va0"; private static const FRAGMENT_SHADER_SOURCE:String = "mov oc, fc0"; private var stage3D:Stage3D; private var context3D:Context3D; private var positions:VertexBuffer3D; private var tris:IndexBuffer3D; private var program:Program3D; private var color:Vector.<Number>; public function Stage3DColorTriangleTest() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; stage.frameRate = 60; // Get the Stage3D to use stage3D = stage.stage3Ds[0]; // or 1 or 2 or 3 // Listen for when the Context3D is created for it stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreated); // Request the Context3D with either software or auto (hardware with software fallback) stage3D.requestContext3D(Context3DRenderMode.AUTO); } // Callback for when the Context3D is created private function onContext3DCreated(ev:Event): void { // Get the Context3D from the Stage3D now that it is created context3D = stage3D.context3D; context3D.enableErrorChecking = true; // Setup the back buffer for the context context3D.configureBackBuffer( stage.stageWidth, // for full-stage width stage.stageHeight, // for full-stage height 0, // no antialiasing true // enable depth buffer and stencil buffer ); // Create an AGALMiniAssembler that will assemble AGAL assembly source code // into AGAL bytecode var assembler:AGALMiniAssembler = new AGALMiniAssembler(); // Assemble the vertex shader source (a String) assembler.assemble(Context3DProgramType.VERTEX, VERTEX_SHADER_SOURCE); // Check for errors that occurred during assembly if (assembler.error) { trace("Error assembling the vertex shader: " + assembler.error); return; } // If there were no errors, get the vertex shader bytecode var vertexShaderBytecode:ByteArray = assembler.agalcode; // Assemble the fragment shader source (a String). // You do NOT need to create a new AGALMiniAssembler to do this. // You can easily reuse any other AGALMiniAssembler. assembler.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER_SOURCE); // Check for errors that occurred during assembly if (assembler.error) { trace("Error assembling the fragment shader: " + assembler.error); return; } // If there were no errors, get the fragment shader bytecode var fragmentShaderBytecode:ByteArray = assembler.agalcode; // Create a new shader program for our shader (vertex and fragment parts) program = context3D.createProgram(); // Upload the vertex and fragment shaders to the shader program try { program.upload(vertexShaderBytecode, fragmentShaderBytecode); } catch (err:Error) { // Lots of error can occur in uploading the program. Many of them // are simple error checking (e.g. null bytecode) but many more // can indicate invalid bytecode such as programs that have more // than 200 hardware instructions. trace("Couldn't upload shader program: " + err); return; } // Create the color constant as a Vector of RGBA values are // between 0 and 1, not 0 and 0xFF. color = new <Number>[0.9296875, 0.9140625, 0.84765625, 1]; // Create a vertex buffer of vertex positions for one // triangle with 3 attributes (X, Y, and Z) per vertex positions = context3D.createVertexBuffer(3, 3); // Upload the triangle's positions to the vertices from 0 through 3 positions.uploadFromVector(new <Number>[ 0, 1, 0, // top-center -1, -1, 0, // bottom-left 1, -1, 0 // bottom-right ], 0, 3); // Create an index buffer for the triangle tris = context3D.createIndexBuffer(3); // Upload the triangle's indices to the index buffer tris.uploadFromVector(new <uint>[0, 1, 2], 0, 3); // Start the simulation addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(ev:Event): void { // Clear the back buffer of last frame's drawing context3D.clear(); // Set the program as the shader program to use for all // drawTriangles calls context3D.setProgram(program); // Use the already-uploaded vertex positions as va0 context3D.setVertexBufferAt(0, positions, 0, Context3DVertexBufferFormat.FLOAT_3); // Upload the color to the fragment shader constant fc0 context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, color); // Draw as many times as you'd like using the same program context3D.drawTriangles(tris); // Finalize the drawing context3D.present(); } } }
That’s all for this time. Stay tuned for next time when we’ll do some complicated things with AGAL. In the meantime, if you’ve spotted a bug or have any questions about AGAL, feel free to post a comment.
#1 by Sam on December 29th, 2011 ·
Very nice Jackson !
I like your systematic approach. You certainly help minimize entropy in our Stage3D code! :)
Many thanks!
#2 by Toma on January 3rd, 2012 ·
Thank’s a lot for theses tutorials
I have one question, about alpha mode, is it possible to draw Triangles with alpha ? I think it’s possible with shader, but i don’t know how.
Thank you :)
#3 by jackson on January 3rd, 2012 ·
You’re very welcome. :)
It sure is possible to draw with alpha. You simply need to draw an alpha component of less than 1. For example, if you sample a texture that has alpha you’ll get that. You’ll also need to use Context3D.setBlendFactors. For “normal” alpha drawing, that usually looks like this:
#4 by Toma on January 4th, 2012 ·
it’s exactly that ! Thank you.
#5 by Nisse Bergman on March 15th, 2012 ·
Great articles! Thanks a million!
#6 by ganesh on September 18th, 2012 ·
this is not for comparing,
about triangle tutorial I find “norbz’ deb blog” one is much easier to understand http://blog.norbz.net/tutorials/
but explanations are better here
#7 by Erlend HL on April 11th, 2014 ·
Great! Couldn’t find any helpful articles anywhere until I found this. Thanks!