前回 (Stage3D と 2 つのシェーダと頂点情報) は、3D モデル情報を Stage3D に渡す方法をご紹介しました。今回は、シェーダ関連の Stage3D の API の紹介です。
それから、AGAL (Adobe Graphics Assembly Language) も、少しだけ紹介します。
シェーダのアップロード
始める前に、今回ご紹介するサンプルでは、AGAL Mini Assembler というサンプルツールを使っています。今のところ、実行時に AGAL をコンパイルする機能を提供する正式なツールは提供されていないので、動作確認には、こちらをダウンロードしてお使い下さい。
早速ですが、以下が AGAL で記述された頂点シェーダと断片シェーダの例です。
// 頂点シェーダを記述 private const VERTEX_SHADER:String = "m44 op, va0, vc0 \n" + "mov v0, va1" // 断片シェーダを記述 private const FRAGMENT_SHADER:String = "mov oc, v0";
読み方はこの記事の後半で説明しますので、ここではプログラムが 2 つ書かれていることだけ確認してください。
シェーダのコードを GPU にアップロードする前に、上で紹介した AGAL Mini Assembler でコンパイルします。これは、オブジェクトコードに変換するためです。
AGAL Mini Assembler は、コンパイルした結果を内部のバッファに保持するため、シェーダごとに AGAL Mini Assembler のインスタンスを作成します。そのため、コードは以下のようになります。
// 頂点シェーダをコンパイル var vertexAssem:AGALMiniAssembler = new AGALMiniAssembler(); vertexAssem.assemble(Context3DProgramType.VERTEX, VERTEX_SHADER); // 断片シェーダをコンパイル var fragmentAssem:AGALMiniAssembler = new AGALMiniAssembler(); fragmentAssem.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER);
コンパイルされたシェーダを GPU にアップロードする機能は、Program3D というクラスが提供します。Program3D のインスタンスは、Context3D.createProgram() で取得します。
var programPair:Program3D; // Program3Dのインスタンスを取得 programPair = context3D.createProgram(); // 頂点シェーダと断片シェーダのコードをアップロード programPair.upload(vertexAssem.agalcode, fragmentAssem.agalcode); // 使用するシェーダのペアを指定 context3D.setProgram(programPair);
Program3D.upload() の引数には、一緒に動作させる頂点シェーダと断片シェーダの組み合わせを指定します。2 つ揃って意味を成す、ということですね。
アップロードしたら、context3D.setProgram() を呼ぶとシェーダが利用可能な状態になります。前回の記事で扱った頂点情報のアップロードも行えば、Stage3D を使った描画の実現まではあと少しです。
シェーダに定数を設定
頂点シェーダが座標変換を行う際は、カメラがどの方向から見ているのか、といった情報を必要とします。これらの計算に使う定数を渡す手段も用意されています。 (もちろん)
渡せる情報の種類は座標か行列です。座標を渡す場合は "Number 型の値 × 4" が繰り返される Vector として渡します。行列を渡すには Matrix3D を使います。
例えば、座標変換のための行列を頂点シェーダに渡す場合は、以下のようなコードになります。
// 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);
最後の行の setProgramConstantsFromMatrix() が行列をシェーダに渡すメソッドです。
最初の引数で、渡すシェーダの種類を指定します。シェーダの種類は Context3DProgramType に定義されています。
2 つ目の引数は、送られたデータを保管する場所 (定数レジスタ) の番号を指定します。行列データの保管には 4 つのレジスタが必要ですが、上のコードでは 0 が指定されているため、0 番から 3 番の定数レジスタにデータを保管する、という指定になります。
4 つめの引数として true を指定すると行列を転置させるという意味になります。上の例ではデフォルト値の false が使われます。
ちなみに、座標データを渡す時は以下のメソッドを使います
Context3D.setProgramConstantsFromVector(programType:String, firstRegister:int, data:Vector.<Number>, numRegisters:int = -1)
最後の引数は、データ保管用に用意するレジスタの数を明示的に指定するためのものです。(デフォルトは全データ分)
AGAL の基礎
話題を変えて、ここからは AGAL のプログラム (記事冒頭のサンプル程度のもの) を読むための補足解説です。あくまで補足なので、以下は読み飛ばしても大丈夫です。
さて、
シェーダの記述に使われる言語は、GPU が提供する機能を前提に作られた特殊な言語です。一般的に使われるものには GLSL や HLSL があります。AGAL は Stage3D 用に開発されたシェーダ記述用の言語です。
AGAL は低レベルのアセンブラ言語です。つまり、AGAL のプログラミングでは、GPU の実際の動作に合わせた記述することになります。分かり易い変数名は使えず、代わりにレジスタの番号だけでデータを管理しなければなりません。これだけでもかなり厄介な作業です。
AGAL に限らず、一般的にアセンブラ言語での開発はかなり面倒です。たとえ Stage3D 対応フレームワークを使いたくない理由がある場合でも、AGAL よりは、PixelBender 3D を使って開発した方が楽でしょう。 (PixelBender 3D についてもそのうち簡単に紹介する予定です)
とはいえ AGAL が分かると、Stage3D のシェーダがどのように動作しているのかを理解できるようになります。なので、開発に使用するかはともかく、ある程度読めると、いろいろと (特にチューニングのときなど) 便利なことがあるかもしれません。 (無い方が多いとは思いますけれど)
ということで、AGAL のコードを見てみると、以下のような形をしているのが分かります。
<オプコード> <デスティネーション>, <ソース1>, <ソース2 または サンプラー>
"オプコード" の箇所は Stage3D で利用可能な命令を記述します。命令は 3 文字の英数字です。そのあとに続く並びは、オプコードに依存しますが、基本は上のような形です。
"デスティネーション" と "ソース" の箇所はレジスタを指定します。レジスタは 128 ビット、すなわち Float 型 4 つ分のデータ保管場所です。
"デスティネーション" は、オプコードが結果を書くレジスタです。"ソース" は、オプコードが参照する値を持つレジスタです。
ちなみに AGAL で使用できる代表的なオプコードには以下のようなものがあります。
- mov: ソース1からデスティネーションにデータを移動
- add: デスティネーション = ソース1 + ソース2
- sub: デスティネーション = ソース1 - ソース2
- mul: デスティネーション = ソース1 * ソース2
- div: デスティネーション = ソース1 / ソース2
オプコードを全て知りたい方はヘルプをご覧下さい (AGAL のバイトコード形式)。テクスチャからサンプルを取得するなんていうオプコードもあります。今後新しいオプコードが追加されることもあるかもです。
次にレジスタについて、AGAL で使用するレジスタは 6 種類に分けられています。
例えば、頂点バッファからの入力である、頂点属性を値として持つレジスタは、"属性レジスタ" と呼ばれます。Context3D.setVertexBufferAt() で属性を設定すると (前回の記事参照) このレジスタに値が入ります。属性レジスタの名前は va に番号を付けたもので、番号は setVertexBufferAt() の第 1 引数で指定したものと一致させます。 (例:va0) 頂点レジスタは 8 つまで使えます。
また、この記事の少し上の方で、setProgramConstantsFromXxxxxx() メソッドで、シェーダに定数を設定する方法を紹介していますが、この定数が格納されるのは "定数レジスタ" です。定数レジスタの名前は、頂点シェーダでは vc + 番号、断片シェーダでは fc + 番号です。上で説明したとおり、setProgramConstantsFromXxxxxx() の第 2 引数で指定した番号から定数の保管に必要な分だけの番号が使われます。頂点シェーダは 128、断片シェーダは 28 個の定数レジスタが使えます。
その他には、データ一時保管専用の "一時レジスタ" (名前は、頂点シェーダの場合 vt + 番号、断片シェーダの場合 ft + 番号:それぞれ最大 8)、デスティネーション専用の "出力レジスタ" (シェーダ毎に 1つ 存在。頂点シェーダ用の名前は op、断片シェーダ用の名前は oc) 、頂点シェーダから断片シェーダにデータを渡すための "可変レジスタ" (名前は v + 番号:最大 8)、テクスチャからサンプルを取り出す "サンプラーレジスタ" (名前は fs + 番号:最大 8)があります。
最初の AGAL サンプルの解説
最後に、記事の冒頭のサンプルコードを確認してみましょう。以下、前回の記事でアップロードした頂点情報を利用するものと仮定します。
まず、
m44 op, va0, vc0
のオプコード m44 は 4x4 行列演算を行う命令です。このような命令がハードウェアでサポートされているのが GPU 描画が高速な理由の 1 つです。あとのレジスタはもうお分かりですね? 属性レジスタの 0 番と、定数レジスタの 0 番を入力として、出力は op に書いています。つまり、頂点の座標を、行列を使って変換してから次の処理に渡す、というのが上の行の意味になります。
次に、
mov v0, va1
では、属性レジスタの 1 番から、可変レジスタの 0 番にデータを移動しています。つまり、頂点の色情報をそのまま断片シェーダに渡している訳です。
最後の、
mov oc, v0
では、単純に可変レジスタの 0 番から出力レジスタ oc にデータを移動しています。つまり、断片シェーダは、頂点シェーダからの値 (もしくは補間された値) をそのまま出力しているだけです。
もう簡単な AGAL なら理解できそうでしょうか? (AGAL を使った開発をお勧めしている訳では決してありませんので念のため)
次は、Stage3D を使った描画の手順の予定です。
コメントする