switching colour alternativa3d

David Jones
@david3jones
avatar-davidejones

A popular shader that I’ve seen used in away3d and flare3d is this sort of colour switching effect with a bulge. Thanks to the excellent post at http://blog.volger.org/switchingcolor-shader/ on how to create such an effect I’ve created an alternativa3d material using the same principles. Note: Thanks to makc from the alternativa3d forums the position is now fixed, and the code below has been updated switching color Here is the finished material

package alternativa.engine3d.materials {
    
    	import alternativa.engine3d.alternativa3d;
    	import alternativa.engine3d.core.Camera3D;
    	import alternativa.engine3d.core.DrawUnit;
    	import alternativa.engine3d.core.Light3D;
    	import alternativa.engine3d.core.Object3D;
    	import alternativa.engine3d.core.Renderer;
    	import alternativa.engine3d.core.Transform3D;
    	import alternativa.engine3d.core.VertexAttributes;
    	import alternativa.engine3d.materials.compiler.Linker;
    	import alternativa.engine3d.materials.compiler.Procedure;
    	import alternativa.engine3d.materials.compiler.VariableType;
    	import alternativa.engine3d.objects.Surface;
    	import alternativa.engine3d.resources.Geometry;
    	import flash.events.TimerEvent;
    	import flash.geom.Vector3D;
    	import flash.utils.Timer;
    	import flash.utils.getTimer;
    
    	import flash.display3D.Context3D;
    	import flash.display3D.Context3DBlendFactor;
    	import flash.display3D.Context3DProgramType;
    	import flash.display3D.VertexBuffer3D;
    	import flash.utils.Dictionary;
    
    	use namespace alternativa3d;
    
    	/**
    	 * The material fills surface with solid color in light-independent manner. Can draw a Skin with no more than 41 Joints per surface. See Skin.divide() for more details.
    	 *
    	 * @see alternativa.engine3d.objects.Skin#divide()
    	 */
    	public class SwitchFillMaterial extends Material {
    		
    		private static var caches:Dictionary = new Dictionary(true);
    		private var cachedContext3D:Context3D;
    		private var programsCache:Dictionary;
    
    		// Vertex Procedure
    		private static const SwitchFillInsetProcedure:Procedure = getSwitchFillVProcedure(true);
    		private static const SwitchFillOutsetProcedure:Procedure = getSwitchFillVProcedure(false);
    		private static function getSwitchFillVProcedure(inset:Boolean):Procedure
    		{
    			var insetMode:String = (inset) ? "" : "neg t5, t5";
    			return new Procedure([
    				"#a0=aPosition",
    				"#a1=aNormal",
    				"#c4=cOrigin",
    				"#c5=cFactors",
    				"#c6=cMixed",
    				"#c7=cMixed2",
    				
    				// calculate distance between current position and distance to color change origin
    				// distance = length(iwposition.xyz - origin.xyz);				
    				"sub t1 a0.xyz c4.xyz",
    				"dp3 t1 t1 t1",
    				"sqt t1 t1",
    				
    				// take position of current vertex and multiply it with the world
    				// iwposition = float4 (position,1) * world;
    				"mul t0 a0 c0",
    				
    				// adjust for aesthetics  
    				// distance /= 3;
    				"div t1 t1 c7.x",
    				
    				// move factors into v2
    				"mov v2 c5",
    				
    				// calculate factors of current colors
    				// f1 = saturate((distance - timedelta) / spread);
    				// f2 = saturate(-((distance - timedelta) - spread) / spread);
    				//f1
    				"sub t2 t1 c6.x", // distance - timedelta
    				"div t2 t2 c6.y", // / spread
    				"sat t2 t2", 	  //saturate
    				"mov v2.x t2",    // factors.x
    				//f2
    				"sub t3 t1 c6.x", // distance - timedelta
    				"sub t3 t3 c6.y", // -spread
    				"neg t3 t3",	  // multiply by -1 to get -((distance - timedelta) - spread)
    				"div t3 t3 c6.y", // / spread
    				"sat t3 t3",      // saturate
    				"mov v2.y t3",    // factors.y 
    
    				//mynormal = float4 (normal * world,1) * -sin(f1 * f2 / 2);
    				"mul t4 a1 c0",   // normal * world				
    				"mul t5 t2 t3",   // f1 * f2
    				"div t5 t5 c6.w", // /2
    				"sin t5 t5",	  // sin
    				insetMode,	  // neg to get -sin
    				"mul t6 t4 t5",  // multiply 
    				
    				//iwposition = float4 (position,1) + mynormal;
    				"add t0 a0 t5",		
    				
    				// iwposition * worldViewProj;	
    				"m44 o0 t0 c0",
    			], "getSwitchFillVProcedure");
    		}
    		
    		// Fragment procedure
    		static alternativa3d const getSwitchFillFProcedure:Procedure = new Procedure([
    			"#c0=cCurrentColor", 
    			"#c1=cNewColor", 
    			//color = factors.x * currentcolor;
    			"mul t0 v2.x c0",
    			//color += factors.y * newcolor;
    			"mul t1 v2.y c1",
    			"add t0, t0, t1",
    			//output
    			"mov o0, t0"
    		], "getSwitchFillFProcedure");
    		
    		/**
    		 * Transparency
    		 */
    		public var alpha:Number = 1;
    		
    		private var red:Number;
    		private var green:Number;
    		private var blue:Number;
    		
    		private var nred:Number;
    		private var ngreen:Number;
    		private var nblue:Number;
    		private var nalpha:Number = 1;
    		
    		private var currentColor:uint;
    		private var newColor:uint;
    		private var origin:Vector3D = new Vector3D(0, 0, 0, 1);
    		private var factors:Vector3D = new Vector3D(0, 0, 0, 1);
    		private var timedelta:Number = 0.5;
    		private var spread:Number = 6;
    		private var startTime:Number = 0;
    		private var timeStretch:Number = 40;
    		private var inset:Boolean = false;
    		
    		/**
    		 * Color.
    		 */
    		public function get color():uint {
    			return (red*0xFF << 16) + (green*0xFF << 8) + blue*0xFF;
    		}
    
    		/**
    		 * @private
    		 */
    		public function set color(value:uint):void {
    			red = ((value >> 16) & 0xFF)/0xFF;
    			green = ((value >> 8) & 0xFF)/0xFF;
    			blue = (value & 0xff)/0xFF;
    		}
    
    		/**
    		 * Creates a new SwitchFillMaterial instance.
    		 * @param color Color .
    		 * @param alpha Transparency.
    		 */
    		public function SwitchFillMaterial(color:uint = 0x7F7F7F, alpha:Number = 1, timeStretch:Number = 40, inset:Boolean = false) {
    			this.color = color;
    			this.alpha = alpha;
    			this.nalpha = nalpha;
    			this.inset = inset;
    			
    			this.timeStretch = timeStretch;
    			this.currentColor = color;
    			this.newColor = color;
    			nred = ((this.newColor >> 16) & 0xFF)/0xFF;
    			ngreen = ((this.newColor >> 8) & 0xFF)/0xFF;
    			nblue = (this.newColor & 0xff) / 0xFF;
    			
    			startTime = getTimer();
    		}
    		
    		public function switchColor(color:uint = 0x7F7F7F, neworigin:Vector3D=null):void
    		{
    			this.currentColor = this.newColor;
    			red = ((this.currentColor >> 16) & 0xFF)/0xFF;
    			green = ((this.currentColor >> 8) & 0xFF)/0xFF;
    			blue = (this.currentColor & 0xff)/0xFF;
    			
    			this.newColor = color;
    			nred = ((this.newColor >> 16) & 0xFF)/0xFF;
    			ngreen = ((this.newColor >> 8) & 0xFF)/0xFF;
    			nblue = (this.newColor & 0xff) / 0xFF;
    			
    			if (neworigin != null)
    			{
    				this.origin = neworigin;
    			}
    			startTime = getTimer() - 1;
    		}
    		
    		public function render():void
    		{
    			timedelta = (getTimer() - this.startTime) / this.timeStretch;
    		}
    		
    		private function setupProgram(object:Object3D):SwitchFillMaterialProgram {
    			var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX);
    			var positionVar:String = "aPosition";
    			var normalVar:String = "aNormal";
    			vertexLinker.declareVariable(positionVar, VariableType.ATTRIBUTE);
    			vertexLinker.declareVariable(normalVar, VariableType.ATTRIBUTE);
    			
    			var proc:Procedure = (this.inset) ? SwitchFillInsetProcedure : SwitchFillOutsetProcedure;
    			
    			proc.assignVariableName(VariableType.CONSTANT, 0, "cProjMatrix", 4);
    			if (object.transformProcedure != null) {
    				positionVar = appendPositionTransformProcedure(object.transformProcedure, vertexLinker);
    			}
    			//vertexLinker.addProcedure(_projectProcedure);
    			//vertexLinker.setInputParams(_projectProcedure, positionVar);
    			vertexLinker.addProcedure(proc);
    			vertexLinker.setInputParams(proc, positionVar);
    			vertexLinker.setInputParams(proc, normalVar);
    
    			var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT);
    			//fragmentLinker.addProcedure(outColorProcedure);
    			fragmentLinker.addProcedure(getSwitchFillFProcedure);
    			fragmentLinker.varyings = vertexLinker.varyings;
    			return new SwitchFillMaterialProgram(vertexLinker, fragmentLinker);
    		}
    
    		/**
    		 * @private 
    		 */
    		override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector., lightsLength:int, useShadow:Boolean, objectRenderPriority:int = -1):void {
    			var object:Object3D = surface.object;
    			// Strams
    			var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION);
    			var normalsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL);
    			
    			// Check validity
    			if (positionBuffer == null) return;
    			// Program
    
    			// Renew program cache for this context
    			if (camera.context3D != cachedContext3D) {
    				cachedContext3D = camera.context3D;
    				programsCache = caches[cachedContext3D];
    				if (programsCache == null) {
    					programsCache = new Dictionary();
    					caches[cachedContext3D] = programsCache;
    				}
    			}
    
    			var program:SwitchFillMaterialProgram = programsCache[object.transformProcedure];
    			if (program == null) {
    				program = setupProgram(object);
    				program.upload(camera.context3D);
    				programsCache[object.transformProcedure] = program;
    			}
    			// Drawcall
    			var drawUnit:DrawUnit = camera.renderer.createDrawUnit(object, program.program, geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program);
    		
    			// Streams
    			// a0 a1
    			drawUnit.setVertexBufferAt(program.aPosition, positionBuffer, geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]);
    			drawUnit.setVertexBufferAt(program.aNormal, normalsBuffer, geometry._attributesOffsets[VertexAttributes.NORMAL], VertexAttributes.FORMATS[VertexAttributes.NORMAL]);
    				
    			// Constants
    			object.setTransformConstants(drawUnit, surface, program.vertexShader, camera);
    			
    			drawUnit.setProjectionConstants(camera, program.cProjMatrix, object.localToCameraTransform); //c0
    			drawUnit.setVertexConstantsFromNumbers(program.cOrigin, origin.x, origin.y, origin.z, origin.w); //c4
    			drawUnit.setVertexConstantsFromNumbers(program.cFactors, factors.x, factors.y, factors.z, factors.w); //c5
    			drawUnit.setVertexConstantsFromNumbers(program.cMixed, timedelta, spread, -1, 2); //c6
    			drawUnit.setVertexConstantsFromNumbers(program.cMixed2, 3, 1, 0, 0); //c7
    			
    			//fragment constants
    			drawUnit.setFragmentConstantsFromNumbers(program.cCurrentColor, red, green, blue, alpha); //c0
    			drawUnit.setFragmentConstantsFromNumbers(program.cNewColor, nred, ngreen, nblue, nalpha); //c1
    			
    			// Send to render
    			if (alpha < 1) {
    				drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA;
    				drawUnit.blendDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA;
    				camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.TRANSPARENT_SORT);
    			} else {
    				camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.OPAQUE);
    			}
    		}
    
    		/**
    		 * @inheritDoc 
    		 */
    		override public function clone():Material {
    			var res:SwitchFillMaterial = new SwitchFillMaterial(color, alpha);
    			res.clonePropertiesFrom(this);
    			return res;
    		}
    
    	}
}
    
