AGAL_p2_featured

You may have notice a slight difference with the title of this post when compared to my first post, “part 1” released previously. To further facilitate writing Vertex and Fragment shaders in AGAL, I will be using EasyAGAL by Studio Barliesque (available on GitHub here or as a ZIP download here)

Once you add EasyAGAL to your project class-paths, it’s really simple to create shaders. Although lengthier than writing its raw hieroglyphic  form, this library will allow you to assign more meaningful variable names to the various registers and components you wish to operate on.

You may recall from part 1 of the “Experimenting with AGAL” installment that I was using Main3D.as as a helper class to setup my Stage3D environment. I’ve added a few extra lines of code to make the EasyAGAL integration even more “easier” .

See for yourself:

package  {
	import com.adobe.utils.AGALMiniAssembler;
	import com.barliesque.agal.EasyAGAL;
	import flash.display.Sprite;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTriangleFace;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.VertexBuffer3D;
	import flash.events.Event;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	import flash.text.TextFormatAlign;
	import flash.utils.ByteArray;
	
	/**
	 * @author Pierre Chamberlain
	 */
	public class Main3D extends Sprite {
		private var _stage3D:Stage3D;
		private var _context3D:Context3D;
		private var _program:Program3D;
		private var _shader:EasyAGAL; //NEW: See the Getter & Setter below... 
		
		private var _backgroundColor:Vector.<Number> =	new <Number>[0,0,0];
		private var _indexData:Vector.<uint>;
		private var _vertexDataLength:int;
		private var _vertexData:Vector.<Number>;
		
		protected var _indexDataBuffer:IndexBuffer3D;
		protected var _vertexBuffer:VertexBuffer3D;
		
		
		public function Main3D() {
			super();
			
			stage ? init() :  addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(e:Event = null):void {
			e && removeEventListener(Event.ADDED_TO_STAGE, init);
			// entry point
			
			stage.align =		StageAlign.TOP_LEFT;
			stage.scaleMode =	StageScaleMode.NO_SCALE;
			
			prepareStage3D();
		}
		
		private function prepareStage3D():void {
			_stage3D =	stage.stage3Ds[0];
			_stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreated);
			_stage3D.requestContext3D();
		}
		
		private function onContextCreated(e:Event):void {
			_context3D =	_stage3D.context3D;
			if (!_context3D) {
				return;
			}
			
			_context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight, 1, false);
			_context3D.setCulling( Context3DTriangleFace.BACK );
			
			main();
			
			addEventListener(Event.ENTER_FRAME, render);
		}
		
		protected function main():void {
			//OVERRIDE
		}
		
		public final function render(e:Event=null):void {
			_context3D.clear(_backgroundColor[0], _backgroundColor[1], _backgroundColor[2]);
			
			draw();
			
			_context3D.present();
		}
		
		protected function draw():void {
			//OVERRIDE
		}
		
		////////////////////////////////////////////////////////
		
		public function addLabel(pMessage:String):void {
			var format:TextFormat =		new TextFormat("Courier New", 20, 0xffffff, true);
			format.align =				TextFormatAlign.CENTER;
			var field:TextField =		new TextField();
			field.autoSize =			TextFieldAutoSize.LEFT;
			field.defaultTextFormat =	format;
			field.selectable =			false;
			field.mouseEnabled =		false;
			field.multiline =			true;
			field.wordWrap =			false;
			field.width =				1;
			field.height =				20;
			field.htmlText =			pMessage;
			field.x =					(viewWidth - field.width) * .5;
			field.y =					(viewHeight - field.height) * .5;
			
			addChild(field);
		}
		
		public function createProgram(pVertexSrc:String, pFragmentSrc:String):Program3D {
			var program:Program3D =	_context3D.createProgram();
			program.upload(assembleVertex(pVertexSrc), assembleFragment(pFragmentSrc));
			
			return program;
		}
		
		public function get program():Program3D { return _program; }
		public function set program(value:Program3D):void {
			_program = value;
			
			_context3D.setProgram(_program);
		}
		
		private function assemble(pString:String, pKind:String):ByteArray {
			var agal:AGALMiniAssembler =	new AGALMiniAssembler();
			return agal.assemble(pKind, pString.replace(/\|/g,"\n"));
		}
		
		public function assembleVertex(pSource:String):ByteArray {
			return assemble(pSource, Context3DProgramType.VERTEX);
		}
		
		public function assembleFragment(pSource:String):ByteArray {
			return assemble(pSource, Context3DProgramType.FRAGMENT);
		}
		
		public function get backgroundColor():uint {
			var r:uint = _backgroundColor[0] * 255;
			var g:uint = _backgroundColor[1] * 255;
			var b:uint = _backgroundColor[2] * 255;
			
			return r << 16 | g << 8 | b;
		}
		
		public function set backgroundColor(value:uint):void {
			_backgroundColor[0] =	((value >> 16) & 0xFF) / 255;
			_backgroundColor[1] =	((value >> 8 ) & 0xFF) / 255;
			_backgroundColor[2] =	(value & 0xFF) / 255;
		}
		
		public function get indexData():Vector.<uint> { return _indexData; }
		public function set indexData(value:Vector.<uint>):void {
			_indexData =			value;
			
			if (_indexDataBuffer) {
				_indexDataBuffer.dispose(); //NEW
			}
			_indexDataBuffer =		_context3D.createIndexBuffer( _indexData.length );
            _indexDataBuffer.uploadFromVector( _indexData, 0, _indexData.length );
		}
		
		public function setVertexData( pDataLength:int, pVertexData:Vector.<Number> ):void {
			_vertexDataLength =	pDataLength;
			_vertexData =		pVertexData;
			
			if (_vertexBuffer) {
				_vertexBuffer.dispose(); //NEW
			}
			_vertexBuffer =		_context3D.createVertexBuffer(numOfVertices, _vertexDataLength);
			_vertexBuffer.uploadFromVector(_vertexData, 0, numOfVertices);
		}
		
		public function get numOfIndexes():int {
			return !_indexData ? 0 : _indexData.length / 3;
		}
		
		public function get numOfVertices():int {
			return !_vertexData || _vertexDataLength <= 0 ? 0 : _vertexData.length / _vertexDataLength;
		}
		
		public function get shader():EasyAGAL { return _shader; } //NEW!
		public function set shader(value:EasyAGAL):void {
			_shader = value;
			
			program =	_shader.upload(context3D);
		}
		
		public function get stage3D():Stage3D { return _stage3D; }
		public function get context3D():Context3D { return _context3D; }
		public function get viewWidth():Number { return stage.stageWidth; }
		public function get viewHeight():Number { return stage.stageHeight; }
		public function get viewRatio():Number { return viewWidth / viewHeight; }
	}
}

