すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

oFでshader勉強中「2.vertex shaderとfragment shader」

前回のおさらい

  • oF * shaderのプログラムの流れ(oF編)をみた.
  • setupでshaderを読み込み.
  • drawではshader.begin()/end()の間に通常の描画コードを記述.

たったこれだけで,すてきなshaderライフがまっている.便利なものだ.
しかし,そもそもshaderって具体的にはどんな処理をするものなのだろう?正直よくわかってないぞ?

というわけで,今回はサンプルプログラムで使われている2つのshader, vertex shaderとfragment shaderというのがどのような処理をするものなのかについて調べてみた.

shaderのいろいろ

前回のコード.setupの中では,

shader.load("shaders/noise.vert", "shaders/noise.frag");

というのがshader読み込み部っぽかった.loadっていってるし.ここで

「...なんで二つもファイル読みこんでんの...?」

というのが疑問の始まり.shaderってもしかしていろいろ種類があるのだろうか..??

よし,google先生に聞いてみよう.

〜〜〜勉強中〜〜〜

shaderについて調べてわかったことだが,一言にshaderといっても,やはりその種類には様々なものがあるらしい.そもそも(programmable)shaderとは「CPUの処理をGPUに任せるもの」であるから,まぁ様々な機能に分けられるってのはわかる話だ.
GPUでは,いくつかの目的に特化した処理を順にをおこなうことで,目的とする出力を計算している.これをレンダリングパイプラインというらしい.イメージとしてはいくつかのモジュールを組み合わせて最終出力を決定するという感じ.programmable shader以前はここで利用される各shaderは固定であったため,インタラクティブな表現が難しかったようだ.

んでこのモジュールとしては様々なshaderがあるとのこと.例えば頂点情報を扱うvertex shader,画面上の色を決定するfragmant shaderをはじめとして,頂点数を増やすgeometry shader,テッセレーションのためのhal shaderやらdomain shaderやら.なんかよくわかんない世界が広がっている,ということがわかった(笑).

ま,とりあえず基本はvertex shaderとfragmant shaderらしい.
てことで,それぞれどんな情報を扱うのか.もう少し詳しくみていこう.

vertex shader

vertex shaderは別名『頂点シェーダ』というらしい.別名も何もそのままである.我らがwikipediaによると,

バーテックスシェーダはオブジェクトの頂点データを使って算術的に操作が行われる3D環境のオブジェクトに特殊効果を加えるために使うグラフィック処理機能である。各頂点は多くの数値で定義できる。例えばXYZ座標を使って3D環境内の位置を定義する。頂点はまた色、テクスチャ、光源といった特徴を定義できる。バーテックスシェーダは実際にはデータの形式を変更しない。データの値を単純に置き換えることにより、異なる色、異なるテクスチャ、空間内の異なる位置などを伴って頂点が現れる。
バーテックスシェーダはGPUで並列処理されるので、CPUで処理するよりも処理が速くなる。

うむ.なるほどわからん.

さらにあれこれ調べた結果,頂点シェーダは『ワールド座標における3Dオブジェクトの座標値』を『カメラ座標における座標値』に変換するもの.というイメージが近そう.

projection matrixを使えばワールド座標系からカメラ座標系にマップすることができる.しかし,頂点シェーダは必ずしも3Dモデルを正確に2次元に落とし込むことだけがその機能というわけではないようだ.たとえば横方向に伸ばすだとか.頂点の疎密を作るとか,様々な効果を適用することができる.これにより,ある種”歪んだ”映像を作り出すことが可能となる.

頂点シェーダとは,『ワールド座標系からカメラ座標系への自由な写像関数』みたいなもの,といえそうだ.

fragment shader

fragment shaderはpixel shaderとも呼ばれるshader.いまいちどwikipediaによれば、

