3D でボトルネックになるのは描画と頂点情報の計算などです。描画については drawTriangles という便利なメソッドがあるのでまだ良いとして、頂点情報については for で回して計算するのが常套手段ですが、最近はシェーダ(Pixel Bender)を使っての最適化に挑戦しています。
‘08 5.29 追記:
Saqoosha さんからコメント欄で頂いた情報を元に新しいエントリーを投稿しました。このエントリーでしているような複雑なことをしなくても、シェーダにデータを渡せますのでそちらを参照ください。
—–
そのためにまず必要になるのが、どのようにシェーダ側にデータを渡すかというところ。シーンの行列などはパラメータで渡せるのですが、頂点の情報量は不定のためパラメータとしては渡せません(そもそも、シェーダは Flash 用に書き出す設定では配列に対応していませんし)。ですので、ピクセルに頂点座標を埋め込もうとしたのですが、これも中々うまくいかず、現状はここで止まってます。
流れを追って説明すると以下のような感じ。
1. Flash 側 − ビットマップに頂点情報を埋め込む。
浮動小数点数な頂点情報を、ビットマップに 15.15 形式の固定小数点数な uint(32 bit) として埋め込む。(ビットマップに書き込む際にアルファ値が 0 だと他の色情報まで 0 になってしまうため、32 bit 目は 1 で固定。また、31 bit 目は正負を表すために 15.15 形式)
例えば {x:100, y:0, z:0} の頂点一つのデータを埋め込むコードは以下。
ActionScript
var vector:Vector. = Vector.([
// 100 << 15 | 0x80000000 でも良い。
// ただ、ビット演算は計算によってはオーバーフローを起こすので注意。
// ちなみに 0x80000000 は 32 bit 目を 1 にするため。
100 * 0x8000 + 0x80000000, 0, 0
]);
var data:BitmapData = new BitmapData(3, vector.length / 3);
data.setVector(data.rect, vector);
2. シェーダ側 − ピクセルから頂点情報を抜き出す。
シェーダ側でピクセルをサンプリングすると float4(a, r, g, b) の形になるので、これをもう一度固定小数点数に戻す。ただし、Pixel Bender の int 型は 16 bit なので float 型で取り扱う。
コードは以下。
ActionScript
// BIT_24 は 16777216.0、BIT_16 は 65536.0、BIT_08 は 256.0 をそれぞれ定数化したもの
pixel4 tmp = sampleNearest(src, outCoord());
float n = floor(tmp.a * 255.0) * BIT_24
+ floor(tmp.r * 255.0) * BIT_16
+ floor(tmp.g * 255.0) * BIT_08
+ floor(tmp.b * 255.0);
3. シェーダ側 − 頂点情報を計算。
パラメータで受け取った行列などを元に、頂点情報を計算する。ここは実際には試していないが、大した問題ではないはず。
4. シェーダ側 − ピクセルに埋め込むために各色情報を分解。
Pixel Bender はビット演算が行えないため、乗算・除算を使って分解します。
コードは以下。
ActionScript
float a = floor(n / BIT_24);
float r = floor(n / BIT_16) - (a * BIT_08);
float g = floor(n / BIT_08) - (a * BIT_16 + r * BIT_08);
float b = floor(n ) - (a * BIT_24 + r * BIT_16 + g * BIT_08);
いま、つまづいているのがまさにここ。例えば、n が 4294770688.0({a:255, r:253, g:0, b:0})の時は誤差が生じて r 成分が 254 になってしまいます。試しに、ActionScript で同様のコードを実行しても誤差は生じず・・・。
同様のコード。
ActionScript
var BIT_24:Number = 16777216.0;
var BIT_16:Number = 65536.0;
var BIT_08:Number = 256.0;
var BYTE:Number = 255.0;
var dst:Object = {a:255/255, r:253/255, g:0/255, b:0/255};
var n:Number = Math.floor(dst.a * BYTE) * BIT_24
+ Math.floor(dst.r * BYTE) * BIT_16
+ Math.floor(dst.g * BYTE) * BIT_08
+ Math.floor(dst.b * BYTE);
trace(n); // 出力 => 4294770688
var a:Number = Math.floor(n / BIT_24);
var r:Number = Math.floor(n / BIT_16) - (a * BIT_08);
var g:Number = Math.floor(n / BIT_08) - (a * BIT_16 + r * BIT_08);
var b:Number = Math.floor(n ) - (a * BIT_24 + r * BIT_16 + g * BIT_08);
trace(dst.a, dst.r, dst.g, dst.b); // 出力 => 1 0.9921568627450981 0 0
trace(aa / BYTE, rr / BYTE, gg / BYTE, bb / BYTE); // 出力 => 1 0.9921568627450981 0 0
色々とデバッグ(Pixel Bender はデバック面倒過ぎ!)して分かったことは、除算を行うと何故か誤差が生じてしまい、うまく分解できていないということ。内部の計算に使用できる bit 数が異なるために起こる誤差なのか、何なのか・・・。誰か助けていただけませんか。