AGAL_p3_featured

In this third experiment of AGAL, I will show you how we can modify the colors of a Texture in realtime by using some FragmentShader constants based on where the mouse is located on the stage.

Since this is an evolving experiment, I have modified (yet again) the Main3D class to facilitate passing some values to the Context3D object.

Here is the code:

package  {
	import com.adobe.utils.AGALMiniAssembler;
	import com.barliesque.agal.EasyAGAL;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DBlendFactor;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.Context3DTriangleFace;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.textures.Texture;
	import flash.display3D.VertexBuffer3D;
	import flash.events.Event;
	import flash.filters.DropShadowFilter;
	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 static const _TEXT_SHADOW:DropShadowFilter =	new DropShadowFilter(1, 90, 0, .75, 4, 4, 5, 1);
		
		private var _stage3D:Stage3D;
		private var _context3D:Context3D;
		private var _program:Program3D;
		private var _shader:EasyAGAL;
		
		private var _vertexConst:Vector.<Number> =		new <Number>[0,0,0,0];
		private var _fragmentConst:Vector.<Number> =	new <Number>[0,0,0,0];
		private var _backgroundColor:Vector.<Number> =	new <Number>[0,0,0];
		private var _indexData:Vector.<uint>;
		private var _vertexDataLength:int;
		private var _vertexData:Vector.<Number>;
		private var _textures:Vector.<Texture>;
		private var _isTextureTransparent:Boolean =		false;
		
		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);
			
			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 );
			
			_textures =	new <Texture>[];
			
			main();
			
			setupTextures();
			
			addEventListener(Event.ENTER_FRAME, render);
		}
		
		public final function render(e:Event=null):void {
			_context3D.clear(_backgroundColor[0], _backgroundColor[1], _backgroundColor[2]);
			
			calc();
			
			_context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 0, _vertexConst);
			_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, _fragmentConst);
			
			draw();
			
			_context3D.present();
		}
		
		private function setupTextures():void {
			var texID:int =	0;
			for (var t:int = 0, tLen:int = _textures.length; t < tLen; t++) {
				context3D.setTextureAt(texID, _textures[t]);
			}
		}
		
		protected function main():void {} //OVERRIDE
		protected function calc():void {} //OVERRIDE
		protected function draw():void {} //OVERRIDE
		
		////////////////////////////////////////////////////////
		
		public function addLabel(pMessage:String, pHasShadow:Boolean=false ):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);
			
			if (pHasShadow) {
				field.filters =	[_TEXT_SHADOW];
			}
			
			addChild(field);
		}
		
		public function createTextureFromBitmap( pBitmap:* ):Texture {
			var bmp:BitmapData, tex:Texture;
			
			if (pBitmap is Class) {
				pBitmap =	new pBitmap();
			}
			if (pBitmap is Bitmap) {
				bmp =	Bitmap(pBitmap).bitmapData;
			} else if(pBitmap is BitmapData) {
				bmp =	BitmapData(pBitmap);
			}
			
			if (!bmp) {
				return null;
			}
			
			tex =	context3D.createTexture(bmp.width, bmp.height, Context3DTextureFormat.BGRA, false);
			tex.uploadFromBitmapData(bmp);
			
			return tex;
		}
		
		public function setBitmapTexture( pBitmap:*, pSamplerID:int=0 ):void {
			var tex:Texture =	createTextureFromBitmap(pBitmap);
			
			_textures[pSamplerID] = tex;
		}
		
		public function createProgram(pVertexSrc:String, pFragmentSrc:String):Program3D {
			var program:Program3D =	_context3D.createProgram();
			program.upload(assembleVertex(pVertexSrc), assembleFragment(pFragmentSrc));
			
			return program;
		}
		
		////////////////////////////////////////////////////////
		
		public function get shader():EasyAGAL { return _shader; }
		public function set shader(value:EasyAGAL):void {
			_shader = value;
			
			program =	_shader.upload(context3D);
		}
		
		public function get program():Program3D { return _program; }
		public function set program(value:Program3D):void {
			_program = value;
			
			_context3D.setProgram(_program);
		}
		
		public function set programXML(value:XML):void {
			var xDoc:XML =			value;
			var xVertex:XML =		xDoc.vertex.children()[0];
			var xFragment:XML =		xDoc.fragment.children()[0];
			var sVertex:String =	xVertex.toXMLString().replace(/\[/g,'<').replace(/\]/g,'>');
			var sFragment:String =	xFragment.toXMLString().replace(/\[/g,'<').replace(/\]/g,'>');
			
			program =	createProgram(sVertex, sFragment);
		}
		
		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();
			}
			_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();
			}
			_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 vertexConst():Vector.<Number> { return _vertexConst; }
		public function get fragmentConst():Vector.<Number> { return _fragmentConst; }
		
		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; }
		
		public function get isTextureTransparent():Boolean { return _isTextureTransparent; }
		public function set isTextureTransparent(value:Boolean):void {
			_isTextureTransparent = value;
			
			if (_isTextureTransparent) {
				context3D.setBlendFactors(Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);
			} else {
				context3D.setBlendFactors(Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ZERO);
			}
		}
	}
}

