Simple Annotated Example of Stage3D

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);
  }
}
}

 

4 thoughts on “Simple Annotated Example of Stage3D

  1. 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

Leave a Reply

Your email address will not be published.

* Copy this password:

* Type or paste password here:

16,851 Spam Comments Blocked so far by Spam Free Wordpress