PV3D2.1: レンズフレア

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

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

太陽のスクリーン座標

3D空間内にあるオブジェクトの基点の2D画面上での位置はDisplayObject3D.screenで簡単に調べられるのでそれを利用します。
	var sun:DisplayObject3D = new DisplayObject3D();
	scene.addChild(sun);
	sun.autoCalcScreenCoords = true;
	sun.position = new Number3D(100000, 20000, 0);
まず太陽の位置を知るためにダミーのDisplayObject3Dを生成して、シーンに追加しておきます。autoCalcScreenCoords=trueしているのはシーンがレンダリングされる度に自動でスクリーン座標を計算させる為です。後は毎フレームスクリーン座標を調べてその位置に太陽の画像を移動させるだけ。
	//最初に実行しておく処理
	//ビューポートサイズ
	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, ring1Mc, ring2Mcはそれぞれ太陽と光の輪のムービークリップ
	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だったら太陽がカメラの後ろにあるので表示を消して、太陽がビューポートの外にあった時も消しています。

※DisplayObject3D.screenはシーンをレンダリングした後でアクセスしないと最新の座標が取れないようです。

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

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

※以前ここで紹介した方法がかなり重かったので、もっと軽い方法での判定に変えました。

今回シーン内に空用モデルを配置していないので、レンダリング結果を表示するビューポート画面で空の部分は描画するものが何もない・・・つまり透明になっているという事になります。なので太陽位置の透明度を調べれば遮蔽物があるかどうかがわかります。

大ざっぱな手順は

  1. ビューポートに映っている画像をBitmapDataで取得する
  2. 取得したBitmapData上の太陽位置のピクセル情報を調べる
  3. 取得したピクセルの透明度を調べて、透明だったら遮蔽物がない事にする
こんな感じです。
1のビューポート画像をBitmapDataで取得する方法ですが、いつもVireport3Dクラスを使うところをBitmapVireport3Dクラスにすると、BitmapVireport3D.bitmapDataにアクセスするだけでビューポートに映っている画像がそのままBitmapDataとして拾えます。

遮蔽物判定部分のコードは抜粋になっちゃうけど以下のような感じ。
・はじめに用意しておくもの
	//BitmapViewport3Dオブジェクトを生成(第4引数をtrueにして背景を透過させる)
	var viewport:BitmapViewport3D = new BitmapViewport3D(600, 400, false, true, 0x000000);
・以下のコードは毎フレーム、シーンのレンダリング直後に実行。
	//ピクセルカラーを取得(※px, pyは太陽のスクリーン座標)
	var argb:uint = viewport.bitmapData.getPixel(px, py);
	//透明度の割合を取得
	var per:Number = 1 - (argb  >>> 24) / 0xFF;
perが透明度の割合(0~1)なので、この数値で太陽のスケールとかアルファとかを変化させるだけです。最後の行の「ARGBカラー値 >>> 24」はアルファチャンネルの値を抜き出すビット演算です。

太陽が半分隠れていた時の処理

上記の処理だけでもいいんだけど、太陽が半分隠れていた時に少し弱まる感じを再現したいので、その処理も考えてみます。
lensflare2.jpg

最初ここで紹介していた方法が、ビューポート画像をBitmapDataで取得した後、そのアルファチャンネル画像をぼかして空と遮蔽物の境界部分をなめらかにする方法でした。実際はもっと回りくどかったけど・・・

omoiyatu.jpg

こうすると太陽が半分隠れる境界部分で徐々に透明度が変化してくれるので、それっぽい表現ができたんだけど・・・毎フレーム画像を複製してぼかしていたので、物凄く重かった。(全体をぼかす必要はなかったんだけど・・・)

そこでぼかすのをやめて、太陽の周囲のピクセルも調べてそれらの透明度の平均値を求める方法に変えてみました。太陽位置のピクセルを中心に、上下左右数ピクセルの矩形範囲の透明度を全て加算して、それを矩形範囲のピクセル数で割るだけです。

・はじめに用意しておくもの
	//BitmapViewport3Dオブジェクトを生成(第4引数をtrueにして背景を透過させる)
	var viewport:BitmapViewport3D = new BitmapViewport3D(600, 400, false, true, 0x000000);
	//上下左右に何ピクセルまで調べるか(padding>=0)
	var padding:int = 2;
	//調べるピクセルの総数
	var pixelNum:int = (padding * 2 + 1) * (padding * 2 + 1);
・以下のコードは毎フレーム、シーンのレンダリング直後に実行。
	var perTotal:int = 0;
	for (var ix:int = -padding; ix <= padding; ix++) {
		for (var iy:int = -padding; iy <= padding; iy++) {
			//(px, py)は太陽のスクリーン座標
			perTotal += viewport.bitmapData.getPixel32(px + ix, py + iy) >>> 24;
		}
	}
	//アルファ値の平均値を求める
	var per:Number = 1 - perTotal / pixelNum / 0xFF;
あとはperの割合(0~1)で太陽の強さを変化させるだけです。

Wonderflのサンプル

この方法で組んだレンズフレアのサンプルをWonderflに投稿してあるので動くコードが見たい人は下のリンクからどうぞ。コードは色々と酷いけど気にしないでね。。。

トラックバック(0)

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

コメント(2)

Hi, I wonder... are you going to release the code?
Are you using quadrant for isolating triangle3D?
Thanks in advance

コメントする