いい加減AGALでシェーダを書くのが辛くなってきたので、
FlashのStage3Dで使えるシェーダ言語を作りました。
ついでにレイトレしてみました。
>> Open ray tracing demo (Required Flash Player 16 or later)
>> View source code
何ができるの?
OGSL – Oimo Graphics Shading Language を使うと
Stage3DのシェーダプログラムがGLSLっぽく書けます。
アセンブリと格闘する日々とお別れできます。
早速試してみる
ここからソースコードをダウンロードしてソースフォルダに配置する。
Stage3Dが使えるように設定後Sample.asを起動する。
ぐるぐるが表示されればOK。下のほうにあるOGSL_SOURCEをいじってみましょう。
設定とかいいから…
今すぐ試したい方はこちらのオンラインエディタをどうぞ。
フラグメントシェーダを適当に編集してコンパイルボタンを押しましょう。
uniform変数をいじるとエラーになるので注意。
言語概要
ActionScript3.0に似てます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
// 最初にvaryingレジスタを設定 varying pos:vec2; // 頂点シェーダのプログラム program vertex { // 頂点シェーダ内でattributeレジスタを設定する attribute position:vec3; // uniformを使って外部定数を宣言する uniform matrix:mat4x4; // main関数からプログラムが実行される function main():void { // output(同次座標系対応なのでvec4型)に座標変換後の頂点座標を代入する output = mul(matrix, vec4(position, 1)); // varyingレジスタに値を代入する pos = position.xy; } } // フラグメントシェーダのプログラム program fragment { // uniformを使って外部定数を宣言する uniform mousePos:vec2; uniform time:float; // main関数からプログラムが実行される function main():void { // ローカル変数を型付きで宣言する // 型にはfloat, vec2, vec3, vec4, mat3x4, mat4x4が利用可能 var dist:float = distance(mousePos, pos); var brightness:float = max(1 - dist, 0); // 宣言した関数を呼び出す var theta:float = calcTheta(dist); // float型はいろんな型に自動変換される var color:vec3 = 0; // コンポーネントはxyzwとrgbaのどちらでもアクセス可能 color.r = sin(theta * 2.3); color.g = sin(theta * 2.5); color.b = sin(theta * 2.7); /* color.x = sin(theta * 2.3); color.y = sin(theta * 2.5); color.z = sin(theta * 2.7); */ // output(RGBAなのでvec4型)に色を出力する color = color * 0.5 + 0.5; output.rgb = color * round(brightness * 16) / 16; } // 返り値ありの関数を宣言 function calcTheta(dist:float):float { // 名前が被る関数外のレジスタにはthisを使ってアクセス可能 var time:float = this.time; // returnで値を返す。return文は関数の最後にしか書けないので注意! return dist * 3 + time; } } |
基本的なところをコメント付きで載せてみました。
丁度AS3とGLSLの中間的な文法なのが分かるかと思います。
次から細かい部分を見ていきます。
型について
float, vec2, vec3, vec4, mat3x4, mat4x4, texture 型があります。
texture型はフラグメントシェーダの外部定数としてのみ使えます。使い方は後述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// hoge == (1, 2, 3) となる var hoge:vec3 = vec3(1, 2, 3); // fuga == 1 となる var fuga:float; fuga = hoge.x; // fuga == 2 となる fuga = hoge.y; // piyo == (2, 1) となる var piyo:vec2; hoge.xy = vec2(1, 2); piyo = hoge.yx; // piyo == (2, 1) となる piyo.yx = hoge.xy; // Error! vec2にvec3を代入できない piyo = hoge; // piyo == (0, 0) となる。floatは型に合わせて自動変換される fuga = 0; piyo = fuga; // imo == (1, 2, 3, 4) となる var imo:vec4; piyo = vec2(1, 2); imo = vec4(piyo, 3, 4); // imo == (4, 3, 2, 1) となる imo = vec4(4, 3, piyo.yx); // imo == (1, 4, 9, 16) となる。行列を含む全ての型で加減乗除は成分ごとに行われる imo = vec4(1, 2, 3, 4); imo = imo * imo; |
型の扱いの雰囲気が掴めたでしょうか。
続いては行列です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// hogeは4x4の単位行列になる var hoge:mat4x4 = mat4x4(1); // fugaはhogeの1行目、すなわち fuga == (1, 0, 0, 0) となる var fuga:vec4; fuga = hoge[0]; // fugaはhogeの3行目、すなわち fuga == (0, 0, 1, 0) となる fuga = hoge[2]; // hogeの2行3列目の要素を1にする hoge[1][2] = 1; // fuga == (0, 1, 1, 0) となる hoge = mat4x4(1); hoge[1][2] = 1; fuga = hoge[1]; // piyoは3x4の単位行列(3x3単位行列+4列目のゼロ平行移動成分)となる var piyo:mat3x4; piyo = mat3x4(1); // fuga == (0, 1, 0, 0) となる fuga = piyo[1]; // 行ごとにvec4を用いての初期化も可能 piyo = mat3x4( vec4(2, 0, 0, 1), vec4(0, 2, 0, 1), vec4(0, 0, 2, 1) ); |
mat3x3はこの辺の事情によりありません。mat3x4を使ってください。
関数について
main関数のほかにも関数を定義して呼び出せます。ただし再帰的な呼び出しはできません。
また、return文を書く場合は必ず関数の最後にする必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
function main():void { // piyo == (3, 2) となる var piyo:vec2 = hoge(1); } function hoge(foo:float):vec2 { var bar:vec2 = vec2(1, 0); // if (foo > 0) return bar; // Error! 関数の途中でreturnできない // 関数の呼び出し後にbarが変更される。fooは変更されない fuga(foo, bar); return bar; } // 引数の変数名の直前に&を付けると参照渡しとなり、 // 関数内での変更が呼び出し元の変数に反映される function fuga(foo:float, &bar:vec2):void { // 値渡しなので呼び出し元のfooは変更されない foo += 1; // 参照渡しなので呼び出し元のbarが変更される bar += foo; // hoge(foo); // Error! 再帰的呼び出しはできない } |
AS3的にはやや破格の文法ですが参照渡しもできます。通常は全て値渡しです。
条件分岐
AS3と同じように使えます。
条件に使える演算子は >, >=, <, <=, ==, !=, ||, && のみ。優先順位もAS3と同じです。
否定(!)は使えないので注意。
また、条件文にfloat型を指定することができ、その場合は != 0 の判定がなされます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var hoge:vec2 = vec2(1, 0); if (hoge.x > hoge.y) { // ifブロック。実行される hoge.x *= 2; if (hoge.x != hoge.y) { // 入れ子もOK。あまりやるとAGALの制限に引っかかる hoge.x = hoge.y; } } else hoge.x = 0; // elseステートメント。実行されない if (hoge.x) { hoge.x += 1;// 0でないので実行される } if (hoge.y) { hoge.y += 1;// 0なので実行されない } |
ループ
実はAGALにはループ命令がありません。
そのためOGSLでのループは静的ループのみで、全て強制的に展開されます。
ループを多用すると命令数の上限にわりとすぐ引っかかりますのでご注意を。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var hoge:float = 0; loop (3) { hoge += 1; // 3回実行される loop (4) { hoge += 1; // 3 * 4 = 12回実行される } } // Error! ループ回数に変数や外部定数は使用できない loop (hoge) { // ... } // これならOK。ただし展開は20回行われるので注意 var count:float = 0; loop (20) { if (count < hoge) { // ... } count += 1; } |
continue, break文はありません。頑張りましたが階層をぶち壊す系の命令は
どうしても技術的に無理でした…。AGALにジャンプ命令が欲しい。
テクスチャを利用する
tex2DまたはtexCube関数を使うとテクスチャをサンプリングできます。
AGALの制約上、uniformで宣言したテクスチャは全てサンプリングしなくてはならず、また、
条件分岐の内部でのサンプリングはできなくなっています。
1 2 3 4 5 6 7 8 9 10 11 12 |
program fragment { uniform tex1:texture, tex2:texture; function main():void { // バイリニアフィルタリング、繰り返しありでサンプリング var color1:vec4 = tex2D(tex1, uv, linear, nomip, repeat); // トリリニアフィルタリング、繰り返しなしでサンプリング var color2:vec4 = tex2D(tex2, uv, linear, miplinear, clamp); // 混ぜ合わせてみる output = mix(color1, color2, 0.5); } } |
フラグメントシェーダの強制終了
discard文を使うと色を出力せずにフラグメントシェーダを
途中で終了させることができます。
1 2 3 4 5 6 7 8 9 10 |
program fragment { uniform hoge:float; function main():void { // hogeが1以上なら強制終了 if (hoge > 1) discard; // そうでなければ青色を出力 output = vec4(0, 0, 1, 1); } } |
ビルトイン関数一覧
引数の型は指定がない場合は任意ですが、異なる型を渡すと怒られることあります。
(例えばmin関数の第1引数にvec2型を、第2引数にvec3型を渡すとエラーになるが、
第1引数にfloat型を、第2引数にvec3型を渡した場合は、floatがvec3に自動変換されるため
エラーは生じない)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
// 成分ごとに a と b のうち小さいほうを返す function min(a:*, b:*):* // 成分ごとに a と b のうち大きいほうを返す function max(a:*, b:*):* // 成分ごとに x が threshold 以上なら 1、そうでないなら 0 を返す function step(threshold:*, x:*):* // 成分ごとに a < b なら1、そうでないなら 0 を返す function lessThan(a:*, b:*):* // 成分ごとに a <= b なら 1、そうでないなら 0 を返す function lessThanEqual(a:*, b:*):* // 成分ごとに a > b なら 1、そうでないなら 0 を返す function greaterThan(a:*, b:*):* // 成分ごとに a >= b なら 1、そうでないなら 0 を返す function greaterThanEqual(a:*, b:*):* // 成分ごとに a == b なら 1、そうでないなら 0 を返す function equal(a:*, b:*):* // 成分ごとに a != b なら 1、そうでないなら 0 を返す function notEqual(a:*, b:*):* // 成分ごとに x を min 以上 max 以下にクランプして返す function clamp(x:*, min:*, max:*):* // 成分ごとに x が min 以下なら 0、x が max 以上なら 1、 // min 以上 max 以下なら 0 ~ 1 のエルミート補間を返す function smoothstep(min:*, max:*, x:*):* // 成分ごとに a の b 乗を返す function pow(a:*, b:*):* // a と b の内積をfloat型で返す function dot(a:*, b:*):float // a と b の外積をvec3型で返す function cross(a:vec3, b:vec3):vec3 // a を b で、あるいは b を a で変換した結果を返す // a と b のうち一方はvec3型かvec4型、もう一方はmat3x4型かmat4x4型でなければならない // vec4型とmat4x4型を渡した場合は返り値はvec4型、そうでなければ返り値はvec3型になる function mul(a:*, b:*):* // a と b の距離をfloat型で返す function distance(a:*, b:*):float // x を normal を法線ベクトルとして反射させて返す // normal は 長さが 1 でなければならない function reflect(x:*, normal:*):* // x を normal を法線ベクトル、eta を比屈折率として屈折させて返す // x および normal は長さが 1 でなければならない function refract(x:*, normal:*, eta:float):* // 成分ごとに a の b による剰余を返す function mod(a:*, b:*):* // 成分ごとに a と b を 1 - ratio : ratio で補間して返す function mix(a:*, b:*, ratio:*):* // x の長さをfloat型で返す function length(x:*):float // x を正規化して返す function normalize(x:*):* // 成分ごとに x の平方根を返す function sqrt(x:*):* // 成分ごとに x の平方根の逆数を返す function rsqrt(x:*):* // 成分ごとに x の 2 を底とする対数を返す function log2(x:*):* // 成分ごとに 2 の x 乗を返す function exp2(x:*):* // 成分ごとに sin(x) を返す function sin(x:*):* // 成分ごとに cos(x) を返す function cos(x:*):* // 成分ごとに tan(x) を返す function tan(x:*):* // 成分ごとに x の絶対値を返す function abs(x:*):* // 成分ごとに x を 0 ~ 1 にクランプして返す function saturate(x:*):* // 成分ごとに x の小数部分を返す function fract(x:*):* // 成分ごとに x の小数部を切り捨てた整数を返す function floor(x:*):* // 成分ごとに x の小数部を切り上げた整数を返す function ceil(x:*):* // 成分ごとに x の小数部を四捨五入した整数を返す function round(x:*):* // 二次元テクスチャをサンプリングする // flags には // フィルタリングに nearest, linear, anisotropic2x, anisotropic4x, anisotropic8x, anisotropic16x を // ミップマップに nomip, mipnearest, miplinear を // 繰り返しに repeat, clamp, repeat_u_clamp_v, clamp_u_repeat_v を // それぞれ指定可能 function tex2D(tex:texture, coord:vec2, ...flags):vec4 // キューブテクスチャをサンプリングする // flags には // フィルタリングに nearest, linear, anisotropic2x, anisotropic4x, anisotropic8x, anisotropic16x を // ミップマップに nomip, mipnearest, miplinear を // それぞれ指定可能 // 注意:nomipの場合でもキューブテクスチャはミップマップが適用済みのものを使用しなければならない function texCube(tex:texture, coord:vec3, ...flags):vec4 |
最適化について
AGALで書き出すときに自動で最適化してくれますが、過度な期待は禁物です。
最適化内容は、コピー伝播、不要命令削除、命令並列化、一時レジスタ数削減、
定数畳み込みなど。あまり賢くないので共通式削除とかはやってくれません。
よくありそうな質問
Q. 他のライブラリへの依存は?
A. AGALを吐き出すだけなら依存なし。
AGALのアセンブルにはAGALMiniAssembler等が必要。
Q. GLSL2AGAL使ったら?
A. よくバグるしAGAL2非対応なのでお勧めしません。
Q. int型はないの?
A. ないです。float型を使ってください。
Q. boolean型はないの?
A. 実はfloat型がboolean型の代わりになったりします。お試しあれ。
Q. MRTとか偏微分命令は使えないの?
A. 使えません。どうしても使いたい場合は出力されたAGALをいじってください。
Q. AGAL2でしか出力できないの?
A. 条件分岐(if文)を使わなければAGAL1でも動きます。
Q. 他のライブラリとあわせて使いたい!
A. AGALが使えるライブラリなら一緒に使えます。
各種レジスタのindexはOGSLクラスから取得可能。
Q. バグを発見したけどどうすれば?
A. コメント欄に書くかTwitterでreplyを送ると直すかもしれません。
Q. Stage3Dってどう設定するの?
A. ここでは触れないので各自で調べてください。
Q. 改造していい?
A. MITライセンスなのでご自由にどうぞ。
Q. 他にどうしても分からないことが…
A. コメント欄に書くかTwitterでreplyを送ると答えるかもしれません。
Q. ソース汚い
A. ごめんね
おわりに
最初は自分用に作ってましたが、
どうせならということでまとめてライブラリ形式にしてみました。
何かの役に立てば嬉しいです。
そろそろゲーム作りたい。
元気ですかッ!!
元気です。生きてます。