シェーダの記述と基礎
GLSL について知る
WebGL では固定機能パイプラインが利用できません。これについては以前のテキスト(レンダリングのための下準備)でも少し触れましたね。
その代わりに、いわゆるプログラマブルシェーダの一種であるシェーダ言語が実装されています。それが GLSL ( OpenGL Shading Language )です。
GLSL は OpenGL との親和性を持つシェーダ記述言語で、C 言語ライクな独自の文法によって記述します。WebGL の難点の一つがこの GLSL であり、GLSL を理解しなければそもそもレンダリング自体を行なうことができません。WebGL の基本プログラムの知識に加えて、GLSL の知識も必要になるのでちょっと大変ですね。
ただし、基本的なことをやるだけであればそれほど難しくはありません。それに、慣れてしまうとシェーダを自分で記述できることの利点のほうが大きくなっていきます。ここはひとつ、焦らずどっしりと構えましょう。
シェーダの役割
GLSL の知識をゼロから全て理解せずとも、基本的なところだけを押さえておけばある程度のことはできます。とりあえず、細かいことは後回しに、基本的な部分だけをざっくりと理解しましょう。※ここからは理解しやすさを最優先にしたテキストを書きますので、一部簡略化した記述をする可能性もあります。それを理解したうえで読んでください。
まず、WebGL には頂点シェーダとフラグメントシェーダの二種類のシェーダがありましたね。いずれも、GLSL を使って記述することができます。頂点シェーダとフラグメントシェーダは相互に関係性を持っていて、どちらが欠けてもいけませんが原則として先に呼ばれるのは頂点シェーダです。
頂点シェーダには、頂点に関する情報の全てを渡すことができます。たとえば、頂点の位置情報や、頂点が持つ法線、テクスチャ座標、頂点の色など、頂点に関する情報の全てをシェーダに渡すことが可能です。ここで、どんな情報をシェーダに渡すのかは基本的に自由です。この柔軟性こそがプログラマブルシェーダの利点ですね。ただし、渡す情報は自由とは言っても、考えてみれば至極当然のことですが頂点の位置情報だけは必須です。頂点の位置がわからないのにモデルを描画することはできませんものね。
さて、頂点シェーダの次はフラグメントシェーダです。頂点シェーダは文字通り頂点に関するあらゆる情報を受け取り、最終的に頂点をどのように処理するのかを決定します。一方、フラグメントシェーダは画面にどんな色を出力すればいいのかを決めることができます。そもそも、フラグメント( fragment )とは、直訳すると[ 断片 ]や[ 欠片 ]といった意味を持つ単語です。要するに、画面上のピクセル(画面上の最も小さな断片ですね)それぞれに対して最終的に出力される色を操作してくれるわけです。
ものすごくざっくりとですが、頂点シェーダは頂点に関する情報を、フラグメントシェーダは画面上の色の情報を、それぞれ処理するものだと考えるとわかりやすいかもしれません。
GLSL 記述の基礎
さて、シェーダがなにをやってくれるのかなんとなく理解できましたか。
ちょっと駆け足ですが、次は GLSL の記述方法について見ていきます。
まず、頂点シェーダにしてもフラグメントシェーダにしても、必ず main
という関数を定義し、そのなかにどんな処理を行なうのかを記述します。そして、頂点シェーダでは gl_Position
という組み込み変数に頂点データを渡さなければなりません。
たとえばものすごく単純な頂点シェーダの記述例は以下のようになりますね。※あくまでも例ですのでこのままは使えませんが……
attribute vec3 position;
void main(void) {
gl_Position = position;
}
さて、ここで変な単語が出てきましたね。最初の行にある attribute
というのはなんなのでしょう。
実は、この attribute
という修飾子を付けて宣言された変数(上記の場合なら position
ですね)が、頂点の情報をシェーダ側で受け取る変数になります。つまり、WebGL のプログラム側で position という名前を付け、あらかじめデータを仕込んでおきシェーダに渡すわけです。
もう少し突っ込んで説明すると、 attribute
修飾子は、頂点ごとに異なるデータを受け取るためのものです。たくさん存在する頂点は、それぞれ位置情報などが異なっていますよね。そういった頂点ごとに異なるデータを受け取るための仕組みが attribute
修飾子付き変数なのです。
座標変換も GLSL で
頂点シェーダでは、頂点に関する処理を行なうと先ほどから書いていますが、頂点に関する処理と言えば忘れてはいけないのが座標変換ですね。モデル変換・ビュー変換・プロジェクション変換の三つの変換を行なうのも頂点シェーダの仕事の一つです。
基本的にはどこまでを頂点シェーダに任せるのかは自由ですが、WebGL のプログラム側でモデル・ビュー・プロジェクションのそれぞれの行列を生成して掛け合わせておき、最終的に出来上がった座標変換行列を頂点シェーダに渡すのが(たぶん)一般的です。
さて、ここで考えてみてください。
この座標変換行列、どうやって頂点シェーダに渡せばいいのでしょうか。
先ほどの attribute
を使いますか? しかし、 attribute
は頂点ごとに異なる情報を渡すのに使うと先ほど書きましたね。座標変換行列はあらゆる頂点に対して、一律で同じものが使われることになります。ですから attribute
修飾子を使うのはおかしいですね。
そんなときに使われるのが、 uniform
修飾子です。この uniform
修飾子を使うと、全ての頂点に対して一律に処理される情報を渡すことが可能になります。そのことを踏まえ、先ほどのシェーダの記述例を手直ししてみましょう。※これもあくまでも一例ですのであしからず
attribute vec3 position;
uniform mat4 mvpMatrix;
void main(void) {
gl_Position = mvpMatrix * position;
}
ここで出てきている mvpMatrix
というのは、モデル・ビュー・プロジェクションのそれぞれの変換行列を掛け合わせた座標変換行列です。その座標変換行列を uniform
修飾子付きの変数に WebGL 側から渡してあげるわけですね。
GLSL で使えるデータ型について
先ほどから登場しているシェーダの記述例の中で、修飾子に続けて[ vec3 ]とか[ mat4 ]などと書かれていますね。これはいわゆる変数の型を表しています。代表的なものだけ、ここで簡単に触れておきます。
vec* という記述はベクトルを表していて、* の部分に入った数字によって、その要素の数を定義できます。2 ~ 4 の範囲があり、vec2 であれば二つの要素を持つベクトルという意味になります。
mat* というふうに書く場合は、正方行列を表しています。ベクトルと同様に 2 ~ 4 の範囲で指定でき、たとえば mat3 であれば 3 x 3 の正方行列を表します。
そのほか、int は符号付の整数型、float は符号付の浮動小数点数型、bool は真偽値を表す条件型を表し、これは C 言語などで使われている一般的な意味と同じです。vec* と mat* については、それぞれの要素は全て浮動小数点数型になっています。
フラグメントシェーダとの連携
ちょっと長いテキストですが、まだもう少し続きます。
さて、 attribute
と uniform
の二つの修飾子については理解できたでしょうか。GLSL には、もう一つ、重要な修飾子があります。それが varying
修飾子付き変数です。
この varying
修飾子ですが、使い道は頂点シェーダとフラグメントシェーダの橋渡しです。たとえば、描画するモデルを半透明で表示したいと考えた場合、どういったプログラムを書くでしょうか。
いろいろな方法がありますが、一般的には、頂点に色情報を付加しておき、その透明度を変化させることでモデルを半透明にしたり、あるいは完全に透明にしたりします。このとき、頂点が持つ色の情報を、画面上の色を操作する役割を持つフラグメントシェーダに、どうにかして渡す必要が出てきますね。
そんなときにこそ、 varying
が活躍します。それを踏まえて、再度シェーダの記述例を手直ししてみます。今度は、頂点シェーダだけではなくフラグメントシェーダの記述例も一緒に見てみます。
まずは頂点シェーダ。
attribute vec4 position;
attribute vec4 color;
uniform mat4 mvpMatrix;
varying vec4 vColor
void main(void) {
vColor = color;
gl_Position = mvpMatrix * position;
}
続いて、 varying
修飾子付きの変数 vColor
を受け取るフラグメントシェーダ。
varying vec4 vColor;
void main(void)
{
gl_FragColor = vColor;
}
このように、頂点シェーダからフラグメントシェーダにデータを渡したい場合には varying
修飾子付きの変数を使います。そして、頂点シェーダでは必須だった gl_Position
へのデータの代入と似たような形で、フラグメントシェーダにおいては gl_FragColor
にデータを代入します。
頂点シェーダの場合とは違い、フラグメントシェーダでの gl_FragColor
は絶対にデータを代入しなければならないもの(つまり必須)ではありません。ただ、普通は何かしらの色を出力することになるので、基本的には gl_FragColor
が必要になります。
まとめ
かなり長いテキストになりました。内容もそれなりに濃かったと思うので、一度に理解するのは大変かもしれませんね。
ざっくりと、今回の内容をまとめてみます。
頂点シェーダとフラグメントシェーダは、いずれも GLSL を使って記述することができ、基本的には双方がセットになった形で使います。シェーダ内部には、必ず main
関数を用意し、そのなかにシェーダの行なう処理を記述します。さらに、WebGL 側からシェーダになにかしらのデータを渡したい場合には、特殊な修飾子付き変数を使うのでしたね。
頂点ごとに異なるデータを渡すために使うのが attribute
修飾子付き変数、頂点に対して一律で使われるデータを渡すために使うのが uniform
修飾子付き変数でした。
そして、頂点シェーダからフラグメントシェーダにデータを渡したい場合には varying
修飾子付きの変数を使います。
頂点シェーダでは、組み込みの変数である gl_Position
へのデータの代入が必須であり、フラグメントシェーダにおいては、 gl_FragColor
への代入が必須ではないものの、基本的には行うのが普通です。
GLSL に関しては、正直かなり内容が深くなるので、今回のテキストの内容ではまだまだ表層的な基本の部分しか解説していません。その辺に関してはテキストの進行状況に応じて、追々説明を加えていくことにしますので、まずは今回の内容をしっかり理解しておきましょう。
次回は、頂点バッファについて解説します。