// forked from psyark's ヘタをつけます // forked from psyark's シェーディングを近づけます // forked from psyark's アングルを近づけます // forked from psyark's 背景を近づけます // forked from psyark's 色を近づけます // forked from psyark's 形を近づけます // forked from psyark's 隣にお手本を表示します // forked from psyark's flash on 2009-12-9 package { import flash.display.*; import flash.events.*; import flash.filters.*; import flash.geom.*; import flash.system.*; import flash.net.*; import flash.utils.*; [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; private var palette:Array; /** * コンストラクタ */ public function Test21() { var bg:BitmapData = new BitmapData(465, 465, false); bg.perlinNoise(250, 250, 1, 0, false, true, 7, true); bg.colorTransform(bg.rect, new ColorTransform(2.8, 2.8, 2.8, 1, -0x140, -0x150, -0x160)); addChild(new Bitmap(bg)); Security.loadPolicyFile("http://farm1.static.flickr.com/crossdomain.xml"); var loader:Loader = new Loader(); loader.load(new URLRequest("http://farm1.static.flickr.com/242/522495023_2a2423ecac.jpg"), new LoaderContext(true)); loader.x = 465; addChild(loader); // 描画対象となるShapeを作成し、表示リストに追加します。 viewport = new Shape(); viewport.x = 465 * 0.53; viewport.y = 465 * 0.55; 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(20, 100, 5, 0, true, true, 7, true); texture.colorTransform(texture.rect, new ColorTransform(0.1, 0.1, 0.1, 1, 0x66, 0x99, 0x00)); tmp1 = new BitmapData(512, 512, false); tmp2 = new BitmapData(512, 512, false); addEventListener(Event.ENTER_FRAME, enterFrameHandler); var paletteFactor:int = 1; palette = []; var spec:Number = 0.96; for (var i:int=0; i<0x100; i++) { var t:Number = i / 0xFF; var value:Number = Math.pow(t, 6) * 0x99; if (t > spec) { value += Math.pow((t - spec) / (1 - spec), 8) * 0x22; } palette[i] = gs(value); } function gs(v:int):int { return v << 16 | v << 8 | v; } } /** * 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 = 200; 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 * Math.SQRT2); 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); } } } var o:int = vertices.length / 3; for (var i:uint=0; i<=hDiv; i++) { for (var j:uint=0; j<=vDiv; j++) { var s2:Number = Math.PI * 2 * j / vDiv; vertices.push(Math.cos(s2) * 20, Math.sin(s2) * 20, i * -13); uvtData.push(i / hDiv, j / vDiv, 1); if (j < vDiv && i < hDiv) { var a:uint = i * (vDiv + 1) + j + o; var b:uint = (i + 1) * (vDiv + 1) + j + o; indices.push(b, a + 1, a, a + 1, b, b + 1); } } } o = vertices.length / 3; vertices.push(0, 0, i * -13); for (var j:uint=0; j<=vDiv; j++) { indices.push(o, o - j - 1, o - j - 2); } } /** * 法線マップを作成します */ 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.identity(); worldMatrix.appendRotation(-18, Vector3D.X_AXIS); worldMatrix.appendRotation(5, Vector3D.Y_AXIS); var r:Number = getTimer() * 0.0003; worldMatrix.appendRotation(Math.cos(r) * 10, Vector3D.X_AXIS); worldMatrix.appendRotation(Math.sin(r) * 10, Vector3D.Y_AXIS); } /** * 描画 */ private function render():void { // ライティング var light:Vector3D = new Vector3D(); light.x = 0; light.y = 0; 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)); tmp1.paletteMap(tmp1, tmp1.rect, tmp1.rect.topLeft, palette, [], []); tmp1.applyFilter(tmp1, tmp1.rect, tmp1.rect.topLeft, new BlurFilter(8, 8, 3)); // テクスチャと光量をハードライトブレンドして、ライティングが行われたテクスチャを得る tmp2.copyPixels(texture, texture.rect, texture.rect.topLeft); tmp2.draw(tmp1, null, null, BlendMode.HARDLIGHT); // 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 ]); } } } せっかくなので、回します