import alternativa.engine3d.materials.ShaderProgram;
import alternativa.engine3d.materials.compiler.Linker;
    
import flash.display3D.Context3D;
    
class SwitchFillMaterialProgram extends ShaderProgram {
    
    	public var aPosition:int = -1;
    	public var aNormal:int = -1;
    	public var cProjMatrix:int = -1;
    	public var cOrigin:int = -1;
    	public var cCurrentColor:int = -1;
    	public var cNewColor:int = -1;
    	public var cFactors:int = -1;
    	public var cMixed:int = -1;
    	public var cMixed2:int = -1;
    
    	public function SwitchFillMaterialProgram(vertex:Linker, fragment:Linker) {
    		super(vertex, fragment);
    	}
    
    	override public function upload(context3D:Context3D):void {
    		super.upload(context3D);
    
    		aPosition =  vertexShader.findVariable("aPosition");
    		aNormal = vertexShader.findVariable("aNormal");
    		cProjMatrix = vertexShader.findVariable("cProjMatrix");
    		cOrigin = vertexShader.findVariable("cOrigin");
    		cFactors = vertexShader.findVariable("cFactors");
    		cMixed = vertexShader.findVariable("cMixed");
    		cMixed2 = vertexShader.findVariable("cMixed2");
    		
    		cCurrentColor = fragmentShader.findVariable("cCurrentColor");
    		cNewColor = fragmentShader.findVariable("cNewColor");
    	}
}
    