The special additions to this update are:

  • Instruction label is placed at the bottom, and can be assigned a DropShadowFilter to improve readability if the rendered result makes it too hard to read.
  • vertexConst and fragmentConst can directly manipulate the first constant components (xyzw  or rgba ) of the VertexShader and FragmentShader, respectively (same as vc0 and fc0).
  • BitmapData can easily be uploaded to the Context3D with setBitmapTexture() as a Bitmap Class, a Bitmap Instance, or directly as a BitmapData instance. NOTE: However, you must make certain yourself to supply them in dimensions of power-of-two (ex: 128 x 1024)… eventually we can take care of this to automatically size it.
  • If using transparent PNGs, you can enable this with isTextureTransparent = true.
  • To modify the Shader’s constant values before rendering, override the calc() method and assign the values to the constants in here. It can also be used to modify vertex-data during this phase if you need to.
  • You can now alternatively supply your AGAL code as XML to the setter property “programXML” . One important difference is that square-brackets [] are used instead of angle-brackets <> to supply opcode options (ex: for the tex  opcode). This is due to XML parsing (which may very well be worked-around using CDATA, but why would we add the extra mess if it can be avoided?).

Experiment #3: Manipulating RGB Colors on a Texture

In this example, I will be using the famous painting “The Scream”  by Edvard Munch.

package {
	import flash.display3D.Context3DBlendFactor;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTriangleFace;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.textures.Texture;
	
	/**
	 * @author Pierre Chamberlain
	 */
	[SWF(frameRate=60,width=640,height=480)]
	public class Test_2DTexture extends Main3D {
		[Embed(source="../images/edvard_munch_scream_256x256.jpg")]
		private static const _BITMAP_TEXTURE:Class;
		
		
		protected override function main():void {
			super.main();
			
			backgroundColor =	0x444444;
			
			//Setup some potentially useful vertex-constants (zero, half, normal, double):
			vertexConst[0] = 0;
			vertexConst[1] = .5;
			vertexConst[2] = 1;
			vertexConst[3] = 2;
			
			//Only 5 pieces of information exists per vertices:
			var size:Number =	.8;
			setVertexData( 4, new <Number>[
				//Coordinates (XY) and Texture-Coordinates (UV)
				-size, -size,	0, 1,
				-size, size,	0, 0,
				size, size,		1, 0,
				size, -size,	1, 1
			]);
			
			indexData =	new <uint>[
				//Draw two triangles (a square)
				2,1,0, 3,2,0
			];
			
			context3D.setVertexBufferAt(0, _vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_4);
			context3D.setCulling(Context3DTriangleFace.NONE);
			
			//Set the current Shader to the context:
			//shader =	new Shader2DTexture();
			
			//An alternative to compile AGAL from XML:
			programXML =
			<shader>
				<vertex>
					mov op.xy, va0.xy
					mov op.zw, vc0.xz
					mov v0, va0.zw
				</vertex>
				<fragment>
					tex ft0, v0, fs0 [2d nearest repeat nomip]
					sub ft0.gb, ft0.gb, fc0.rr
					sub ft0.rb, ft0.rb, fc0.gg
					sub ft0.rg, ft0.rg, fc0.bb
					mov oc, ft0
				</fragment>
			</shader>;
			
			setBitmapTexture(_BITMAP_TEXTURE);
			
			addLabel('Move Mouse ANYWHERE to change Texture Color\n<font size="12">(bottom = blue, middle = green, top = red / left = brighter, right = darker)</font>', true);
		}
		
		protected override function calc():void {
			var theX:int =		stage.mouseX;
			var theY:int =		stage.mouseY;
			var ratioX:Number =	theX / viewWidth;
			var ratioY:Number =	theY / viewHeight;
			fragmentConst[0] =	getRange(ratioY, 0, .5, 0) * ratioX;
			fragmentConst[1] =	getRange(ratioY, 0, 1, .5) * ratioX;
			fragmentConst[2] =	getRange(ratioY, .5, 1, 1) * ratioX;
		}
		
		protected override function draw():void {
			context3D.drawTriangles(_indexDataBuffer);
		}
		
		private function getRange( pValue:Number, pMin:Number, pMax:Number, pNeutral:Number ):Number {
			pValue =	pValue<pMin ? pMin : (pValue>pMax ? pMax : pValue);
			return		1 - Math.abs(pNeutral - pValue) * 2;
		}
	}
}

Those of you using EasyAGAL can append this to the end of this class and uncomment the line:

"//shader = new Shader2DTexture()"

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

class Shader2DTexture extends EasyAGAL {
	protected override function _vertexShader():void {
		super._vertexShader();
		
		var thePositionXY:IField =	ATTRIBUTE[0]._("xy");
		var theUV:IField =			ATTRIBUTE[0]._("zw");
		var zero_one:IField =		CONST[0]._("xz");
		
		var dst_XY:IField =			OUTPUT._("xy");
		var dst_ZW:IField =			OUTPUT._("zw");
		var var_Frag:IRegister =	VARYING[0];
		
		mov(dst_XY, thePositionXY);
		mov(dst_ZW, zero_one);
		mov(var_Frag, theUV);
	}
	
	protected override function _fragmentShader():void {
		super._fragmentShader();
		
		var param_0:IRegister =		CONST[0];
		var var_Frag:IRegister =	VARYING[0];
		var src_Tex:ISampler =		SAMPLER[0];
		var dst_Tex:IRegister =		TEMP[0];
		
		var tex_options:Array =	[TextureFlag.TYPE_2D, TextureFlag.FILTER_NEAREST, TextureFlag.MIP_NO];
		
		tex(dst_Tex, var_Frag, src_Tex, tex_options);
		sub(dst_Tex._("gb"), dst_Tex._("gb"), param_0._("rr"));
		sub(dst_Tex._("rb"), dst_Tex._("rb"), param_0._("gg"));
		sub(dst_Tex._("rg"), dst_Tex._("rg"), param_0._("bb"));
		mov(OUTPUT, dst_Tex);
	}
}

Here is the demo:

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