Away3D Shader (SwitchFillColor)

Here it is, my first AGAL shader for Away3D 4.1.4! Actually it’s a porting of this Flare3D Shader done by Jonas Volger.

All started when i saw the Nissan Juke websiste, if you try to change the color of the car you will see a really cool effect of a wave filling the car with the new color.

So i looked for a way to reproduce the effect and i found only two articles speaking about it, one about Flare3D and one about Alternativa3D, but no one about Away3D. After 2 days, one to learn how Away3D handle materials under the hood and one to write the code, i completed the porting of the shader to Away3D 4.1.4!

For a technical description of how it works i suggest you to look at Volger Bloog.

Now the code:

SwitchFillColorMaterial.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package it.partner.away3d.materials
{
import flash.geom.Vector3D;

import away3d.materials.MaterialBase;

import it.partner.away3d.materials.passes.SwitchFillColorPass;

public class SwitchFillColorMaterial extends MaterialBase
{
private var sfcPass : SwitchFillColorPass;

public function SwitchFillColorMaterial()
{
sfcPass = new SwitchFillColorPass();
addPass(sfcPass);
}

public function switchColor(origin:Vector3D, color:uint, alpha:Number=1.0):void
{
sfcPass.switchColor(origin, color, alpha);
}
}
}

SwitchFillColorPass.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
package it.partner.away3d.materials.passes
{
import flash.display3D.Context3D;
import flash.display3D.Context3DProgramType;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
import flash.utils.getTimer;

import away3d.arcane;
import away3d.cameras.Camera3D;
import away3d.core.base.IRenderable;
import away3d.core.managers.Stage3DProxy;
import away3d.materials.passes.MaterialPassBase;

use namespace arcane;

public class SwitchFillColorPass extends MaterialPassBase
{

private var _fData : Vector.<Number> = new Vector.<Number>(8);
private var _vData : Vector.<Number> = new Vector.<Number>(12);

private var _matrix : Matrix3D = new Matrix3D();

private var _startTime : Number = 0;
private var _timeStretch : Number;
private var _spread : Number;


private var _origin : Vector3D = new Vector3D(0,0,0,1);

private var _currentColor : uint = 0xffffff;
private var _curColR : Number = 1, _curColG : Number = 1, _curColB : Number = 1, _curColA : Number = 1;

private var _newColor : uint = 0xffffff;
private var _newColR : Number = 1, _newColG : Number = 1, _newColB : Number = 1, _newColA : Number = 1;

public function SwitchFillColorPass(timeStretch:Number = 40, spread:Number=6)
{
super(renderToTexture);

// tell the material how many resources are used so it will know which to clear when switching passes.
_numUsedStreams = 2; // vertex position and normals
_numUsedVertexConstants = 7;

this.timeStretch = timeStretch;
this.spread = spread;

//origin = _origin;

currentColor = _currentColor;
newColor = _newColor;

spread = _spread;
_vData[6] = -1;
_vData[7] = 2;

_vData[8] = 3;
_vData[9] = 1;
_vData[10] = 0;
_vData[11] = 0;

_startTime = getTimer();
}

public function get spread():Number
{
return _spread;
}

public function set spread(value:Number):void
{
_spread = value;
_vData[5] = _spread;
}

public function get timeStretch():Number
{
return _timeStretch;
}

public function set timeStretch(value:Number):void
{
_timeStretch = value;
}

public function get newAlpha():Number
{
return _newColA;
}

public function set newAlpha(value:Number):void
{
_newColA = value;
_fData[7] =_newColA;
}

public function get currAlpha():Number
{
return _curColA;
}

public function set currAlpha(value:Number):void
{
_curColA = value;
_fData[3] =_curColA;
}

public function get origin():Vector3D
{
return _origin;
}

public function set origin(value:Vector3D):void
{
_origin = value;
_vData[0] = _origin.x;
_vData[1] = _origin.y;
_vData[2] = _origin.z;
_vData[3] = _origin.w;
}

public function get newColor():uint
{
return _newColor;
}

public function set newColor(value:uint):void
{
_newColor = value;

_fData[4] =_newColR = ((_newColor >> 16) & 0xff)/0xff;
_fData[5] =_newColG = ((_newColor >> 8) & 0xff)/0xff;
_fData[6] =_newColB = (_newColor & 0xff)/0xff;

}

public function get currentColor():uint
{
return _currentColor;
}

public function set currentColor(value:uint):void
{
_currentColor = value;

_fData[0] = _curColR = ((_currentColor >> 16) & 0xff)/0xff;
_fData[1] =_curColG = ((_currentColor >> 8) & 0xff)/0xff;
_fData[2] =_curColB = (_currentColor & 0xff)/0xff;

}

public function switchColor(origin:Vector3D, color:uint, alpha:Number=1.0):void
{
// set previous new color to the current one
currentColor = _newColor;
currAlpha = newAlpha;

// set new color and origin of transformation
newColor = color;
newAlpha = alpha;

this.origin = origin;

// save starttime, to avoid subtraction to zero in renderEvent, decrease about 1
_startTime = getTimer() - 1;
}

/**
* Sets the render state which is constant for this pass
* @param stage3DProxy The stage3DProxy used for the current render pass
* @param camera The camera currently used for rendering
*/
override arcane function activate(stage3DProxy : Stage3DProxy, camera : Camera3D) : void
{
super.activate(stage3DProxy, camera);

_vData[4] = (getTimer() - _startTime) / _timeStretch;

stage3DProxy._context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, _vData, 3);
stage3DProxy._context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, _fData, 2);
}