And here is its usage

package 
{
    	import alternativa.engine3d.core.events.MouseEvent3D;
    	import alternativa.engine3d.core.RayIntersectionData;
    	import alternativa.engine3d.objects.Mesh;
    	import flash.geom.Matrix3D;
    
    	import alternativa.engine3d.primitives.Box;
    	import alternativa.engine3d.controllers.SimpleObjectController;
    	import alternativa.engine3d.core.Camera3D;
    	import alternativa.engine3d.core.Object3D;
    	import alternativa.engine3d.core.Resource;
    	import alternativa.engine3d.core.View;
    	
    	import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.events.MouseEvent;
    	import flash.geom.Vector3D;
    	import flash.display.Stage3D;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	
    	import alternativa.engine3d.materials.FillMaterial;
    	import alternativa.engine3d.materials.SwitchFillMaterial;
    	
    	import alternativa.engine3d.alternativa3d;
    	use namespace alternativa3d;
    	
    	/**
    	 * ...
    	 * @author David E Jones
    	 */
    	 
    	public class Main extends Sprite 
    	{
    		private var scene:Object3D = new Object3D();
    		private var camera:Camera3D;
    		private var controller:SimpleObjectController;
    		private var stage3D:Stage3D;
    		private var b:Box;
    		private var smat:SwitchFillMaterial;
    		
    		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);
    			
    			stage.align = StageAlign.TOP_LEFT;
    			stage.scaleMode = StageScaleMode.NO_SCALE;
    			
    			camera = new Camera3D(1, 10000);
    			camera.view = new View(stage.stageWidth, stage.stageHeight, false, 0x1d1d1d, 0, 4);
    			addChild(camera.view);
    			addChild(camera.diagram);
    
    			camera.x = -45;
    			camera.y = -40;
    			camera.z = 60;
    			controller = new SimpleObjectController(stage, camera, 200);
    			controller.lookAt(new Vector3D(0,0,0));
    			
    			scene.addChild(camera);
    			
    			stage3D = stage.stage3Ds[0];
    			stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
    			stage3D.requestContext3D();
    		}
    		
    		private function onContextCreate(e:Event):void {
    			stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
    			
    			setupSceneObjects();
    			
    			stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
    			stage.addEventListener(Event.RESIZE, onResize);
    			onResize();
    		}
    		
    		private function setupSceneObjects():void
    		{
    			smat = new SwitchFillMaterial(0xFF0000, 1, 40, false);
    			
    			b = new Box(50, 50, 10, 10, 10, 10, false, null);
    			b.setMaterialToAllSurfaces(smat);
    			uploadResources(b.getResources(true));
    			scene.addChild( b );
    			
    			b.addEventListener(MouseEvent3D.CLICK, onClick);
    		}
    		
    		private function onClick(e:MouseEvent3D):void
    		{		
    			var local_point:Vector3D = new Vector3D(e.localX, e.localY, e.localZ);
    			var col:uint = Math.random() * 0xFFFFFF;
    			smat.switchColor(col, local_point);
    		}
    
    		private function uploadResources(resources:Vector.):void {
    			for each (var resource:Resource in resources) {
    				resource.upload(stage3D.context3D);
    			}
    		}
    
    		private function onEnterFrame(e:Event):void 
    		{
    			controller.update();
    			smat.render();
    			camera.render(stage3D);
    		}
    		
    		private function onResize(e:Event = null):void {
    			camera.view.width = stage.stageWidth;
    			camera.view.height = stage.stageHeight;
    		}
    		
    	}
    	
}

You can download the example files here

Downloads

Comments

    Comments are currently closed