flash on 2009-12-9
このクラスでは、法線マップから簡単なライティングを行います。
/**
* Copyright psyark ( http://wonderfl.net/user/psyark )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/dPq0
*/
package {
import flash.display.*;
import flash.events.*;
import flash.filters.*;
import flash.geom.*;
[SWF(width=465,height=465,frameRate=60,backgroundColor=0x000000)]
/**
* このクラスでは、法線マップから簡単なライティングを行います。
*/
public class Test21 extends Sprite {
private var viewport:Shape;
private var vertices:Vector.<Number>;
private var uvtData:Vector.<Number>;
private var indices:Vector.<int>;
private var worldMatrix:Matrix3D;
private var viewMatrix:Matrix3D;
private var projection:PerspectiveProjection;
private var texture:BitmapData;
private var normalMap:BitmapData;
// 一時計算用ビットマップ
private var tmp1:BitmapData;
private var tmp2:BitmapData;
/**
* コンストラクタ
*/
public function Test21() {
// 描画対象となるShapeを作成し、表示リストに追加します。
viewport = new Shape();
viewport.x = stage.stageWidth * 0.5;
viewport.y = stage.stageHeight * 0.5;
addChild(viewport);
createMesh();
createNormalMap();
worldMatrix = new Matrix3D();
viewMatrix = new Matrix3D();
viewMatrix.appendTranslation(0, 0, 800);
projection = new PerspectiveProjection();
projection.fieldOfView = 60;
texture = new BitmapData(512, 512, false);
texture.perlinNoise(100, 100, 5, 0, true, true, 7);
tmp1 = new BitmapData(512, 512, false);
tmp2 = new BitmapData(512, 512, false);
addEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
/**
* 3D形状を作成します
*/
private function createMesh():void {
vertices = new Vector.<Number>();
uvtData = new Vector.<Number>();
indices = new Vector.<int>();
var hDiv:int = 32;
var vDiv:int = 16;
var hRadius:Number = 200;
var vRadius:Number = 100;
for (var i:uint=0; i<=hDiv; i++) {
var s1:Number = Math.PI * 2 * i / hDiv;
for (var j:uint=0; j<=vDiv; j++) {
var s2:Number = Math.PI * 2 * j / vDiv;
var r:Number = Math.cos(s2) * vRadius + hRadius;
vertices.push(Math.cos(s1) * r, Math.sin(s1) * r, Math.sin(s2) * vRadius);
uvtData.push(i / hDiv, j / vDiv, 1);
if (j < vDiv && i < hDiv) {
var a:uint = i * (vDiv + 1) + j;
var b:uint = (i + 1) * (vDiv + 1) + j;
indices.push(b, a + 1, a, a + 1, b, b + 1);
}
}
}
}
/**
* 法線マップを作成します
*/
private function createNormalMap():void {
normalMap = new BitmapData(512, 512, false, 0);
var mtx:Matrix3D = new Matrix3D();
for (var x:uint=0; x<normalMap.width; x++) {
for (var y:uint=0; y<normalMap.height; y++) {
mtx.identity();
mtx.appendRotation(y / normalMap.height * -360, Vector3D.Y_AXIS);
mtx.appendRotation(x / normalMap.width * +360, Vector3D.Z_AXIS);
var normal:Vector3D = mtx.transformVector(Vector3D.X_AXIS);
var color:uint = (normal.x / 2 + 0.5) * 0xFF << 16 |
(normal.y / 2 + 0.5) * 0xFF << 8 |
(normal.z / 2 + 0.5) * 0xFF;
normalMap.setPixel(x, y, color);
}
}
}
/**
* フレームごとの処理
*/
private function enterFrameHandler(event:Event):void {
update();
render();
}
/**
* 更新
*/
private function update():void {
worldMatrix.appendRotation(0.27, Vector3D.X_AXIS);
worldMatrix.appendRotation(0.61, Vector3D.Y_AXIS);
}
/**
* 描画
*/
private function render():void {
// ライティング
var light:Vector3D = new Vector3D();
light.x = viewport.mouseX / (465 / 2) / Math.SQRT2;
light.y = viewport.mouseY / (465 / 2) / Math.SQRT2;
light.z = -Math.sqrt(1 - light.lengthSquared);
var invertWorld:Matrix3D = worldMatrix.clone();
invertWorld.invert();
light = invertWorld.transformVector(light);
// 法線マップと光線ベクトルを掛けて光量を得る
tmp1.applyFilter(normalMap, normalMap.rect, new Point(), createLightingFilter(light));
// テクスチャと光量を掛けて、ライティングが行われたテクスチャを得る
tmp2.copyPixels(texture, texture.rect, texture.rect.topLeft);
tmp2.draw(tmp1, null, null, BlendMode.MULTIPLY);
// World行列、View行列、Projection行列を結合して一つの行列にする
var m:Matrix3D = new Matrix3D();
m.append(worldMatrix);
m.append(viewMatrix);
m.append(projection.toMatrix3D());
// 上記の行列を使って頂点座標を投影する
var projected:Vector.<Number> = new Vector.<Number>();
Utils3D.projectVectors(m, vertices, projected, uvtData);
viewport.graphics.clear();
viewport.graphics.beginBitmapFill(tmp2, null, false, true);
viewport.graphics.drawTriangles(projected, getSortedIndices(), uvtData, TriangleCulling.POSITIVE);
viewport.graphics.endFill();
}
/**
* Zソートされたインデックスを返す
*/
private function getSortedIndices():Vector.<int> {
var triangles:Array = [];
for (var i:int=0; i<indices.length; i+=3) {
var i1:int = indices[i+0];
var i2:int = indices[i+1];
var i3:int = indices[i+2];
var z:Number = Math.min(uvtData[i1 * 3 + 2], uvtData[i2 * 3 + 2], uvtData[i3 * 3 + 2]);
if (z > 0) {
triangles.push({i1:i1, i2:i2, i3:i3, z:z});
}
}
triangles = triangles.sortOn("z", Array.NUMERIC);
var sortedIndices:Vector.<int> = new Vector.<int>(0, false);
for each (var triangle:Object in triangles) {
sortedIndices.push(triangle.i1, triangle.i2, triangle.i3);
}
return sortedIndices;
}
/**
* 光の方向ベクトルからライティング用フィルタを作成
*/
private function createLightingFilter(light:Vector3D):ColorMatrixFilter {
return new ColorMatrixFilter([
2 * light.x, 2 * light.y, 2 * light.z, 0, (light.x + light.y + light.z) * -0xFF,
2 * light.x, 2 * light.y, 2 * light.z, 0, (light.x + light.y + light.z) * -0xFF,
2 * light.x, 2 * light.y, 2 * light.z, 0, (light.x + light.y + light.z) * -0xFF,
0, 0, 0, 1, 0
]);
}
}
}