PV3D2.1: レンズフレア

2009年10月27日
Papervision3Dでウォークスルーの実験中。
walkthrough5.jpg
レンズフレアエフェクトをつけてみました。
海面のウネウネはなんか失敗。キラキラさせたかったんだけど・・・

太陽の部分は3dsMaxのレンズエフェクトでレンダリングしたもので、
画面全体にかかる光の輪はFlashの放射グラデーションです。
レンズフレアの仕組みはよくわかってないんだけど、
光の輪が複数個あって、それが画面中央の座標と光源を結ぶ直線上に並んでるって
イメージはあったのでそんな感じで表現してます。

太陽のスクリーン座標

3D空間内にあるオブジェクトの基点の2D画面上での位置は
DisplayObject3D.screenで調べられるのでそれを使ってます。
	sun = new DisplayObject3D();
	scene.addChild(sun);
	sun.autoCalcScreenCoords = true;
	sun.position = new Number3D(100000, 20000, 0);
まず太陽の位置を知るためにダミーのDisplayObject3Dを生成して、シーンに追加しておきます。
autoCalcScreenCoords=trueしているのは
シーンがレンダリングされる度に自動でスクリーン座標を計算させる為です。

後は毎フレームスクリーン座標を調べてその位置に太陽の画像を移動させるだけ。
毎フレーム実行しているコードはこんな感じ。
	//sunMc, ring1Mc, ring2Mcはそれぞれ太陽と光の輪のムービークリップ
	var viewRect:Rectangle = new Rectangle(0, 0, viewport.width, viewport.height);
	var centerX:Number = viewport.width/2;
	var centerY:Number = viewport.height/2;
	var isShow:Boolean = (sun.screen.z > 0 && viewRect.contains(sun.screen.x + centerX, sun.screen.y + centerY));
	sunMc.visible = isShow;
	ring1Mc.visible = isShow;
	ring2Mc.visible = isShow;
	if (isShow) {
		sunMc.x = sun.screen.x + centerX;
		sunMc.y = sun.screen.y + centerY;
		ring1Mc.x = sun.screen.x * 0.5 + centerX;
		ring1Mc.y = sun.screen.y * 0.5 + centerY;
		ring2Mc.x = sun.screen.x * -1.0 + centerX;
		ring2Mc.y = sun.screen.y * -1.0 + centerY;
	}
ここではsun.screenが太陽の画面上でのスクリーン座標になります。
screen.xとscreen.yがビューポートの真ん中を(0, 0)としたスクリーン座標で
screen.zはオブジェクトのカメラからの距離・・・だと思う。
カメラの前に太陽があるのか、後ろにあるのかを判定するのにscreen.zを使ってます。
もしscreen.z<0だったらカメラの後ろにあるので表示を消すようにしてます。
(太陽がビューポートの矩形範囲外にあった時も消してます)
ちなみにscreenプロパティはシーンをレンダリングした後でアクセスしないと最新の座標が取れないようです。

太陽を遮るオブジェクトの判定

レンズフレアが画面内にあっても、それを遮るオブジェクトがあったら消す必要があります。
以前Shockwave3D時代にやっていた方法は、
カメラの位置から太陽の方向にレイを飛ばして地形モデルと交差したかどうかで判定していたんだけど
交差判定が重くなりそうだったので今回は別の方法にしてみました。

まずビューポートに移っている映像で背景部分を黒くそれ以外を赤くした状態にして、
その画像から太陽の座標の色情報をBitmapData.getPixel()で調べて
赤い成分の割合で太陽の位置に遮蔽物があるかどうかを判定してます。

↓赤と黒に分けるのはこんな流れでやってます。
lensflare1.jpg

今回シーンの背景が透過している画像を取得する為に
レンダリング結果を表示するビューポートにBitmapViewport3Dクラスを使ってみました。
普段Viewport3Dクラスを使う所を、BitmapViewport3Dに変えただけです。
BitmapViewport3Dを使うとBitmapViewport3D.bitmapDataにアクセスして
ビューポートに映っている画像がそのままBitmapDataとして拾えます。

図のやり方で赤黒画像が出来上がるので、太陽の座標の色情報から赤成分を調べて、
0だったら太陽を表示する、255だったら表示しないという感じです。

ちなみに赤黒画像をBlurFilterでぼかしているのは、
太陽が半分隠れていた時にレンズフレアが弱まる感じを表現したかったので
画像をぼかして空と遮蔽物の境界部分で赤色成分が徐々に変化するようにしてます。

lensflare2.jpg

遮蔽物判定部分のコードは抜粋になっちゃうけど以下のような感じです。
・はじめに用意しておくもの
	//BitmapViewport3Dオブジェクトを生成(第4引数をtrueにして背景を透過させる)
	viewport = new BitmapViewport3D(600, 400, false, true, 0x000000);
	//遮蔽物判定用のBitmapDataを生成(ビューポートと同じ大きさ)
	bmp = new BitmapData(viewport.width, viewport.height, false);
	bmp.lock();
・以下のコードは毎フレーム、シーンのレンダリング直後に実行。
	//遮蔽物判定用BitmapDataを黒で塗り潰す
	bmp.fillRect(bmp.rect, 0xFF000000);
	//シーン画像のアルファチャンネルを赤チャンネルへコピー
	bmp.copyChannel(viewport.bitmapData, bmp.rect, new Point(), 8, 1);
	//全体をぼかす
	bmp.applyFilter(bmp, bmp.rect, new Point(), new BlurFilter(4, 4, 1));
	//最後に赤色成分の割合を取得(※px, pyは太陽の2D画面上でのXY座標です)
	var per:Number = (bmp.getPixel(px, py)  >> 16 & 0xFF) / 255;

最後のperが赤成分の割合(0~1)なので、
これを使って太陽のスケールと光の輪のアルファをいじくってます。
ちなみに最後の行の「RGBカラー値 >> 16 & 0xFF」のビット演算で赤成分の値を取ってます。
それにしてもBitmapDataの処理ってAS3になっても相変わらず重いね・・・
交差判定よりマシとはいえ毎フレームぼかしたりで結構重くなってしまった。。

トラックバック(0)

トラックバックURL: http://www.morocoshi.net/mt/mt-tb.cgi/22

コメントする