Aside from the additional “shader”  getter & setter, I’ve made sure to dispose any resources if they were replaced with new ones, as this will happen frequently in the example I want to show you.

Experiment #2: Creating and Interpreting your own Vertex Data.

Many of the AGAL examples on the web that I’ve found so far have been using vertex-data with a length of six (6) pieces of information per vertices: 3 for the coordinates (XYZ) and 3 for the color (RGB). I’ve mentioned before, however, that I’m only interested in using Shaders for 2D purposes at this time. So I was curious if I could get rid of the z component part of the vertex-data without much catastrophic side-effects. Ends up that… YES, you can! BUT, this can only be done at the cost of one vertex-constant.

Let’s see what I mean by that:

package {
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTriangleFace;
	import flash.display3D.Context3DVertexBufferFormat;
	
	/**
	 * @author Pierre Chamberlain
	 */
	[SWF(frameRate=60,width=640,height=480)]
	public class Test_2Vertices3Colors extends Main3D {
		private static const RADIAN:Number =	180 / Math.PI;
		
		private var _four_constants:Vector.<Number>;
		
		private var _iterations:int =	0;
		private var _counter:Number =	0;
		private var _halfWidth:Number;
		
		public var speed:Number =		.001;
		
		protected override function main():void {
			super.main();
			
			backgroundColor =	0x444444;
									//Just entering some constant values [3 zeros and then one]
			_four_constants =		new <Number>[0,0,0,1];
			_halfWidth =			viewWidth * .5;
			
			indexData =	new <uint>[
				//Draw two triangles (a square)
				2,1,0, 3,2,0
			];
			
			//Set the current Shader to the context:
			shader =	new Shader2Vertices3Colors();
			
			addLabel('Move Your Mouse LEFT-to-RIGHT\n<font size="12">(control the speed and direction of color-rotation)</font>');
		}
		
		protected override function draw():void {
			super.draw();
			
			_iterations = 0;
			
			//Only 5 pieces of information exists per vertices:
			setVertexData( 5, new <Number>[
				//Coordinates (xY) and Colors (RGB)
				-.9, -.9,	someCLR(), someCLR(), someCLR(),
				-.9, .9,	someCLR(), someCLR(), someCLR(),
				.9, .9,		someCLR(), someCLR(), someCLR(),
				.9, -.9,	someCLR(), someCLR(), someCLR()
			]);
			
			speed =		(_halfWidth - stage.mouseX) * .0001;
			_counter += speed;
			
			context3D.setVertexBufferAt(0, _vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_2);
			context3D.setVertexBufferAt(1, _vertexBuffer, 2, Context3DVertexBufferFormat.FLOAT_3);
			context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 0, _four_constants);
			context3D.setCulling(Context3DTriangleFace.NONE);
			
			context3D.drawTriangles(_indexDataBuffer);
		}
		
		private function someCLR():Number {
			return .5 + Math.cos((_counter * 12 + _iterations++) * RADIAN * .1) * .5;
		}
	}
}

