MOTION BLUR DEMO 001 (PV3D)
東京てら子7 『夏休みの自由研究発表』の発表その1
http://atnd.org/events/6936
PV3Dで(モーション)ブラーをかけられるかを試したものです
秒間でfps x ブラーステップ数分のレンダリングをかけているのでおよそ実用的じゃないです。
参考:PV3D演出サンプルNo.08:カスタムフラットシェーディング
http://clockmaker.jp/blog/2009/11/papervision3d-flatshader/
ほとんど上記のソースをパクっただけです。
ブラー効果で意味があるのはloop内。
/**
* Copyright fumix ( http://wonderfl.net/user/fumix )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/mRun
*/
/*
東京てら子7 『夏休みの自由研究発表』の発表その1
http://atnd.org/events/6936
PV3Dで(モーション)ブラーをかけられるかを試したものです
秒間でfps x ブラーステップ数分のレンダリングをかけているのでおよそ実用的じゃないです。
参考:PV3D演出サンプルNo.08:カスタムフラットシェーディング
http://clockmaker.jp/blog/2009/11/papervision3d-flatshader/
ほとんど上記のソースをパクっただけです。
ブラー効果で意味があるのはloop内。
*/
package {
import flash.system.LoaderContext;
import flash.net.URLRequest;
import net.hires.debug.Stats;
import com.bit101.components.HUISlider;
import com.bit101.components.Label;
import com.bit101.components.PushButton;
import com.bit101.components.RadioButton;
import org.papervision3d.core.math.Number3D;
import org.papervision3d.materials.BitmapMaterial;
import org.papervision3d.materials.utils.MaterialsList;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.primitives.Cube;
import org.papervision3d.view.BasicView;
import flash.display.*;
import flash.events.*;
import flash.filters.*;
import flash.geom.*;
[SWF(backgroundColor="#FFFFFF", frameRate="15", width="465", height="465")]
public class MotionBlurTest extends BasicView {
static public const RENDERING_STEP : int = 32; // cache step
static public const CUBE_NUM : int = 3; // amount of cube
static public const CUBE_LENGTH : int = 650; // size of cube
static public const DARK_RANGE : int = -256; // color minus range
private static const IMAGE_URL : String = "http://assets.wonderfl.net/images/related_images/1/1a/1ab8/1ab8f862d947f2e40bde530585397949208c61d5";
private var bmpSteps : Vector.<BitmapData>;
private var cubes : Vector.<Cube>;
//motion blur用
private var _view : BitmapData;
private var _colorTransBlur : ColorTransform;
private var _colorTrans : ColorTransform;
//stats
private var _stats : Stats;
//UI
private var _slider : HUISlider;
private var _radio1 : RadioButton;
private var _radio2 : RadioButton;
private var _radio3 : RadioButton;
private var _radio4 : RadioButton;
private var _blurCount : int = 1;
private var _radio5 : RadioButton;
public function MotionBlurTest() {
// flash init
stage.quality = StageQuality.MEDIUM;
//BG設置
var bg : Shape = new Shape();
addChildAt(bg, 0);
var gradType : String = GradientType.LINEAR;
var gradColors : Array = [0xFFFFFF , 0xE8E8E8];
var gradAlphas : Array = [1, 1];
var gradRadios : Array = [0, 255];
var gradMrx : Matrix = new Matrix();
gradMrx.createGradientBox(stage.stageWidth, stage.stageHeight, Math.PI / 2, 0, 0);
var gradSpread : String = SpreadMethod.PAD;
bg.graphics.beginGradientFill(gradType, gradColors, gradAlphas, gradRadios, gradMrx, gradSpread);
bg.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
//view設置
_view = new BitmapData(stage.stageWidth, stage.stageHeight);
addChild(new Bitmap(_view));
//ColorTransform設定
_colorTransBlur = new ColorTransform();
_colorTrans = new ColorTransform(1, 1, 1, 0.0);
//UIまわり設定
var la : Label = new Label(this, 8, 4, "MOTION BLUR DEMO");
la.scaleX = la.scaleY = 2;
new Label(this, 10, 30, "PLASE DRAG STAGE");
_slider = new HUISlider(this, 8, 45, 'speed');
_slider.maximum = 20;
_slider.minimum = -20;
new Label(this, 8, 60, " blur");
_radio1 = new RadioButton(this, 40, 65, '1', true, onRadioButton);
_radio2 = new RadioButton(this, 65, 65, '2', false, onRadioButton);
_radio3 = new RadioButton(this, 90, 65, '4', false, onRadioButton);
_radio4 = new RadioButton(this, 115, 65, '8', false, onRadioButton);
_radio5 = new RadioButton(this, 140, 65, '16', false, onRadioButton);
new PushButton(this,8,80,'stop',onPushButton);
//stats
_stats = new Stats();
_stats.x = stage.stageWidth - 80;
_stats.y = 8;
addChild(_stats);
//画像の読み込み
var req : URLRequest = new URLRequest(IMAGE_URL);
var loader : Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplate);
loader.load(req, new LoaderContext(true));
}
/**
* 画像ダウンロード完了
* @param event
*/
private function loadComplate(event : Event) : void {
event.target.removeEventListener(Event.COMPLETE, loadComplate);
// pv3d init
var cameraTarget : DisplayObject3D = DisplayObject3D.ZERO;
cameraTarget.y = 500;
camera.target = cameraTarget;
// create bitmap cache to Array(Vector)
var bmp : Bitmap = event.target.content as Bitmap;
bmpSteps = new Vector.<BitmapData>(RENDERING_STEP, true);
var dest : Point = new Point(0, 0);
for (var i : int = 0;i < RENDERING_STEP;i++) {
var cm : ColorMatrixFilter = new ColorMatrixFilter([1, 0, 0, 0, DARK_RANGE * i / RENDERING_STEP,
0, 1, 0, 0, DARK_RANGE * i / RENDERING_STEP,
0, 0, 1, 0, DARK_RANGE * i / RENDERING_STEP,
0, 0, 0, 1, 0]);
var bmpData : BitmapData = new BitmapData(bmp.width, bmp.height);
bmpData.applyFilter(bmp.bitmapData, bmp.bitmapData.rect, dest, cm);
bmpSteps[i] = bmpData;
}
bmpSteps.fixed = true;
// craete cubes
cubes = new Vector.<Cube>(CUBE_NUM, true);
for (i = 0;i < CUBE_NUM;i++) {
// separate all material for update
var ml : MaterialsList = new MaterialsList({
front : new BitmapMaterial(bmpSteps[0], true), back : new BitmapMaterial(bmpSteps[0], true), top : new BitmapMaterial(bmpSteps[0], true), bottom : new BitmapMaterial(bmpSteps[0], true), right : new BitmapMaterial(bmpSteps[0], true), left : new BitmapMaterial(bmpSteps[0], true)
});
var cube : Cube = new Cube(ml, CUBE_LENGTH, CUBE_LENGTH, CUBE_LENGTH, 1, 1, 1);
scene.addChild(cube);
// Check Object Hit
var isHit : Boolean = true;
while (isHit) {
// random position and rotation
cube.x = 4000 * (Math.random() - 0.5);
cube.y = 900 * Math.random() + 300;
cube.z = 4000 * (Math.random() - 0.5);
cube.rotationX = 360 * Math.random();
cube.rotationY = 360 * Math.random();
cube.rotationZ = 360 * Math.random();
// check no hit
// for no using QuadrantRenderEngine
isHit = false;
for (var j : int = 0;j < cubes.length;j++) {
if (cubes[j]) {
if (cube.hitTestObject(cubes[j])) {
isHit = true;
}
}
}
}
cubes[i] = cube;
}
cubes.fixed = true;
// render and loop
addEventListener(Event.ENTER_FRAME, loop);
// mouse interactive
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
viewport.visible = false;
}
private function loop(e : Event) : void {
onRotation();
//前フレームのカメラの位置を保存
easePitchOld = easePitch;
easeYawOld = easeYaw;
//現在のカメラ位置を計算
easePitch += (cameraPitch - easePitch) * 0.2;
easeYaw += (cameraYaw - easeYaw) * 0.2;
//(現在のカメラの位置 - 前フレームのカメラの位置) / ブラーのステップ数
easePitchDiff = (easePitch - easePitchOld) / _blurCount;
easeYawDiff = (easeYaw - easeYawOld) / _blurCount;
// update flat shading
var tmp : DisplayObject3D = new DisplayObject3D();
var m : BitmapMaterial;
var cube : Cube;
_view.lock();
_view.colorTransform(_view.rect, _colorTrans);
//ブラーのステップ毎にカメラを少しずつ移動させてレンダリング
for(var j : int = 1;j <= _blurCount;j++) {
camera.x = 3800 * Math.sin((easeYawOld + easeYawDiff * j) * Number3D.toRADIANS);
camera.z = 3800 * Math.cos((easeYawOld + easeYawDiff * j) * Number3D.toRADIANS);
camera.y = 80 * (easePitchOld + easePitchDiff * j);
flatShade(tmp, m, cube);
singleRender();
_colorTransBlur.alphaOffset = -255*(_blurCount - j) / _blurCount;
_view.draw(viewport, null, _colorTransBlur);
}
_view.unlock();
}
private function flatShade(tmp : DisplayObject3D,m : BitmapMaterial,cube : Cube) : void {
for (var i : int = 0;i < cubes.length;i++) {
cube = cubes[i];
// front face
tmp.copyTransform(cube);
tmp.moveForward(CUBE_LENGTH / 2);
m = cube.getMaterialByName("front") as BitmapMaterial;
updateFlatShade(m, tmp);
// back face
tmp.copyTransform(cube);
tmp.yaw(180);
tmp.moveForward(CUBE_LENGTH / 2);
m = cube.getMaterialByName("back") as BitmapMaterial;
updateFlatShade(m, tmp);
// left face
tmp.copyTransform(cube);
tmp.yaw(-90);
tmp.moveForward(CUBE_LENGTH / 2);
m = cube.getMaterialByName("left") as BitmapMaterial;
updateFlatShade(m, tmp);
// right face
tmp.copyTransform(cube);
tmp.yaw(90);
tmp.moveForward(CUBE_LENGTH / 2);
m = cube.getMaterialByName("right") as BitmapMaterial;
updateFlatShade(m, tmp);
// top face
tmp.copyTransform(cube);
tmp.pitch(-90);
tmp.moveForward(CUBE_LENGTH / 2);
m = cube.getMaterialByName("top") as BitmapMaterial;
updateFlatShade(m, tmp);
// bottom face
tmp.copyTransform(cube);
tmp.pitch(90);
tmp.moveForward(CUBE_LENGTH / 2);
m = cube.getMaterialByName("bottom") as BitmapMaterial;
updateFlatShade(m, tmp);
}
}
/**
* Update Custom Flat Shading
* @param material
* @param obj
*/
private function updateFlatShade(material : BitmapMaterial, obj : DisplayObject3D) : void {
// calc angle
var radian : Number = getDirectionRadian(obj, camera);
// calc distance
var distance : Number = obj.distanceTo(camera);
var step : uint = Math.round(Math.abs(radian) / Math.PI * 2 * (RENDERING_STEP - 1));
if (step > RENDERING_STEP - 1) step = RENDERING_STEP - 1;
else if (step < 0) step = 0;
material.bitmap = bmpSteps[step];
}
/**
* calc radian of two object's z-axis
* @param obj
* @param target
* @return
*/
private function getDirectionRadian(obj : DisplayObject3D, target : DisplayObject3D) : Number {
// obj's Z axis vector
var zAxis : Number3D = new Number3D(obj.transform.n13, obj.transform.n23, obj.transform.n33);
zAxis.normalize();
// calc target's direction
var dummyObj : DisplayObject3D = new DisplayObject3D();
dummyObj.copyTransform(obj.transform);
dummyObj.lookAt(target);
// target's Z axis vector
var targetZAxis : Number3D = new Number3D(dummyObj.transform.n13, dummyObj.transform.n23, dummyObj.transform.n33);
// calc radian of two vector
var rot : Number = Math.acos(Number3D.dot(zAxis, targetZAxis));
return rot;
}
// ----------------------------------------------
// Mouse Interactive
// ----------------------------------------------
private var isOribiting : Boolean;
private var cameraPitch : Number = 30;
private var cameraYaw : Number = 90;
private var previousMouseX : Number;
private var previousMouseY : Number;
private var easePitch : Number = 270;
private var easeYaw : Number = 90;
private var easePitchOld : Number = 270;
private var easeYawOld : Number = 90;
private var easePitchDiff : Number;
private var easeYawDiff : Number;
private function onMouseDown(event : MouseEvent) : void {
isOribiting = true;
previousMouseX = event.stageX;
previousMouseY = event.stageY;
singleRender();
}
private function onMouseUp(event : MouseEvent) : void {
isOribiting = false;
}
private function onMouseMove(event : MouseEvent) : void {
var differenceY : Number = event.stageY - previousMouseY;
if(isOribiting) {
cameraPitch += differenceY * 0.25;
cameraPitch = Math.max(5, Math.min(cameraPitch, 360));
previousMouseY = event.stageY;
}
}
private function onRadioButton(e : MouseEvent) : void {
var radio : RadioButton = e.currentTarget as RadioButton;
_blurCount = int(radio.label);
}
private function onRotation() : void {
cameraYaw += _slider.value;
//if(cameraYaw > 360) cameraYaw -= 360;
}
private function onPushButton(e : MouseEvent) : void {
var button : PushButton = e.currentTarget as PushButton;
if(button.label == 'stop') {
button.label = 'play';
removeEventListener(Event.ENTER_FRAME, loop);
}else{
button.label = 'stop';
addEventListener(Event.ENTER_FRAME, loop);
}
}
}
}