The first step is often the hardest. Here I provide a very simple example of rendering using Stage3D, complete with extensive comments.
All this does is draw a single multicoloured triangle on the screen. Not much, but it’s a good simple place to start.
This example makes use of the MiniAGALAssembler, which is a neat little class allowing the creation of AGAL shader code from within our Actionscript. It is available from the original source (bundled with a couple of other things), or mirrored on this site.
My apologises for the slightly ugly way the code gets displayed on this page. If you prefer it is also available as a FlashDevelop project, with the included Actionscript files of course readable in any text editor.
Main.as
package
{
import flash.display.Sprite;
import flash.display.Stage3D;
import flash.display3D.Context3D;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DVertexBufferFormat;
import flash.display3D.Program3D;
import flash.events.Event;
import flash.geom.Matrix3D;
/**
* @author Salt
* www.saltgames.com
*/
public class Main extends Sprite
{
private const renderAreaWidth:int = 600;
private const renderAreaHeight:int = 400;
private var myContext3D:Context3D;
private var myStage3D:Stage3D;
private var aListOfBundlesToRender:Vector.<BundleToRender>;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// Start here <<
// to do any rendering a Context3D is needed, and they can only be made by a Stage3D object
// fortunately the Stage already has a list of Stage3D objects ready for us to use
myStage3D = stage.stage3Ds[0];
// creating a Context3D is not instant so we have to wait for an event telling us when it is ready
myStage3D.addEventListener(Event.CONTEXT3D_CREATE, context3DCreated);
myStage3D.requestContext3D();
}
private function context3DCreated(e:Event):void
{
// as the CONTEXT3D_CREATE event has triggered, we know that myStage3D has finished creating its Context3D
myContext3D = myStage3D.context3D;
// set up the back buffer of this Context3D, this is the 'canvas' which will be displayed.
var antiAliasLevel:int = 0; // valid values are 0, 2, 4, 8, 16
var enableDepthAndStencilBuffers:Boolean = true;
myContext3D.configureBackBuffer(renderAreaWidth, renderAreaHeight, antiAliasLevel, enableDepthAndStencilBuffers);
// if you want the rendered area to be offset from the top-left corner, the position of myStage3D can be changed
myStage3D.x = 0;
myStage3D.y = 0;
// myContext3D is now ready for rendering to take place on it
addEventListener(Event.ENTER_FRAME, perFrame);
// create a test item to render
aListOfBundlesToRender = new Vector.<BundleToRender>;
aListOfBundlesToRender.push(createTestBundleOfTrianglesToRender());
}
private function perFrame(e:Event):void
{
performRender();
}
private function performRender():void
{
// the back buffer must be cleared before we start trying to draw new stuff on it
// here you can specify what it should be cleared to - including the depth and stencil buffers
myContext3D.clear();
// if you want your view of the world to be changed, simply translate the cameraMatrix
var cameraMatrix:Matrix3D = new Matrix3D();
// the cameraMatrix and orthographicProjectionMatrix are combined to give a complete transformation matrix
// this matrix describes how each vertex in the world should be transformed (moved) to appear in the correct place on the screen
var completeProjectionMatrix:Matrix3D = new Matrix3D();
completeProjectionMatrix.append(cameraMatrix);
completeProjectionMatrix.append(createOrthographicProjectionMatrix(renderAreaWidth, renderAreaHeight, 0, 1));
// now that the transformation matrix has been created, it is loaded into the vertex constant register vc0,
// ready to be accessed by our vertex shader program.
myContext3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, completeProjectionMatrix, true);
for each (var bundleToRender:BundleToRender in aListOfBundlesToRender)
{
// set the shader program
var shaderProgram:Program3D = myContext3D.createProgram();
shaderProgram.upload(bundleToRender.vertexShaderAssembler.agalcode, bundleToRender.fragmentShaderAssembler.agalcode);
myContext3D.setProgram(shaderProgram);
// load vertex position into the vertex registers
myContext3D.setVertexBufferAt(0, bundleToRender.vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_2);
// ^ load a pair of values for each vertex - x and y position
// ^ load into the va0 (vertex attribute zero) register
// load vertex colour into the vertex registers
myContext3D.setVertexBufferAt(1, bundleToRender.vertexBuffer, 2, Context3DVertexBufferFormat.FLOAT_3);
// ^ load three values for each vertex - r,g,b to define a colour
// ^ skip over the first two values for each vertex (those two values are the x and y coordinates)
// ^ load into the va1 (vertex attribute one) register
// actually perform the rendering operation
myContext3D.drawTriangles(bundleToRender.indexBuffer, 0, bundleToRender.numberOfTriangles);
}
// once all the rendering is complete, present the back buffer so that the user actually sees it
myContext3D.present();
}
private function createOrthographicProjectionMatrix(viewWidth:Number, viewHeight:Number, near:Number, far:Number):Matrix3D
{
// this is a projection matrix that gives us an orthographic view of the world (meaning there's no perspective effect)
// the view is defined with (0,0) being in the middle,
// (-viewWidth / 2, -viewHeight / 2) at the top left,
// (viewWidth / 2, viewHeight / 2) at the bottom right,
// and 'near' and 'far' giving limits to the range of z values for objects to appear.
return new Matrix3D(Vector.<Number>
([
2/viewWidth, 0, 0, 0,
0, -2/viewHeight, 0, 0,
0, 0, 1/(far-near), -near/(far-near),
0, 0, 0, 1
]));
}
private function createTestBundleOfTrianglesToRender():BundleToRender
{
var testBundle:BundleToRender = new BundleToRender();
// generate the vertex and index buffers for a single triangle
// in this case the vertex buffer will include both the position and colour data
// if we were to be using texturing, uv coordinates would be loaded in the vertex buffer too
// first prepare the vertex buffer, specifying its dimensions
var verticesForATriangle:int = 3;
var numberOfTriangles:int = 1;
var dataCountForEachVertex:int = 5; // for x, y, red, green, blue
testBundle.vertexBuffer = myContext3D.createVertexBuffer(verticesForATriangle * numberOfTriangles, dataCountForEachVertex);
// similarly prepare the index buffer
var numberOfIndicesForATriangle:int = 3;
testBundle.indexBuffer = myContext3D.createIndexBuffer(numberOfIndicesForATriangle * numberOfTriangles);
// create the vertex data that will be loaded into the buffer
var vertexData:Vector.<Number> = new Vector.<Number>();
// x y r g b
vertexData.push( 0, -100, 1, 0, 0);
vertexData.push(-150, 150, 0, 1, 0);
vertexData.push( 200, 50, 0, 0, 1);
// load the vertex data to the buffer
testBundle.vertexBuffer.uploadFromVector(vertexData, 0, verticesForATriangle * numberOfTriangles);
// create the index data to be loaded in
var indexData:Vector.<uint> = new Vector.<uint>();
indexData.push(0, 1, 2);
testBundle.numberOfTriangles = numberOfTriangles;
// load the index data to the buffer
testBundle.indexBuffer.uploadFromVector(indexData, 0, numberOfIndicesForATriangle * numberOfTriangles);
// create both the vertex and fragment shader programs to be used when rendering this bundle of triangles
// the vertex data created above will be available to these shaders through registers:
// the first 2 values for each vertex (the x and y position) will appear in va0
// the remaining 3 values for each vertex (r, g, b for colour) will appear in va1
testBundle.setVertexShaderCode(
"m44 op, va0, vc0 \n" + // apply the matrix transform to va0 (vertex position), putting result in op (the position output)
"mov v0, va1" // move va1 (vertex colour) to v0 where the fragment shader will be able to access it
);
testBundle.setFragmentShaderCode(
"mov oc, v0" // simply put the vertex colour directly into oc (the colour output)
);
return testBundle;
}
}
}
BundleToRender.as
package
{
import flash.display3D.Context3DProgramType;
import flash.display3D.IndexBuffer3D;
import flash.display3D.VertexBuffer3D;
/**
* @author Salt
* www.saltgames.com
*/
public class BundleToRender
{
// I use the not entirely technical term of "bundle" for a group of triangles being rendered all in one batch.
public var vertexShaderAssembler:AGALMiniAssembler;
public var fragmentShaderAssembler:AGALMiniAssembler;
// the vertexBuffer stores all the per-vertex data
public var vertexBuffer:VertexBuffer3D;
// the indexBuffer is used to determine how the vertices should be connected to form triangles
public var indexBuffer:IndexBuffer3D;
// the number of triangles for which we have data, and we want to draw.
public var numberOfTriangles:int;
public function BundleToRender()
{
}
public function setVertexShaderCode(vertexCode:String):void
{
vertexShaderAssembler = new AGALMiniAssembler();
vertexShaderAssembler.assemble(Context3DProgramType.VERTEX, vertexCode);
}
public function setFragmentShaderCode(fragmentCode:String):void
{
fragmentShaderAssembler = new AGALMiniAssembler();
fragmentShaderAssembler.assemble(Context3DProgramType.FRAGMENT, fragmentCode);
}
}
}
Please make more tutorials about Stage3D!
I got this error message “Context3D not available”.
It’s work fine in stand alone FP11, but not in the web browser.
please I need some advices.
thank you in advance
Hello,
Usually that’s because the SWF is not being embedded correctly in the page. When you embed it, you need to set the wmode parameter to “direct”.
Exactly how you do that depends on how you’re embedding it, but you want to end up with: <param name=”wmode” value=”direct”> within the <object> tag that embeds your SWF.
I hope that helps you!
thanks for quick response Sam and for your specific details and this is helped me, I modify flex index template to add direct param to flash object. And now it’s work fine.
Thank you agai