ピクセルシェーダはピクセルを操作する機能であり、基本的には頂点シェーダからの情報を元にテクスチャを合成したり表面色を適用したりする。これをプログラミングし、GPUで実行することにより、バンプマッピング等のより高度なエフェクトをCPUですべて実行するよりもはるかに高いパフォーマンスで実現できる。このことから転じ、ピクセルを操作するGPU用プログラム自体をもピクセルシェーダと呼ぶようになったが、厳密には誤用・誤称である。

らしい.さっきよりはイメージつきやすいかな.機能的にフラグメント、よりピクセルの方がわかりやすい気がする.

も少し調べた上での自分の理解としては「画面上の各ピクセルに対し、カメラ座標系を元にその色を決定するもの」といった感じだろうか.カメラから見た物体の座標がわかるので、画面のどこに何が見えているかわかるよね?環境中のライト位置とかもわかったら,対応するピクセルの色が計算できるよね?というイメージ.処理を加えることでぼかしとか光沢とかを作れるみたい.

簡単なGLSLコード

では、紹介した2種類のshaderの基本的なコードをみてみよう.今回は構造理解のため、shaderとしての処理はほとんどしていないのであしからず.なお,これらのコードはDavid WolffによるOpenGL 4.0 shading languageより拝借.

vertex shader

レンダリングパイプラインにおいては,VBOに格納された頂点情報はまずvertex shaderに渡される.例えば次のようなコードだ.

in vec3 VertexPoition;
in vec3 VertexColor;

out vec3 Color;

void main(){
  Color = VertexColor;
  gl_Position = Vec4(VertexPosition, 1.0);
}

GLSLにおいては,各要素がfloatのベクトル型(vec2,vec3,vec4)を使うことができる.そしてin/out修飾子は,それぞれの変数がshaderに対する入力変数,および出力変数であることを示す.実際のOpenGLプログラムと連携する際にはshaderをコンパイルしたり,各変数がバッファ中のどの領域に相当するか等を指定する必要があるが,今回はそこは割愛.
このコードで行っていることはとても単純である.shaderは与えられたVBOの各頂点に対し,main関数を実行することに注意.各頂点に対し,その属性をvec3型の2変数,VertexPositionとVertexColorを入力していると考える.mainの中ではこれらの属性を用いた処理を行う,まずはfragment shaderに渡す変数Colorに,頂点属性のVertexColorを渡している.今回は値をただ流しているだけ,と考えて差し支えない.次の行では,glに対し渡す頂点の座標情報をvec4として格納している.こちらも今回は与えられたvec3をそのまま流しているだけ.vec4になっているのは,移動回転行列を扱う際にmat4と積算できるようにするためであろう.

このようにvertex shaderではglに対し渡す頂点座標情報と,必要であればfragment shaderに渡すための変数を準備している.


fragment shader

vertex shaderを経て,GPUは次にfragment shaderの処理を行う.例えば次のようなコード.

in vec3 Color;

out vec4 FragColor

void main(){
  FragColor = vec4(Color, 1.0);
}

うむ.これまたシンプルだ.

fragment shaderでは,画面上の各画素についてmain関数の処理が行われる.この際vertex shaderから渡された値を用いることができ,またその出力はout修飾子を用いた変数に格納する.今回の例では,vertex shaderから得たvec3 Colorの情報を用い,出力としてアルファチャンネルを1.0にしたColorをFrag Colorに格納,出力している.

まとめ

レンダリングパイプラインにおいて、3Dオブジェクトの頂点情報はまずvertex shaderに入り,カメラからの座標値に変換される.そしてその情報はfragment shaderに入り,画面上の対応するピクセルの出力色を決定し,frame bufferに格納する.

ふむふむ.ごく簡単に見ただけではあるが,いろんなことができそうな気がしてくる.まだ「こんなコード書くとこーゆー処理になるよ!」というテクニックのイメージは全くないがので,いろいろなエフェクトを勉強していきたい.

いやしかし,気づいたら二回目にしてまったくoFの要素がなくなったw 次回は勉強がてら,サンプルに含まれるshaderのコードリーディングをしていきまーす.

リレーは次はももえさま!よろしく〜