WordPress へ移行
WordPress へ移行しました。これからはちゃんとエントリーを・・・していけたらいいな。
WordPress へ移行しました。これからはちゃんとエントリーを・・・していけたらいいな。
前回のエントリーでは複雑なことをしてシェーダにデータを渡そうとしていましたが、Saqoosha さんからコメント欄で教えていただいた boostworthyisryantaylor というサイトの情報(ShaderJob クラスなど)を元に、シェーダでの頂点情報の計算に成功しました。
フレームレートが 20 を切る分割数を計測(Z ソートは今回無し)
従来の通りの頂点計算 => 分割数 75、頂点数 5625
シェーダを使った頂点計算 => 分割数 80、頂点数 6400
こんな感じで、望んでいたような劇的な変化はありませんでした。自分の環境は普通の MacBook ですので GPU は搭載されていませんが、GPU がある環境では wmode を gpu とかにすれば結果は変わってくるんでしょう。試してないですが。
・従来の通りの頂点計算デモはこちら
・シェーダを使った頂点計算デモはこちら
※ 要 Flash Player 10
※ 左右キーで分割数、上下キーで視野角を変更できます。
boostworthyisryantaylor さんでも書かれているように、現在はバグなのか、ShaderInput に Vector.<number> を渡すとエラーになるので ByteArray で渡しています(ドキュメントでは渡せると書いてあります)。この問題が直れば幾分は良くなるでしょうが、あまり期待はできないのかも。引き続き、調査は続けていくつもりです。とりあえずは Vector.<number> が渡せるようになるのを待つつもりですが。
http://www.libspark.org/svn/as3/Astro/VertexShader_01/
に今回のコードを上げましたので必要な方はチェックアウトしてください。
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} の頂点一つのデータを埋め込むコードは以下。
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 型で取り扱う。
コードは以下。
// 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 はビット演算が行えないため、乗算・除算を使って分解します。
コードは以下。
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 で同様のコードを実行しても誤差は生じず・・・。
同様のコード。
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 数が異なるために起こる誤差なのか、何なのか・・・。誰か助けていただけませんか。
3D のパフォーマンステストには最適なドーナッツのプリミティブで Z ソートを実装しました。
※ 要 Flash Player 10
デモはこちら(左右キーで分割数を変更できます)
Z ソートはこんな感じの実装。
drawTriangles はやっぱり凄い、分割数を結構上げてもサクサク動く。巷で言われている通り、3D ライブラリは軒並み性能アップするでしょうね。
すっかり FlashPlayer 10 の魅力に取り付かれています。
‘08.10.09 追記:
CS4 からはメタデータを使ってファイルの埋め込みができるようになるようなので、こんな面倒臭いことしなくても大丈夫です。
通常、Shader を使用するときは pbj 形式のファイルが必要ですが、今回はこのファイルを使用せずにカスタムフィルターを作成するという試み。結果から言うと大成功でした。これで他の BitmapFilter 同様、import するだけでカスタムフィルターが使用できます。このやり方は流行ると思うなぁ。Adobe の人もこういう使い方を想定しているんでしょう、きっと。
まずはカスタムフィルターのコード。
package
{
import flash.display.Shader;
import flash.filters.ShaderFilter;
import flash.utils.ByteArray;
public class ColorizeFilter extends ShaderFilter
{
// ここが重要!
// あらかじめ、PixelBender のバイナリデータをベクター情報として格納しておく。
private static var _data:Vector.<int> = Vector.<int>([-91,1,0,0,0,-92,11,0,67,111,108,111,114,70,105,108,116,101,114,-96,12,110,97,109,101,115,112,97,99,101,0,109,117,116,97,0,-96,12,118,101,110,100,111,114,0,109,117,116,97,0,-96,8,118,101,114,115,105,111,110,0,1,0,-96,12,100,101,115,99,114,105,112,116,105,111,110,0,84,104,105,115,32,102,105,108,116,101,114,32,70,105,108,108,32,99,111,108,111,114,46,0,-95,1,2,0,0,12,95,79,117,116,67,111,111,114,100,0,-95,1,4,1,0,15,99,111,108,111,114,0,-94,4,100,101,102,97,117,108,116,86,97,108,117,101,0,0,0,0,0,0,0,0,0,0,0,0,0,63,-128,0,0,-94,12,100,101,115,99,114,105,112,116,105,111,110,0,99,111,108,111,114,0,-93,0,4,115,114,99,0,-95,2,4,2,0,15,100,115,116,0,48,3,0,-15,0,0,16,0,29,4,0,-13,3,0,27,0,1,4,0,-13,1,0,27,0,29,2,0,-13,4,0,27,0]);
private static var _byteCode:ByteArray;
private var _color:uint;
public function ColorizeFilter(color:uint):void
{
// ムービーを通して初めての new 時のみ、Vector を ByteArray に変換する。
if (_byteCode == null)
{
_byteCode = new ByteArray();
for (var i:int = 0, l:int = _data.length; i < l; i++)
{
_byteCode.writeByte(_data[i]);
}
}
super(new Shader(_byteCode));
this.color = color;
}
public function get color():uint
{
return _color;
}
public function set color(value:uint):void
{
_color = value;
// プロパティのセッター内で Shader にデータを渡す。
shader.data.color.value = [value >> 16, value >> 8 & 0xFF, value & 0xFF];
}
}
}
実際に使用するときのコード。
var data:BitmapData = Bitmap(new image()).bitmapData;
data.applyFilter(
data,
data.rect,
new Point(),
new ColorizeFilter(0xFF0000)
);
addChild(new Bitmap(data));
こんな感じで通常のフィルターと同じように import して new するだけで使用できます。今回のコード一式も例のごとく libspark に上げてますので、必要な方は http://www.libspark.org/svn/as3/Astro/ShaderFilter/CustomFilter/ 以下をチェックアウトしてください。
beinteractive さんの twitter でのコメント。
Player10 で結構な長さのint配列に対して演算をしなければならない場合、
1. ただのArray
2. Vector.<int>
3. Vector.<int> を BitmapData に setVector して、Shader (Pixel Bender) で計算後、 getVector
のどれが一番速いんだろう
を受けて、自分も気になったので調べてみました。100 万個強のデータに uint を加算するテスト結果は以下の通り。
Array => 361 ms
Vector => 188 ms
Shader + Vector => 35 ms
Shader + Vector の組み合わせは Array より十倍近く早い結果に。今回は簡単な加算のみですが、色々と試してみる価値がありそうですね。試したコードは libspark に上げています。一式必要な方は http://www.libspark.org/svn/as3/Astro/ShaderFilter/CalcShader/ 以下をチェックアウトしてください。
‘08 5/23 追記:
活用法がないか色々と考えたり試したりしているんですが、そもそも setVector する時点で 0×00FFFFFF 以下のデータはアルファが 0 になるので、数値自体も 0 としてセットされてしまうんですよね。PixelBender 上で一ピクセルの情報だけを取り出して・・・とかの方法も分からない(できない?)し、何か良い活用法思いついた方はいらっしゃらないでしょうか?
今回は先日のデモにテクスチャを適用します。コード自体に大まかな変更は無いので、前回のコードで説明が足りない所や今回の肝になる部分の解説だけしていきます。
※ 要 Flash Player 10
平面のテクスチャ適用デモはこちら
立方体のテクスチャ適用デモはこちら
まずは以下のコード。
var world:Matrix3D = scene.transform.matrix3D.clone(); world.append(projection.toMatrix3D());
早速新しい Matrix3D クラスですが、そもそも行列(Matrix)はある座標系のものを別の座標系に変換するときに使用します。今回はシーンの座標系を透視投影を用いて僕たちが見ているスクリーン上の座標に変換する必要があるので、シーンの座標系を表す行列に透視投影の行列を乗算することで変換用の行列が出来上がります。このとき使用するのが append メソッド、もしくは prepend メソッドで、A.append(B) は A × B、A. prepend(B) は B × A を表します(多分)。詳しくは行列について調べてみてください。
次はこのコード。
var v:Vector3D = world.transformVector(vertex); v.w = projection.focalLength / (projection.focalLength + v.z); v.project(); vertices.push(v.x, v.y);
ここでは変換用の行列でプリミティブの頂点を変換しています。その後にベクトルの w 要素に上の計算で求まる数値を代入してあげて、project メソッドを実行してあげることでスクリーン上の座標へと変換出来ます。晴れて二次元上の座標となった頂点を描画用の頂点ベクターに格納しています。
最後はこのコード。
canvas.graphics.beginBitmapFill(texture);
canvas.graphics.drawTriangles(
vertices, primitive.indices, primitive.uvtData, TriangleCulling.NEGATIVE
);
この drawTriangles が新しく Graphics クラスに実装されたメソッドなんですが、ポリゴンを描画するために生まれたようなもので、とても便利。二次元上に変換した頂点ベクターとプリミティブの各情報、最後にカリングの指定をしてあげることでプリミティブのポリゴンが描画出来ます。ちなみに TriangleCulling.POSITIVE は描画する三角形の頂点が反時計周りの時には描画しません。逆に TriangleCulling.NEGATIVE は時計回りの時には三角形を描画しません。詳しい解説はこちら(Flash Player 10 Drawing API)や、「法線」とかでググってみるといいかも。今回も FlashDevelop のプロジェクトファイル一式を上げておきますので、参考にどうぞ。
‘08 5/21 追記:
Flash Player 10 Drawing API を元ソースではなく、なぜか中国?の Flash サイトへのリンクにしてたので修正しました。すいません。
パースペクティブコレクトまでしてくれる(してくれてますよね?)、とても素敵な drawTriangles メソッドですが、線の設定(lineStyle)が出来ないのでワイヤーフレームなプリミティブが描画出来ないんですよね。これは仕様なのかバグなのか。
‘08 5/21 追記:
パースペクティブコレクト(透視補正)はしてくれないみたいです、すいません。デモを上げておきますので、ご確認ください。
‘08 5/25 追記:
やっぱり補正にも対応しているみたいですね。
Mtok-blog さんで紹介されていますので、参考にどうぞ。
ついにベータ版が登場した Flash Player 10 。GPU サポートやテキスト周りの強化なんかがありますが、その辺りは他の人に任せるとして…。3D が正式にサポートされたことが何とも嬉しいです。いくつか試してみたので、デモをアップしてみます。
※ 要 Flash Player 10
解説を交えたコードは下記の通り。
package
{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.geom.Matrix3D;
import flash.geom.PerspectiveProjection;
import flash.geom.Vector3D;
import flash.text.TextField;
import net.unbland.debug.FPS;
import primitives.Cube;
import primitives.Plane;
[SWF(width=320, height=320, backgroundColor=0xDDDDDD, frameRate=30)]
public class Demo01 extends Sprite
{
// 描画用のスプライト
private var canvas:Sprite = new Sprite();
// 透視投影のためのクラス
private var projection:PerspectiveProjection = new PerspectiveProjection();
// 3D シーン(ステージには配置しない)
private var scene:Sprite = new Sprite();
// 3D プリミティブ(別クラスで定義)
// 平面用
private var primitive:Plane = new Plane();
// 立方体用
//private var primitive:Cube = new Cube();
public function Demo01():void
{
// ステージの設定、3D には関係無い
setupStage();
// 3D では中心点が左上でなく、中央になるので画面中央に配置
canvas.x = canvas.y = 160;
addChild(canvas);
// プロジェクションの視野角を決める
// これにより焦点距離が変わる
projection.fieldOfView = 60;
// scene.transform.matrix3D は最初は空になっている(バグ?)ので、
// 行列を代入しておく
scene.transform.matrix3D = new Matrix3D();
// 実際に描画を行う関数を毎フレーム実行
scene.addEventListener(Event.ENTER_FRAME, renderScene);
}
private function renderScene(e:Event):void
{
// 変換用行列を作成
var world:Matrix3D = scene.transform.matrix3D.clone();
// 描画用の頂点情報が格納されたベクター
var vertices:Vector.<Number> = new Vector.<Number>();
// プロジェクションの行列を変換用行列に乗算
world.append(projection.toMatrix3D());
// プリミティブの各頂点を走査
for each (var vertex:Vector3D in primitive.vertices)
{
// 変換用行列で頂点をシーンの座標系に変換
var v:Vector3D = world.transformVector(vertex);
// プロジェクションの焦点距離を元に計算
v.w = projection.focalLength / (projection.focalLength + v.z);
// スクリーン上に投影
v.project();
// 描画用頂点に追加
vertices.push(v.x, v.y);
}
// ポリゴンを描画
canvas.graphics.clear();
canvas.graphics.beginFill(0xDDDDDD);
// 平面用
canvas.graphics.drawTriangles(vertices, primitive.indices);
// 立方体用
//canvas.graphics.drawTriangles(
//vertices, primitive.indices, null, TriangleCulling.NEGATIVE
//);
// シーンを回転
scene.rotationY += 2;
scene.rotationZ += 1;
}
private function setupStage():void
{
FPS.show(stage);
stage.showDefaultContextMenu = false;
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
graphics.lineStyle(1, 0xAAAAAA);
graphics.beginFill(0xFFFFFF);
graphics.drawRect(10, 10, 300, 300);
}
}
}
FlashDevelop の開発途中版が早くも Flash Player 10 に対応しており、Vector とかのコードヒントとかもで出るのでオススメです。twitter での beinteractive 先生のコメントで知ったのですが、Flash Player 10 Debug 版もこっそりあるみたいなので合わせて使えばウハウハです。今回のサンプルに使ったプロジェクト(プリミティブ含む)を上げておきますので、参考にどうぞ。
以前、HiSprite クラスという、フィルター関連の設定をプロパティで行えるようにしたクラスを作成しました。ただ、これだと Sprite を継承した独自クラスなどに適用できません。ですので、この場合は委譲用クラスを実装した方が実用的だと考えました。しかし、ことごとく壁にぶち当たり、結論から言うと断念。
そんなこんなを twitter で愚痴っていると、nium 先生と beinteractive 先生からアドバイスを頂きました。「元オブジェクトに触られた場合に整合性を取ることは委譲の永遠の課題」というコメントなどはとても参考になり、ありがたかったです。この場を借りてお礼申し上げます。両先生、ありがとうございました!
以下、色々試してみたメモ。
3. 辺りはまだ望みがありそうなので、今後も試してみる予定です。
突然ですが、 unbland.net はさくらインターネットのスタンダードプランなのです。で、僕は ZendFramework を試したかったのです。なのにさくらインターネットに昨年導入された PHP5 は、MySQL 用の PDO ドライバが入ってなかったりします。問い合わせてみても今のところ導入予定はないそうなので、PHP5 から自分で入れてみました。忘れないようにメモ。