Molehill Stage3D APIでシェーダとして使用するAGAL言語について。
前回(毎回)、勝手な妄想で記事を書いたがちょっとAGALの仕様について調べてみた。
公式のASDocのProgram3Dの項及びAGAL Bytecode Reference(pdf)に一応の言語仕様のようなものは見られるが正直よくわからない。
iFlash3D "My Name is AGAL, and I Come from Adobe – Part1"になかなか良さげな説明があったので、拙訳を載せてみる。
---
いつか人類が滅びることを考えてみよう。
私たちが絶滅した後、紙製のドキュメントが経年で摩耗し、読めなくなることを。そして知的外来種が、人々が作ったなにか情報が記録されたDVDや個体記憶装置を発見したときのことを。
彼らはどのようにしてそれを解読するのだろう。その"人工物"にどんな情報があるのかを理解する鍵をどのようにして得るのだろう。
一般に、彼らが私たちのテクノロジーの産物に触れ、彼らがそれを理解する必要に迫られたとき、どうすれば私たちの発見を習得し、テクノロジーを利用できるのだろう。
Adobeが最近、AGAL(Adobe Graphics Assembly Language)と呼ばれる新しい言語を発表した。これはMolehill APIの一部で、その目的はいわゆる"シェーダ"、3Dモデルをどのようにシーンにレンダリングするかを決める小さなプログラム、を作ることだ。
シェーダはすごい。様々なレンダリング効果を実現できる。しかし通常のActionScriptよりはるかにコーディングしづらい。
AGALはこのように書かれる。
//vertex shader m44 op, va0, vc0 // pos to clipspace mov v0, va1 // copy uv //pixel shader tex ft1, v0, fs0 <2d,linear,nomip> mov oc, ft1
まるでヒエログリフのようだ。どうしたらわかるだろうか?
問題は現在、AGALについてのドキュメントがまだほどんどないことだ。謎のシェーディング言語について少し考えてみよう。
シェーダのすべての行は"オペコード"と呼ばれる既定の3文字の命令で構成されている。
AGALコードのシンタックスは以下のようになる。
<オペコード> <出力先> <入力1> <入力2もしくはサンプラー>
これが鍵だ。このシンタックスを頭に入れておけばてきめんAGALが読めないへんなものではなくなる。
オペコードを見ると、命令によって出力先と1つないしは2つの入力をとることができる。出力先と入力は"レジスタ"というシェーダが使うGPU上の小さなメモリ領域だ(レジスタについては後程詳しく説明しよう)。
AGALには30ものオペコードがある。使用できる全てのオペコード一覧はMolehillのドキュメントでみることができる。よく使うオペコードはこのようなものだ。
レジスタはAGALプログラム(シェーダ)が実行中に使用できるGPU内の小さなメモリ領域だ。AGAL命令の入力と出力先どちらにも使われる。
このレジスタを通してシェーダにパラメータを送ることもできる。
各レジスタは128bit長で、4つのfloat値をもつ。これらの各値をレジスタの"成分"という。
レジスタの成分にはxyzwの座標アクセサかrgbaのカラーアクセサどちらでもを通してアクセスできる。
つまりレジスタの最初の成分にはこのどちらのようにしてもアクセスできる。
<レジスタ名>.x
<レジスタ名>.r
"add"のようないくつかのオペコードは"component wise"に働く。これは加算操作が成分ごとに行われるということを意味する。
以下の6種類のレジスタが使用できる。
このレジスタは頂点シェーダの入力されたVertexBufferを参照する。ゆえに頂点シェーダでしか使うことはできない。
VertexBufferを特定のAttributeレジスタに割り当てるにはContext3D:setVertexBufferAt()メソッドをインデックスを指定して使う。
シェーダからはva<n>でAttributeレジスタにアクセスする。<n>はAttributeレジスタのインデックス番号だ。
合計8つのAttributeレジスタが頂点シェーダで使える。
このレジスタはActionScriptからシェーダにパラメータを送る目的で使われる。これにはContext3D::setProgramConstants()メソッドファミリーを使う。
このレジスタには頂点シェーダからはvc<n>で、ピクセルシェーダからはfc<n>でアクセスする。<n>はConstantレジスタのインデックス番号だ。
頂点シェーダには128のConstantレジスタが、ピクセルシェーダには28のConstantレジスタが使える。
このレジスタはシェーダが一時的に計算をするのに使うことができる。
頂点シェーダからはvt<n>で、ピクセルシェーダからはft<n>でアクセスする。<n>はレジスタのインデックス番号だ。
頂点シェーダで8、ピクセルシェーダで8使うことができる。
Outputレジスタは頂点シェーダとピクセルシェーダが計算の結果を記録する場所だ。頂点シェーダの出力は頂点のクリップスペース位置で、ピクセルシェーダではピクセルの色だ。
頂点シェーダからはopで、ピクセルシェーダからはocでこのレジスタにアクセスする。
もちろん頂点シェーダでもピクセルシェーダでもOutputレジスタは1つだ。
このレジスタは頂点シェーダからピクセルシェーダへデータを送るのに使われる。送られるデータはGPUによって適切に補間されるので、ピクセルシェーダは処理中のピクセルの正しい値を受け取る。
この方法で送る典型的なデータは頂点の色やテクスチャのUV座標だ。
このレジスタにはv<n>でアクセスできる。<n>はレジスタ番号だ。
8つのVaryingレジスタを使うことができる。
Texture SamplerレジスタはUV座標に基づいてテクスチャから色の値を拾うのにつかわれる。
使用されるテクスチャはActionScriptのContext3D::setTextureAt()メソッドを通して指定されたものだ。
texture Samplersはfs<n> <flags>でアクセスされる。<n>はSamplerのインデックス、<flags>はサンプリングがどのように作られるかを指定する1つもしくは複数のフラグの集まりだ。
<flags>はコンマで区切られた文字の集まりで、次のように定義されている。
たとえばミップマップがなく、線形フィルタリングの普通の2DテクスチャをTemporaryレジスタft1に以下のようにしてサンプリングできる。
tex ft1, v0, fs0 <2d,linear,nomip>
(Varyinfレジスタv0には補間されたテクスチャUV座標が入っている)
シェーダ例に戻ってその命令を読み解いてみよう。
VertexBufferにオフセット0に頂点の座標、オフセット3にテクスチャのUV座標がすでにセットされた頂点を仮定する。
頂点シェーダでは頂点の位置をクリップスペースに変換したい。またUV座標をピクセルシェーダに送りたい。
これは以下のコードで実現できる。
m44 op, va0, vc0 // pos to clipspace mov v0, va1 // copy uv
1行目は頂点入力va0とモデルスペースからクリップスペースへの変換をするようActionScriptからConstantレジスタ0のvc0に書いた行列の4*4行列積を行う。
この行列はシェーダに以下のようにすれば書き込める。
Context3D::setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, matrix, true );
シェーダの2行目で頂点のUV座標データを、補間しピクセルシェーダに送れるようVaryingレジスタ0のv0にコピーする。
ピクセルシェーダではテクスチャをサンプリングし、出力に色をコピーしたい。
tex ft1, v0, fs0 <2d,linear,nomip> mov oc, ft1
この頂点シェーダとピクセルシェーダで3Dモデルを2Dスクリーンに変形しテクスチャマッピングを行うことができる。
以上が"はじめての頂点シェーダ/ピクセルシェーダ"だ。
(iFlash "My Name is AGAL, and I Come from Adobe – Part2"に続く)
---
まあ前回の記事は大体あってたのかな。
再確認:レジスタの1成分は32bit(レジスタは4成分なので128bit)
新発見:".r"でもアクセスできる
3Dの変換行列などの知識があればそこまで難しくなさそう。ないけどね。
Author:9ballsyn
ActionScriptについて
最近はMolehill