switching colour alternativa3d
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 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;
}
}
}
Comments
Comments are currently closed