import com.barliesque.agal.EasyAGAL;
import com.barliesque.agal.IField;
import com.barliesque.agal.IRegister;

class Shader2Vertices3Colors extends EasyAGAL {
	protected override function _vertexShader():void {
		super._vertexShader();
		
		var thePositionXY:IField =		ATTRIBUTE[0]._("xy");
		var thePositionZW:IField =		CONST[0]._("zw");
		var theColor:IField =			ATTRIBUTE[1];
		var theFragmentVar:IField =		VARYING[0];
		
		mov(OUTPUT._("xy"), thePositionXY);
		mov(OUTPUT._("zw"), thePositionZW);
		mov(theFragmentVar, theColor);
	}
	
	protected override function _fragmentShader():void {
		super._fragmentShader();
		
		var theFragmentVar:IField =		VARYING[0];
		
		mov(OUTPUT, theFragmentVar);
	}
}

Let’s first focus on the internal class Shader2Vertices3Colors found at the bottom. It extends EasyAGAL and overrides two of it’s methods (this is intentional and mandatory). In the _vertexShader() protected method, I created local variables with meaningful names to identify what the register and it’s components contains.

  • The position is obtained in two parts: X and Y is defined in the vertex-input #0 (aka: ATTRIBUTE[0]._("xy") or "va0.xy"), while Z and W is defined in a vertex-constant #0 (aka: CONST[0]._("zw") or "vc0.zw").
  • The color is obtained as usual: RGB values from the vertex-input #1 (aka: ATTRIBUTE[1] or "va1").
  • The usual variant register used by the Fragment Shader: This will receive the interpolated color data (aka: VARYING[0] or "v0").
  • The mov  opcode transfers values: thePositionXY gets sent to the vertex-output register’s XY components (aka: OUTPUT._("xy") or "op.xy").
  • The mov  opcode transfers more values: thePositionZW gets sent to the vertex-output register’s ZW components (aka: OUTPUT._("zw") or "op.zw").

The _fragmentShader() protected method then makes use of the VARYING[0] register to draw the color of the pixels to the fragment-output register (aka: OUTPUT or "oc").

As for the main class that uses this shader, Test_2Vertices3Colors sets vertices each time the draw() method gets called (internally by the Main3D class). Notice that we can still initialize our index-data once at the beginning of our application, and it will still refer correctly to indices of our updated vertex-data on each frame.

Now… this was mostly pure luck. I first tried to assign random colors to each vertices, but I wasn’t really pleased with the flashy color results. Then I wanted to try using a function that would return more gradual color results based on a counter variable – so I used Math.cos(). Since it returns values between -1.0 to 1.0 (a range of 2.0), I narrowed it down to 0.0 to 1.0 (a range of 1.0) by multiplying it by 0.5 (cut it in half) and offset it by 0.5 (shift it up).

For additional entertainment, the mouse position is used to vary the speed and direction of the “circular motion” of the color rotation.

Try it!

This area requires
Adobe FlashPlayer version 11 or above.
iOS Devices are not currently supported.

If you like these tutorials, please provide your feedback! If you would like to suggest any way I can improve the structure or clarity of these tutorials, please let me know in the comments below :)