前回の記事 (Stage3D のシェーダのアップロードと AGAL の基本情報) までで、Stage3D で 3D モデルの形状とシェーダを GPU にアップロードする手順を紹介しました。後は、GPU に描画指示を出す手順だけ理解すれば Stage3D の基本は完了です。
Stage3D で描画をする場合、中心になるクラスは Context3D です。今までも、Context3D は、頂点バッファやインデックスバッファ作成のサンプルコードの中で登場してました。覚えてなければ、後で確認してみて下さい。
Context3D は、描画するリソースやその状態を管理します。GPU が利用できないとき、ソフトウェアの描画エンジンにフォールバックするのも Context3D の役割です。
これまで紹介してきた GPU 描画の仕組み、例えば、2 つのシェーダを描画処理パイプラインで使えること、常に 3 角形を使って 3D の形状を記述すること、2D 描画にも対応できること、なども、どれも Context3D の仕様です。
Context3D インスタンスの生成
さて、Context3D のインスタンスは、Stage3D のインスタンスが作成/初期化します。
Stage3D のインスタンスに対して requestContext3D() メソッドを呼ぶと Context3D のインスタンスが初期化されるのですが、Context3D インスタンス初期化は時間のかかる処理のため、作業完了はイベントで非同期に通知されます。
通知用のイベントとして context3DCreate が新しく追加されました。下がその使い方のサンプルです。
// Stage3Dのインスタンスにイベントリスナを追加 stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate); // Context3Dのインスタンス作成を指示 stage3D.requestContext3D(Context3DRenderMode.AUTO); private function onContext3DCreate(event:Event):void { // Stage3DのインスタンスからContext3Dのインスタンスを取得 context3D = Stage3D(event.target).context3D; // 以下、必要な処理を記述 }
最初に Stage3D のインスタンスにインベントリスナを追加してから requestContext3D() を呼びます。requestContext3D() の引数は Context3DRenderMode クラスに定義されている AUTO か SOFTWARE のどちらかを指定します。通常は AUTO で良いと思われます。
Context3D の初期化が完了するとイベントハンドラが呼び出されます。初期化された Context3D のインスタンスは Stage3D.context3D 属性に設定されるので、イベントのターゲットをキャストして取り出します。
表示バッファの設定
Context3D の描画は、ダブルバッファを使用します。 (バックバッファと呼ばれるバッファ内のデータを更新しておいて、それを表示中のバッファと置き換えることにより表示を更新する方法です)
表示バッファの初期化には renderContext.configureBackBuffer() メソッドを使います。
context3D.configureBackBuffer(640, 400, 2, false);
configureBackBuffer() の最初の 2 つの引数は、表示領域の幅と高さです。単位はピクセルです。最低 50x50 の広さが必要です。表示位置の指定は、Context3D の属性ではなくて、Stage3D の属性の x と y を使います。
3 つ目の引数は、アンチエイリアスの品質を指定します。指定できる値は以下の 4 つです。
- 0 :アンチエイリアス処理を行わない
- 2 :最低限のアンチエイリアス処理
- 4 :高品質のアンチエイリアス処理
- 16:最高品質のアンチエイリアス処理
一般的には、品質が高くなるほどパフォーマンスへの影響が大きくなると考えられますが、この点に関しては、環境依存の度合いが大きいようです。また、高品質のアンチエイリアスが行えない、あるいは全くアンチエイリアスが行われない環境も存在するようです。
最後の引数は、ステンシルバッファと深度バッファを利用する場合 true にします。
表示バッファの初期化は手間のかかる処理のため、描画処理中に表示サイズやアンチエイリアスの指定を変えるのは避けたほうがよいそうです。
描画処理の実行
表示バッファが準備できたら、次は 3D オブジェクトの形状 (頂点バッファ、インデックスバッファ) とシェーダを GPU にアップロードします。(ここはもう大丈夫ですね?) あとは、表示の命令を出すだけです。
表示の指示で使われる主要なメソッドは以下の 3 つです。
// drawTriangles()を呼ぶ前に必ずバッファをクリア context3D.clear(.6, .6, .6); // 3角形を全て描画する context3D.drawTriangles(indices, 0, -1); //ビューポートに表示 context3D.present();
最初の clear() メソッドは、context3D に関連づけられている色それからステンシルと深度バッファの値をクリアして、それぞれ特定の値で置き換えます。
clear() の最初の 3 つの引数は RGB 値です。0 から 1 の間の値で指定します (上の例だと背景が薄いグレーになります)。4 つ目以降は、アルファ (値は 0 から 1)、深度バッファ (値は 0 から 1)、ステンシルバッファ (値は 0 から 0xff) です。
7 つ目の引数でクリアするバッファを明示的に指定できます。指定する場合は Context3DClearMask クラスに定義された値から必要なものを "|"(or のオペレータ)で並べます。デフォルトは全てのバッファとなっています。
clear() でバッファをクリアしたら、drawTriangles() メソッドを呼び出して、シェーダによる 3 角形の処理を実行します。
drawTriangles() の引数は、順番に、インデックスバッファ、描画する最初の頂点のインデックス、描画する 3 角形の数です。デフォルトでは、インデックスバッファ内の全ての 3 角形が描画されます。
drawTriangles() で処理された 3 角形は、次に present() メソッドが呼ばれるまで画面には表示されません。そのため、複数の形状を表示するために、複数回 drawTriangles() を実行してから present() を呼ぶという使い方ができます。
一旦 present() を呼んだら、次に clear() を呼ぶまで drawTriangles() は動作しません。
リソースの解放
描画関連に使うリソースは大きくなりがちです。Context3D.dispose() メソッドを使うと、Context3D のインスタンスに関連する全てのリソース (頂点バッファやインデックスバッファ等も含む) を解放できます。
頂点バッファやインデックバッファそれぞれにも dispose() があるので、それらを個々にを呼ぶことで、1 つ 1 つリソースを解放できますが、バックバッファのように Context3D が内部的に管理するリソースは context3D.dispose() が呼ばれたときだけ解放されます。
Stage3D のインスタンスに context3DCreate のイベントハンドラが登録された状態で context3D.dispose() を呼ぶと、Stage3D は Contents3D がアクセス不能になったことを検知したら、新しい Context3D のインスタンスを生成します。なので、もし再作成が不要な場合は、イベントハンドラの削除を先に行います。
他の GPU を利用するアプリケーションが前面に表示された時は、明示的に dispose() を呼ばなくても Context3D のインスタンスが解放されます。そして、再び Stage3D が表示を行える状況になると、改めて Context3D のインスタンスが生成されます。
Contents3D が作成されるたびに、context3DCreate イベントが発行されるので、context3DCreate イベントハンドラは、何回も呼ばれることを想定して書いておくのが良さそうです。
エラーチェックモード
最後に、デバッグ用の機能の紹介です。
Context3D の enableErrorChecking 属性の値を true にすろと、様々なエラーが通知されるようになります。
context3D.enableErrorChecking = true;
例えば、drawTriangles() を呼び出すと、通常はすぐにメソッドが終了しますが、エラーチェックモードになっていると、全ての 3 角形の描画終了まで待って、その間に起きた例外を通知します。
ということから想定されるように、エラーチェックを有効にすると描画速度に大きな影響が出ます。デバッグ時やテスト時以外は利用しないように注意しましょう。
下は、今回の一連の記事で使ったサンプルコードの元になったプログラムです。適当にいじって試してみて下さい。 (参考:AGAL Mini Assembler のダウンロード、Flash Professional CS5 & CS5.5 に Flash Player 11 設定を追加する MXP)
package { import com.adobe.utils.AGALMiniAssembler; import flash.display.Sprite; import flash.display.Stage3D; import flash.display3D.Context3D; import flash.display3D.Context3DProgramType; import flash.display3D.Context3DRenderMode; import flash.display3D.Context3DVertexBufferFormat; import flash.display3D.IndexBuffer3D; import flash.display3D.Program3D; import flash.display3D.VertexBuffer3D; import flash.geom.Matrix3D; import flash.geom.Vector3D; import flash.events.Event; public class Context3D_drawTriangles extends Sprite { private var stage3D:Stage3D; private var context3D:Context3D; // 頂点シェーダを記述 private const VERTEX_SHADER:String = "m44 op, va0, vc0 \n" + "mov v0, va1"; // 断片シェーダを記述 private const FRAGMENT_SHADER:String = "mov oc, v0"; public function Context3D_drawTriangles() { stage3D = this.stage.stage3Ds[0]; stage3D.x = 10; stage3D.y = 10; // Stage3Dのインスタンスにイベントリスナを追加 stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate); // Context3Dのインスタンス作成を指示; stage3D.requestContext3D(Context3DRenderMode.AUTO); } private function onContext3DCreate(event:Event):void { // Stage3DのインスタンスからContext3Dのインスタンスを取得 context3D = Stage3D(event.target).context3D; context3D.enableErrorChecking = true; context3D.configureBackBuffer(640, 400, 2, false); var vertexData:Vector.= Vector. ([ -0.3, -0.3, 0, 1, 0, 0, 1, // x, y, z, r, g, b, a のフォーマット -0.3, 0.3, 0, 0, 1, 0, 1, 0.3, 0.3, 0, 0, 0, 1, 1, 0.3, -0.3, 0, 1, 0, 0, 1 ]); var vertices:VertexBuffer3D; // 7つの値を持つ4つの頂点用の頂点バッファを作成 vertices = context3D.createVertexBuffer(4,7); // 上で定義したvertexDataの全データをアップロード vertices.uploadFromVector(vertexData, 0, 4); // 最初の属性は座標の情報:Float型の数値が3つ context3D.setVertexBufferAt(0, vertices, 0, Context3DVertexBufferFormat.FLOAT_3); // 次の属性は色とアルファ情報:Byte型の数値が4つ; context3D.setVertexBufferAt(1, vertices, 3, Context3DVertexBufferFormat.FLOAT_4); var indexData:Vector. = Vector. ([ 0, 1, 2, // 頂点バッファ内の最初の3つの頂点 2, 3, 0 // 最初の頂点と最後の2つの頂点 ]); var indices:IndexBuffer3D; // 2つの3角形を定義するため6つの値を持インデックスバッファを作成 indices = context3D.createIndexBuffer(6); // 上で定義したindexDataの全データをアップロード indices.uploadFromVector(indexData, 0, 6); // 頂点シェーダをコンパイル; var vertexAssembly:AGALMiniAssembler = new AGALMiniAssembler(); vertexAssembly.assemble(Context3DProgramType.VERTEX, VERTEX_SHADER); // 断片シェーダをコンパイル; var fragmentAssembly:AGALMiniAssembler = new AGALMiniAssembler(); fragmentAssembly.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER); var programPair:Program3D; // Program3Dのインスタンスを取得 programPair = context3D.createProgram(); // 頂点シェーダと断片シェーダのコードをアップロード programPair.upload(vertexAssembly.agalcode, fragmentAssembly.agalcode); // 使用するシェーダのペアを指定; context3D.setProgram(programPair); // Matrix3Dのインスタンスを生成 var matrix3D:Matrix3D = new Matrix3D(); // X軸とY軸方向に移動、Y軸周りに回転 matrix3D.appendTranslation(1,1,0); matrix3D.appendRotation(1, Vector3D.Y_AXIS); // 頂点シェーダに行列のデータを設定; context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, matrix3D); // drawTriangles()を呼ぶ前に必ずバッファをクリア; ontext3D.clear(.6, .6, .6); // 3角形を全て描画する; context3D.drawTriangles(indices, 0, -1); //ビューポートに表示; context3D.present(); } } }
コメントする