/**
* Set render state for the current renderable and draw the triangles.
* @param renderable The renderable that needs to be drawn.
* @param stage3DProxy The stage3DProxy used for the current render pass.
* @param camera The camera currently used for rendering.
* @param viewProjection The matrix that transforms world space to screen space.
*/
override arcane function render(renderable : IRenderable, stage3DProxy : Stage3DProxy, camera : Camera3D, viewProjection : Matrix3D) : void
{
var context : Context3D = stage3DProxy._context3D;

// calculate model-view-projection matrix for the current renderable
_matrix.copyFrom(renderable.sceneTransform);
_matrix.append(viewProjection);

renderable.activateVertexBuffer(0, stage3DProxy);
renderable.activateVertexNormalBuffer(1, stage3DProxy);

//renderable.
context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _matrix, true);
context.drawTriangles(renderable.getIndexBuffer(stage3DProxy), 0, renderable.numTriangles);
}

/**
* Get the vertex shader code for this shader
*/
override arcane function getVertexCode() : String
{

// calculate distance between current position and distance to color change origin
// distance = length(iwposition.xyz - origin.xyz);
return "sub vt1 va0.xyz vc4.xyz\n" +
"dp3 vt1 vt1 vt1\n" +
"sqt vt1 vt1\n" +

// take position of current vertex and multiply it with the world
// iwposition = float4 (position,1) * world;
"mul vt0 va0 vc0\n" +

// adjust for aesthetics
// distance /= 3;
"div vt1 vt1 vc6.x\n" +

// move factors into v2
//"mov v2 c5\n" +

// calculate factors of current colors
// f1 = saturate((distance - timedelta) / spread);
// f2 = saturate(-((distance - timedelta) - spread) / spread);
//f1
"sub vt2 vt1 vc5.x\n" + // distance - timedelta
"div vt2 vt2 vc5.y\n" + // / spread
"sat vt2 vt2\n" + //saturate
"mov v2.x vt2\n" + // factors.x
//f2
"sub vt3 vt1 vc5.x\n" + // distance - timedelta
"sub vt3 vt3 vc5.y\n" + // -spread
"neg vt3 vt3\n" + // multiply by -1 to get -((distance - timedelta) - spread)
"div vt3 vt3 vc5.y\n" + // / spread
"sat vt3 vt3\n" + // saturate
"mov v2.y vt3\n" + // factors.y

//need to fill other v2 components
"mov v2.z vc6.z\n" +
"mov v2.w vc6.w\n" +

//mynormal = float4 (normal * world,1) * -sin(f1 * f2 / 2);
"mul vt4 va1 vc0\n" + // normal * world
"mul vt5 vt2 vt3\n" + // f1 * f2
"div vt5 vt5 vc5.w\n" + // /2
"sin vt5 vt5\n" + // sin
"neg vt5, vt5\n" + // neg to get -sin
"mul vt6 vt4 vt5\n" + // multiply

//iwposition = float4 (position,1) + mynormal;
"add vt0 va0 vt5\n" +

// iwposition * worldViewProj;
"m44 op vt0 vc0";
}

/**
* Get the fragment shader code for this shader
* @param fragmentAnimatorCode Any additional fragment animation code imposed by the framework, used by some animators. Ignore this for now, since we're not using them.
*/
override arcane function getFragmentCode(fragmentAnimatorCode : String) : String
{
//color = factors.x * currentcolor;
return "mul ft0 v2.x fc0\n" +
//color += factors.y * newcolor;
"mul ft1 v2.y fc1\n" +
"add ft0, ft0, ft1\n" +
//output
"mov oc, ft0";
}
}
}

This type of material use it’s own pass and do not consider all the other methods and passes so it’ll not be influenced by lights, texture, etc…

Maybe in the future i’ll make SwitchFillColorMethod, i just need to figure out how the hell Away3D’s methods works.