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
| package com.dafishinsea.tutorials.normalmap {
import com.bit101.components.*;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Shader;
import flash.display.ShaderJob;
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display.TriangleCulling;
import flash.events.Event;
import flash.filters.ShaderFilter;
import flash.geom.Matrix3D;
import flash.geom.PerspectiveProjection;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Utils3D;
import flash.geom.Vector3D;
import flash.utils.ByteArray;
import net.hires.debug.Stats;
[SWF(backgroundColor="0xffffff", width="800", height="600", frameRate="50")]
public class NormalMap3 extends Sprite {
private var texture:BitmapData = new BitmapData(500,500,false,0xFFFFFF);
private var textureBmp:Bitmap = new Bitmap(texture);
private var normalMap:BitmapData = new BitmapData(500,500,false,0xFFFFFF);
private var normalBmp:Bitmap = new Bitmap(normalMap);
private var normalMapShp:Shape = new Shape();
private var vertices:Vector.<Number>;
private var indices:Vector.<int>;
private var uvtData:Vector.<Number>;
private var perspective: PerspectiveProjection;
private var projectionMatrix : Matrix3D;
private var rotationMatrix : Matrix3D;
private var projectedVerts:Vector.<Number>;
private var focalLength:Number = 50;
private var container:Sprite;
private const PI:Number = Math.PI;//half revolution in radians
private const HALFPI:Number = Math.PI/2;//1/4 revolution in radians
private const TWOPI:Number = 2*Math.PI;//full revolution in radians
//x,yz rotation
private var rx:Number = 60;
private var ry:Number = 40;
private var rz:Number = 30;
private var testBmp:Bitmap;
private var r:Number = 0;//yrotation of sphere
private var normalMapShader:Shader;
private var shaderJob:ShaderJob;
private var input:ByteArray;
private var output:ByteArray;
private var normalMapRdo:PushButton;
private var textureMapRdo:PushButton;
private var noMapsRdo:PushButton;
[Embed(source="NormalMapShader.pbj", mimeType="application/octet-stream")]
private var NormalMapShader:Class;
public function NormalMap3() {
init();
trace("init");
}
private function init():void {
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
vertices = new Vector.<Number>();
indices = new Vector.<int>();
uvtData = new Vector.<Number>();
//set up perspective
perspective = new PerspectiveProjection();
perspective.fieldOfView = 50;
//3D transformation matrix - used to rotate object
projectionMatrix = perspective.toMatrix3D();
rotationMatrix = new Matrix3D();//used to keep track of rotation of the sphere
projectedVerts = new Vector.<Number>();
//container to hold scene
container = new Sprite();
container.x = stage.stageWidth/2;
container.y = stage.stageHeight/2;
addChild(container);
createSphere(100, 40, 80);
createNormalMap();
createShaderJob();
addEventListener(Event.ENTER_FRAME, onEnterFrame);
addEventListener(Event.RENDER, render, false, 0, true);
addChild(new Stats());
//texture map
//controls
normalMapRdo = new PushButton(this, 500, 30, "Show normal map", onNomalMapBtnClick);
textureMapRdo = new PushButton(this, 500, 50, "Show texture map", onTextureRdoClick);
noMapsRdo = new PushButton(this, 500, 10, "Show only sphere", onNoMapsRdoClick);
}
protected function onNomalMapBtnClick(event:Event):void
{
if(textureBmp.parent == this)
removeChild(textureBmp);
addChild(normalBmp);
}
protected function onTextureRdoClick(event:Event):void
{
if(normalBmp.parent == this)
removeChild(normalBmp);
addChild(textureBmp);
}
protected function onNoMapsRdoClick(event:Event):void
{
if(normalBmp.parent == this)
removeChild(normalBmp);
if(textureBmp.parent == this)
removeChild(textureBmp);
}
private function createSphere(radius:Number, rows:int, cols:int):void {
var lon_incr:Number = TWOPI/cols;
var lat_incr = PI/rows;
var lon:Number = 0;//angle of rotation around the y axis, *in radians*
var lat:Number = 0;//angle of rotation around the x axis
var x:Number, y:Number, z:Number;
var vnum:int = 0;
var ind:int = 0;
//a full rotation is PI radians
for(var h:int = 0; h <= rows; ++h)
{
lon = 0;
y = radius*Math.cos(lat);//need to shift angle downwards by 1/4 rev
for(var v:int = 0; v <= cols; ++v)
{
x = radius*Math.cos(lon)*Math.sin(lat);
z = radius*Math.sin(lon)*Math.sin(lat);//seen from above, z = y
//add vertex triplet
vertices[vnum] = x;
vertices[vnum+1] = y;
vertices[vnum+2] = z;
//uvts
uvtData[vnum] = v/cols;
uvtData[vnum+1] = h/rows;
uvtData[vnum+2] = 1;
vnum+=3;
//add indices
if(h < rows && v < cols){
indices.push(ind, ind+1, ind + cols+1);
indices.push(ind + cols+1, ind+1, ind + cols + 2);
}
ind+=1;
lon += lon_incr;
}
lat += lat_incr;
}
}
/**
* create ShaderJob for use later
*/
private function createShaderJob():void
{
normalMapShader = new Shader(new NormalMapShader());
normalMapShader.data.src.width = normalMap.width;
normalMapShader.data.src.height = normalMap.height;
normalMapShader.data.src.input = normalMap;
shaderJob = new ShaderJob(normalMapShader, texture, width, height);
shaderJob.addEventListener(Event.COMPLETE, shaderJobCompleteHandler);
shaderJob.start();
}
/**
* shade job complete
*/
private function shaderJobCompleteHandler(event:Event):void
{
//update params and restart job
var lightDir:Vector3D = rotationMatrix.transformVector(new Vector3D(1,1,1));
lightDir.normalize();
normalMapShader.data.xr.value = [lightDir.x];
normalMapShader.data.yr.value = [lightDir.y];
normalMapShader.data.zr.value = [lightDir.z];
shaderJob = new ShaderJob(normalMapShader, texture, width, height);
shaderJob.addEventListener(Event.COMPLETE, shaderJobCompleteHandler);
shaderJob.start();
}
/**
* create normal map from geometry to reuse for lighting
*/
private function createNormalMap():void
{
for(var i:int = 0; i < indices.length; i+=3){
//get the 3 points of the triangle as Vector3D objects
var pt1:Vector3D = new Vector3D(vertices[indices[i]*3],
vertices[indices[i]*3+1],
vertices[indices[i]*3+2]);
var pt2:Vector3D = new Vector3D(vertices[indices[i+1]*3],
vertices[indices[i+1]*3+1],
vertices[indices[i+1]*3+2]);
var pt3:Vector3D = new Vector3D(vertices[indices[i+2]*3],
vertices[indices[i+2]*3+1],
vertices[indices[i+2]*3+2]);
//subtract the first two from the third
var d1:Vector3D = pt3.subtract(pt1);
var d2:Vector3D = pt3.subtract(pt2);
//get the cross-product of the results to get the normal
var normal:Vector3D = d1.crossProduct(d2);
normal.normalize();
var r:uint = 255*(normal.x + 1)/2;
var g:uint = 255*(normal.y + 1)/2;
var b:uint = 255*(normal.z + 1)/2;
var normalColor:uint = r << 16 | g << 8 | b;
normalMapShp.graphics.beginFill(normalColor);
var p1:Point = new Point(uvtData[indices[i]*3]*normalMap.width, uvtData[indices[i]*3+1]*normalMap.height);
var p2:Point = new Point(uvtData[indices[i+1]*3]*normalMap.width, uvtData[indices[i+1]*3+1]*normalMap.height);
var p3:Point = new Point(uvtData[indices[i+2]*3]*normalMap.width, uvtData[indices[i+2]*3+1]*normalMap.height);
normalMapShp.graphics.moveTo(p1.x, p1.y);
normalMapShp.graphics.lineTo(p2.x, p2.y);
normalMapShp.graphics.lineTo(p3.x, p3.y);
normalMapShp.graphics.lineTo(p1.x, p1.y);
normalMapShp.graphics.endFill();
}
normalMap.draw(normalMapShp);
}
/**
* save the normal map -- so I can play with it in PixelBender Toolkit ;)
*/
private function onSaveBtnClick(click:Event):void
{
var savingBmp:SavingBitmap = new SavingBitmap(normalMap);
savingBmp.encoderType = "png";
savingBmp.save("normalMap.png");
}
private function onEnterFrame(event:Event):void {
//createSphere()
update();
}
private function update():void {
r+=0.5;
rotationMatrix = new Matrix3D();
rotationMatrix.prependRotation(-r, new Vector3D(0,1,0))
projectionMatrix = perspective.toMatrix3D();
projectionMatrix.prependTranslation(0.0,0.0,250);
projectionMatrix.prependRotation(r, new Vector3D(0,1,0))
stage.invalidate();//trigger render event
}
private function render(e:Event):void {
container.graphics.clear();
Utils3D.projectVectors(projectionMatrix, vertices, projectedVerts, uvtData);
container.graphics.beginBitmapFill(texture,null, false, false);
container.graphics.drawTriangles(projectedVerts, indices, uvtData, TriangleCulling.POSITIVE);
container.graphics.endFill();
}
}
}
|