GLSL とはなにかを知ろう
シェーダと GLSL の概念・基礎知識
自己紹介
WebGL に関する開発支援を目的とした入門サイトや、WebGL を利用した事例などを紹介するサイトを運営しています。
イベントへの登壇や書籍執筆など、WebGL 普及のための活動を行っています。
本スクールの開催概要
本スクールは、WebGL などの OpenGL ファミリで主に利用されている GLSL と呼ばれるシェーダ言語を主題に、シェーダの記述に特化した 技術を広く学習することを目的としたスクールになります。
シェーダとはそもそもなんなのか、そして WebGL や GLSL との関係とはどんなものなのか。さらには、シェーダを記述できることの利点や、もちろんシェーダ記述のスキルアップのための基礎から応用までの知識を、総合的に扱っていきます。
本スクールの開催概要
シェーダは大変奥が深く、同時に非常に難しいジャンルでもあります。短い講義時間のなかだけで一度に全てを理解するのは、なかなか大変です。
本スクールでは、最終的に作品を発表する場までを運営側で用意するなどしつつ、受講者の自己研鑚や制作を促進。さらには講師への質問が行いやすい環境をできる限り作るようにしたいと考えて運営を行っています。ぜひ、本講義や、プラスワン講義など、講義の時間のなかだけでなくそのほかの場面でも、気軽に質問など投げかけていただければと思います。
本スクールの開催概要
本スクールの講義が全て修了したあと、来年の 1 月に行う第五回は 受講者全員による発表会 を行う予定です。
正直に書くと、これまでは受講者に対して「作品の提出を強制したことは無かった」のですが、今回は思い切ってそのようなスタイルで運営することにしました。
これには理由がいくつかあるのですが……基本的には、このような取り組みが確実にみなさんの糧になると私が信じるからです。
本スクールの開催概要
明確なアウトプットの場というゴールがあることによって、より自分自身の表現を具体化できるということは誰しもあると思います。
ぜひ、自分がどんなものを作れるようになりたいのか、どんな表現をしてみたいのか、少しずつでも構いませんのでスクールを通じてイメージできるように意識しながら受講してみてください。
私も精一杯がんばりますし、アドバイスもしますので!
講義を始める前に
今回のスクールは、GLSL に特化した短期スクールとなります。
文字通り、シェーダや GLSL についてがメインテーマになりますので、WebGL や JavaScript に関して、少々説明を省いて進めていく形です。もし、通常の JavaScript の記述方法や、WebGL 実装部分での疑問点で詰まってしまいそうな場合は、遠慮無く、質問していただいて大丈夫です。
特に普段 JS 書かない方はマジで遠慮せずに聞いてね!
講義を始める前に
また、今回は WebGL の基礎スクールではないので、始めからある程度 WebGL 実装の整えられた環境を用意してあります。
同時に、GLSL やシェーダというよりも WebGL の API 特有な処理となるような部分については、本講義内では触れない場合もあることをまずはご理解ください。
全部やってたら時間足りないからね!
講義を始める前に
さらに、オンラインでアクセスすることができる、ブラウザから操作するエディタもあります。場面によっては、こちらを利用してシェーダを書くこともあります。
こちらはよりシェーダだけで書く! という色合いが強いです。
講義を始める前に
これらのエディタでは、GLSL を記述することにしっかり集中できるよう工夫してあります。使い方はまた後で説明しますので、ひとまずいつでもページを開けるようにしておいてください。
また、事前に参加者のみなさんには招待を送っていますが、今回のスクールでは、Slack をコミュニケーションのためのツールとして利用していきます。
講義を始める前に
講師と受講者、あるいは受講者同士などで、気軽にコミュニケーションを取ってもらいたいというのが Slack を導入した意図です。
どうしても、口頭で質問するというのは心理的障壁が大きいと思うのですが、Slack であれば、ログも一定量は残りますし、質問者以外の方もその内容を把握できますし、メリットが多いのかなと思っています。
他の人にとっても有用になるかもしれないので、臆せずどんどん質問しよう!
さあはじめよう!
さて、それでは早速講義を始めていきます。
まずは、シェーダを記述するために最低限必要となる、前提や基礎を固めておきましょう。
後半は実際にみなさんにも手を動かしてコードを記述してもらうフェーズが待っています。まずは概念から押さえていきましょう。
必ずしも暗記する必要はないので楽な気持ちで聞いてください
進化するシェーダ
シェーダ、という言葉には非常に広い意味があり、一言で表現するのは難しいです。
ただ、一般に解釈されているシェーダをあえて端的に表現すると「GPU を利用し高速にグラフィックスを描画するための技術や概念の総称」というふうに言えるかもしれません。
進化するシェーダ
WebGL の登場で、ウェブの世界でも、シェーダの存在は日々大きくなっていっています。これまでのウェブでは、速度の関係からどうしても難しかったような表現が、WebGL の高速な動作によって実現可能になったのですね。
WebGL というと一見 3DCG 専用のように感じるかもしれませんが、これは必ずしも 3D とは限りません。2D の描画であっても、GLSL の高速な動作が役に立つ場面は多いと言えます。
進化するシェーダ
現在では、シェーダは映像表現のためだけに限らず、あらゆる演算処理を担う縁の下の力持ちになりました。
たとえば、流体表現のための、流れや勢いを計算するために使われたり、フラクタルを描画するための計算に使われたり、多くの演算が必要となる様々なシーンでシェーダが活躍しています。最近だと、Google がディープラーニングをブラウザ上で行うためのライブラリを発表しましたが、これも裏では WebGL に関係した技術を使っています。
とは言えやはり、これらの計算の結果も大抵の場合は、最終的にグラフィックスを生成するために使われます。つまり、シェーダの最大の仕事はやはり グラフィックスを描くことに繋がる 場合がほとんどと考えていいでしょう。
進化するシェーダ
シェーダはその役割に応じて、様々な名称がつけられています。
API が変わると微妙に呼び方が変わったりもするので紛らわしいですが、共通して GPU を利用した高速な演算処理が行える場合が多いです。
進化するシェーダ
- 多くの場合において、高い演算能力を誇る GPU を駆使し高速に計算を行う
- グラフィックス生成のあらゆる行程で活躍する
- 役割に応じて○○シェーダ、というふうに名前が付けられている場合が多い
3D API とシェーダ
3DCG を描くためには、多くの計算や行程が必要になります。そして大抵の場合、それらは非常に負荷の大きなものになるため GPU と呼ばれる高速な演算装置を利用することが多いです。
PC であれモバイル端末であれ、近年のデジタルデバイスの多くにはこの GPU がなんらかの形で備わっています。CPU とメモリを共有しているタイプの安価なものから、GPU としての性能に特化したグラフィックボードと呼ばれる専用パーツまで、様々な種類のものがあります。
いいグラボを積んでいれば当然グラフィックス関連処理が高速に。
3D API とシェーダ
そんな GPU を利用するためのインターフェース(API)には、OS の種類や、その用途に合わせていくつかの種類があります。
Windows であれば、DirectX と呼ばれる Windows に最適化された高性能な API が存在しますし、オープンソースで提供されている著名な API として OpenGL などがありますね。これらは非常に低レベルの、よりハードウェア(GPU)に近い部分でアプリケーションとハードウェアとの間を取り持ってくれます。
ソフトウェアを設計する我々エンジニアは、DirectX や OpenGL の使い方を覚えることによって、間接的に GPU の性能を引き出した高いパフォーマンスを誇るアプリケーションを制作することができるわけです。
3D API とシェーダ
CPU と GPU の間を取り持ってくれる API のイメージ
OpenGL と OpenGL ES
OpenGL はその名の通り、オープンな規格として管理されている 3D API で、モバイル向けの OpenGL ES と共に、様々なところで利用されています。
ES のほうはモバイル向けの軽量な OpenGL 実装です
OpenGL と OpenGL ES
スマートフォンにおけるグラフィックス API は事実上 OpenGL ES 一択です。
つまり OpenGL ES が存在しなかったら、スマートフォンで高性能なグラフィックス処理を行うことが難しくなるでしょう。世界標準のオープンな規格である OpenGL のような 3D API はとても貴重です。
例外としては Apple の独自規格 Metal などがあります
OpenGL ES と WebGL
WebGL もやはり、世界標準の仕様に即した API ですが、これはあくまでも JavaScript の API ですね。
内部的には、OpenGL ES の 2.0 のグラフィックス API を踏襲した実装になっています。
GLSL
GLSL(OpenGL Shading Language)は、OpenGL ファミリーで利用できるシェーダ記述言語の総称で、GLSL にも様々なバージョンが存在します。
現状の WebGL で利用する GLSL のバージョンは、GLSL ES 1.0 です。これは OpenGL ES 2.0 相当で利用されているものと同じですね。
GLSL
さて、少し紛らわしい感じになってきました。ちょっと整理しましょう。
OpenGL は、GPU を利用するための 3D API でしたね。そして、その OpenGL のファミリーのなかで、モバイル端末向けに軽量化されたものが OpenGL ES です。さらに、WebGL はこの OpenGL ES をブラウザから制御できるようにしたものであり、中身はほとんど OpenGL ES と同じです。
これらの OpenGL ファミリーでは、シェーダを記述するための専用の言語を用い、それが GLSL と呼ばれているのでした。そしてこの GLSL にも様々なバージョンが存在する、ということになります。
GLSL
ちなみに GLSL は、Unity などでも利用することができますが、Unity には専用のシェーダを記述するためのプラットフォームがあり、GLSL が必ずしも推奨されているわけではありません。
しかし、シェーダの概念そのものは似通った部分がいくつもありますので、GLSL の記述を通して、シェーダそのものに対するスキルを磨いていくことはけして無駄にはならないはずです。最初のうちはあまり難しく考えず、3DCG やシェーダそのものにどんどん慣れていきましょう。
WebGL の素晴らしいところはブラウザさえあれば実行できることです。だからこそ、学習には非常に適した環境だと言えますしどんどん臆せずトライしていくことが大切です。
固定機能パイプラインとプログラマブルシェーダ
そもそも、OpenGL という API が存在するにもかかわらず、それ以外にわざわざ GLSL のような専用のシェーダ記述言語が必要なのでしょうか。
コンピュータグラフィックスの歴史を振り返るとその必要性が見えてきます。
あともう少しだけ、歴史の話をします。
固定機能パイプラインとプログラマブルシェーダ
かつて、OpenGL には、現在シェーダが担っている領域の機能が 全て備わって いました。
ちょっと驚くかもしれませんが、昔は OpenGL には GLSL のようなシェーダ記述に特化した機構は備わっておらず、全て OpenGL 単体で完結するように作られていたんですね。
固定機能パイプラインとプログラマブルシェーダ
この、OpenGL 内に組み込まれていた現在のシェーダに相当する機能を、現在では固定機能パイプラインと呼んでいます。
その呼び方からもわかるとおり、これは機能が完全に固定されているもので汎用性に乏しく、ある程度同じようなことしかできないものでした。
固定機能パイプラインとプログラマブルシェーダ
コンピュータグラフィックスが日々進化していく過程で、固定機能パイプラインのような限定的な機能だけでは、求める結果が得られないようになってきます。そこで生まれたのが GLSL のような、シェーダ機能を自由にプログラマが制御できる機構です。
それまではシェーディングに関することは全て OpenGL(正確には GPU などのハードウェア)が一括して行っており、その領域にプログラマが介入する余地はありませんでした。しかし GLSL のようなシェーダを自由に記述することができる仕組みが誕生したことで、より汎用性の高い、様々な演算や表現を行うことが可能になりました。
固定機能パイプラインとプログラマブルシェーダ
複雑なライティングやブルーム(光の溢れ)などはプログラマブルシェーダだからこそできる
固定機能パイプラインとプログラマブルシェーダ
このように、プログラマの裁量によって自由にシェーディングを制御できるような機構を、プログラマブルシェーダ、というふうに呼びます。
そして、WebGL や OpenGL ES はこのプログラマブルシェーダを用いることが基本です。というより、両者には 固定機能パイプラインは存在しません ので、必然的にシェーダを自前で実装してやる必要があります。
シェーダを自力で実装する必要があるため、若干難易度が高くはなりますが、その分、豊富な表現や制御が可能になっています。
すべて自分でやる
ここまで来ると、やっと本スクールの本題に入ってきます。
つまり本スクールでは、プログラマブルシェーダの記述方法を学習 していきます。環境によっては固定機能パイプラインが肩代わりしてくれるような基本的なことでさえも、WebGL と GLSL を用いる場合は 全て自分で実装しなければなりません。
最初は、これがとても難しいことのように感じられるかもしれませんが、見方を変えてみれば、基本をじっくりしっかり取り組むことができるということでもありますね。
逆に Unity とかは勝手に全部やってくれちゃいます
すべて自分でやる
3D 数学やシェーダの記述は、本当に難しいです。また、奥深すぎてゴールがありません。
しかしその分、ひとつひとつ確実にスキルアップしていくことで、確実にみなさんのなかに価値ある技術が積み上げられていくはずです。
全力でサポートしますので、一緒にがんばっていきましょう。
シェーダの種類
さて、ここからはより深くシェーダについて見ていきます。
まずは、現在一般によく利用されているシェーダの種類にどんなものがあるのか、見ていきましょう。
シェーダの種類
とは言え、ここではみなさんがこれから学習していく予定の、当スクールで登場するシェーダにある程度限った話をします。
先程も少し書きましたが、現在ではシェーダの種類や役割は非常に多岐に渡るのでそれら全てを一度に理解するのは難しいです。
焦る気持ちもあるかもしれませんが、まずはどんなプラットフォーム上でも利用できる基本的なシェーダから徐々に覚えていきましょう。
頂点シェーダ
まず最初に登場するのが 頂点シェーダ です。
このシェーダは、3DCG に欠かすことのできない、頂点の座標変換を主な役割とするシェーダです。
3D プログラミングでは、あらゆるものは頂点によって表現されます。頂点シェーダを利用することで、この頂点の位置や、頂点の持つ様々な情報を自在に制御することが可能になります。
頂点シェーダ
頂点は、3DCG を描画する上で欠かせない、と書きました。ここはあえて詳細を省きますが、頂点がひとつで点を、頂点がふたつで線を、それ以上の個数の頂点を使うことで三角形や四角形を表現するのが、3DCG の一般的な概念です。
頂点シェーダは、この頂点ひとつひとつを制御するためのものです。ですから、頂点の個数=頂点シェーダが実行される回数 となるのが普通です。
描画する頂点が少なければ頂点シェーダの負荷も少なくなる。
頂点シェーダ
頂点が増えれば増えるほど頂点シェーダの実行回数が増える
フラグメントシェーダ
一方、代表的なシェーダとして頂点シェーダと並ぶ存在なのが フラグメントシェーダ です。API の種類によっては、ピクセルシェーダなどと呼ばれることもあります。
このシェーダは頂点シェーダとは役割が異なり、最終的にスクリーンに描かれる ピクセルひとつひとつ を対象とした処理を行います。
フラグメントシェーダ
つまり、画面の解像度が大きい場合、当然フラグメントシェーダによる負荷は大きなものになります。
頂点シェーダは頂点の個数に比例して負荷が増えますが、フラグメントシェーダは実行されるピクセル数が増加するにしたがってパフォーマンスが落ちていきます。両者の違いについてはしっかり理解しておきましょう。
頂点単位とピクセル単位!
WebGL で利用できるシェーダ
- 頂点シェーダ
- 頂点の個数分だけ頂点ごとに実行される
- フラグメントシェーダ
- 描画するピクセル単位で実行される
この違いをしっかり意識できることが非常に重要です
WebGL で利用できるシェーダ
ここまで紹介してきた頂点シェーダとフラグメントシェーダの両者は、WebGL で利用できる二種類のシェーダです。
WebGL と GLSL を使ったアプリケーションでは、このふたつを使いこなすことであらゆる表現を行っていきます。
しかし、その他のプラットフォーム上では、頂点シェーダとフラグメントシェーダ以外のシェーダが備わっているケースもあります。
その他のシェーダ
代表的なところでは、ジオメトリシェーダがあります。
ジオメトリシェーダは、OpenGL ES 3.2 や、DirectX、OpenGL などで利用できるシェーダです。このシェーダは頂点シェーダとフラグメントシェーダの間に割り込むようなタイミングで動作するもので、頂点シェーダから送られてきた頂点の情報を、さらに加工してからフラグメントシェーダへと渡します。
WebGL にはジオメトリシェーダは残念ながらありません
その他のシェーダ
ジオメトリシェーダの他にも、プラットフォームを限定したシェーダはいくつもあります。
たとえば OpenGL には、テッセレーションシェーダやコンピュートシェーダなどのさらなるシェーダが存在します。ほかにも Unity にはサーフェイスシェーダと呼ばれる独自のシェーダが備わっています。
このように、シェーダには様々な役割や実装があり、プラットフォームに応じて独自の仕様を持っている場合が多く、得てして非常に難解な場合が多いです。
その他のシェーダ
シェーダにはたくさんの種類がありますが、その中でも頂点シェーダとフラグメントシェーダ(ピクセルシェーダ)は非常に重要なシェーダだと言えます。
最も基本的な構成のシェーダであり、おおよそどのようなプラットフォームにおいてもこれらに相当するシェーダが備わっているのが普通です。両者をしっかりと理解しておけば、それ以外のシェーダについても理解が深まるでしょう。
ここまでの簡単なまとめ
- シェーダにはたくさんの種類がある
- プラットフォームを限定するものもあり非常に難解な場合が多い
- 頂点シェーダとフラグメントシェーダは特に大切なシェーダ
- WebGL と GLSL の組み合わせの場合は二種類のシェーダを記述可能
グラフィックスパイプライン
さて、GLSL で記述したシェーダはディスプレイに映像が映し出されるまでの過程で、どのように利用されるのでしょうか。
ここでは簡単に、概念図を用いてその概要だけ把握しておきましょう。
グラフィックスパイプライン
これはほんの一部だけを抜粋したものです
グラフィックスパイプライン
アプリケーションから情報を受け取った頂点シェーダは、頂点を指定された情報を元に座標変換しパイプラインの次のステージに送ります。
そして座標変換された頂点は、ラスタライズなどの処理を経てフラグメントシェーダへと渡されます。フラグメントシェーダで着色などが行われたあと、いくつかの行程をさらに経た後にやっとディスプレイに映像が出力されます。
このパイプラインの行程全体をグラフィックスパイプラインと呼び、非常に多くの行程があります。
レンダリングパイプライン、と呼ぶ場合もあります。
グラフィックスパイプライン
かつては、グラフィックスパイプラインには固定機能シェーダが埋め込まれた状態になっており、完全に固定されていました。
このグラフィックスパイプラインのいくつかの行程を、プログラマが自由に実装できるようにしたものが他ならぬプログラマブルシェーダだったんですね。
また、頂点、フラグメント、以外のシェーダについても、基本的にはこのグラフィックスパイプラインの何かしらの処理を置き換えたり、拡張したりするものであるのが普通です。
グラフィックスパイプライン
ここで最も重要なポイントとなるのは、頂点シェーダとフラグメントシェーダのうち、先に呼び出されるのは常に頂点シェーダ であるということ。
頂点シェーダがまず最初に形を作り、そのあとの行程で、色を塗るべきだと判断された場所に対してのみフラグメントシェーダが実行されます。
グラフィックスパイプライン
もし仮に、頂点シェーダで処理した結果、ひとつも頂点が画面上に描かれなかったとしたら……
そのときは、塗るべきピクセルがひとつもないということになりますので、フラグメントシェーダは一度も実行されません。
これは実は地味に重要です!
グラフィックスパイプライン
逆に言えば、画面上の全てのピクセルを頂点やポリゴンが覆っている場合、全てのピクセル上で何かしらのフラグメントシェーダが実行されるわけです。
フル HD の 1920 x 1080 だと、約 214 万ピクセルです。気軽に掛け算をひとつシェーダに追記すると、214 万回の乗算処理が GPU によって処理されることになります。
そう考えると半端ない処理能力ですよね。
ここまでのまとめ
- シェーダが実行される順序は決まっている
- 頂点シェーダが必ず先に実行される
- フラグメントシェーダは頂点が無いところでは実行されない
サンプルを見ながら考えていこう
それでは、GLSL を実際にどのように記述していけばいいのか、その基本をまず知っておきましょう。
まず最初は、サンプル 001 から。
こちらのサンプルは、主に WebGL 側の処理の流れを把握するためのものですので、普段 JavaScript を書いてらっしゃらない方にはちょっと難しいかもしれません。
サンプルを見ながら考えていこう
とは言え、C++ などで同様の実装を OpenGL で組む場合でも、基本的な流れはこれとほとんど同じになります。
Unity 等の場合は、こういった基盤処理は全て Unity 側で担保してくれるので意識することは無い場合がほとんどでしょう。しかし、こういったアプリケーションの下地の部分を知っておくことはけして無駄にはならないので、コメントを参考にしながら、特にその 一連の処理の流れ を把握するようにしましょう。
参考までに、かなり大雑把ですがサンプル 001 でなにが行われているのか箇条書きにしてみると……
サンプル 001
- ウェブページの読み込み完了と同時に処理を実行開始
- まずは WebGL のコンテキストを取得
- コンテキスト経由で様々な WebGL(OpenGL ES)の命令を実行していく
- シェーダは一種のプログラムなのでそのソースコードを読み込んで利用する
- シェーダをコンパイルしてリンクし、プログラムオブジェクトを生成
- プログラムオブジェクトが JavaScript と GLSL の架け橋になる
- 頂点は単なる配列によって定義される
- GLSL 側の変数には ロケーション を通じてアクセスする
WebGL の初期化の流れ
箇条書きにするとどうにも「やることが多すぎ」て、なにがなんだかわかりにくいですね……
でもこれは、どんな 3D API を用いる場合でも 残念ながら同じ です。
最初は途方に暮れてしまいそうになりますが、大事なのは「そのとき理解できなくても気にしすぎない」こと。じっくりと、少しずつ慣れ親しんでいけば大丈夫。きっといずれは、理解できるようになってきます。
まじで最初は特に、理解できないことをネガティブに捉えないことが大事です
WebGL の初期化の流れ
ここから一連の処理の流れを説明するのですが……
まず最初に一番大事なこととして、サンプル 001 を通じて なにを知ってほしいのか を明確にします。
いまから説明することは全て、GPU で動作する GLSL と アプリケーション本体のある CPU が連携するために必要な手順です。
このことをまずしっかり意識してください。
WebGL の初期化の流れ
CPU と GPU は別々のハードウェアなので、相互にやりとりを行うためにはきちんとした手続きが必要です。
わかりやすく言うなら、PC とプリンタを接続したら、ドライバのインストールとかセットアップをしますよね? あれと同じです。
CPU 側にある JavaScript や C++ の実装、あるいは Unity などが、GPU と連携するための中間にいるのが OpenGL や WebGL なのです。
これ地味にだいじ!
WebGL の初期化の流れ
WebGL は、canvas エレメントから取得した WebGLRenderingContext
と呼ばれるコンテキストを経由して様々な処理を行います。サンプルでは、統一して gl
という変数がこの WebGL のコンテキストを表します。
ですから、コードのなかに gl.xxxxx
というように gl.
から始まる記述があったときは、それが WebGL の API をコールしているもの なのだな、と考えるといいでしょう。
gl === WebGL コンテキスト!
WebGL の初期化の流れ
そして WebGL や OpenGL では、シェーダを利用するために GLSL の仕様に沿って書かれたソースコードを取得してシェーダを初期化していきます。
シェーダとは一種の小さなプログラムなので、GLSL のソースコードをアプリケーションでコンパイルする、という手順が必要になります。
コンパイルされたシェーダは、単なる文字列の羅列から、そこで初めて実態のあるシェーダオブジェクトになります。
WebGL の初期化の流れ
さて、シェーダオブジェクトがコンパイルされて生成されても、実はこれでゴールではありません。
シェーダには、最初のほうで解説したとおり頂点シェーダとフラグメントシェーダという、種類の異なるものが存在しますよね。
これらの種類が異なるシェーダは、それぞれにデータの受け渡しなどを行う場合があるため、これらを管制する役割のものが必要です。
WebGL の初期化の流れ
シェーダ同士、あるいはシェーダと CPU とを結ぶ役割を持つ管制塔……
これを プログラムオブジェクト と呼び、コンパイル済みのシェーダオブジェクトを、このプログラムオブジェクトに関連付けることにより、やっとシェーダを使った描画を行うための準備が完了します。
言葉だけではイメージが難しいと思うので図を見て考えよう
WebGL の初期化の流れ
シェーダそのものというよりプログラムオブジェクトが GPU とやりとりする
WebGL の初期化の流れ
先ほどプリンタのドライバの例を出しましたが、プログラムオブジェクトはまさにプランタのドライバのような存在です。
印刷したいデータをプリンタに送るのがドライバの役割であるのと同じように、GPU 側にある GLSL(シェーダ)に対してデータを送り込むことができるのがプログラムオブジェクトです。
プログラムオブジェクトはその他にも、GLSL 側で利用されている変数の情報を取得したりするのにも活躍する頼れる存在だと考えるといいでしょう。
WebGL の初期化の流れまとめ
- シェーダは、要するに単なる文字列のソースコード
- それをアプリケーション側にある WebGL などの API でコンパイルする
- コンパイルしたシェーダはプログラムオブジェクトに関連付けする
- プログラムオブジェクトが GPU と情報のやりとりを行ってくれる
WebGL の描画までの流れ
さて、プログラムオブジェクトが無事にシェーダのリンクまでを行ったら、次は、GLSL のソースコードに記述されている変数の情報をアプリケーション側(JavaScript 側)で捕捉してやらなくてはいけません。
GLSL 側でどんな名前の変数を使っているのか、その変数のデータ型はなんのか……
そういったことをしっかり確認しながら、双方で正しいやりとりが行えるよう準備をしていきます。
WebGL の描画までの流れ
C 言語などのプログラミングでは「ポインタ」というのが頻繁に登場しますが、GLSL の場合はこれが「ロケーション」と呼ばれるものに相当します。
意味としてはほとんどそのままの意味で捉えればよくて、要は「対象の変数が(GPU 内の)広大なメモリ畑の中の、どのロケーションを指し示しているか」ということを取得して使うわけですね。
CPU と GPU は物理的に離れているのでこういう処理が必要なんですね
WebGL の描画までの流れ
ロケーションの取得には、前述のプログラムオブジェクトが活躍します。
プログラムオブジェクトに正しくシェーダがアタッチされている状態で、プログラムオブジェクトに対して「この名前の変数のロケーションはどうなってるの?」というふうに尋ねてやれば、該当する変数のロケーション情報を得ることができます。
CPU 側と GPU 側の通訳みたいな感じですね
WebGL の描画までの流れ
取得したロケーションは、あとで GPU 側、つまりシェーダにデータを流し込む際に必要になります。
要するに GPU 側で動作する GLSL で定義された変数 に、何かしらのデータを渡して利用する、ということがこれで行えるようになったわけです。
CPU の世界と GPU の世界がつながった!
WebGL の描画までの流れのまとめ
- シェーダが動作するのは GPU の世界
- CPU 側にあるアプリケーションから GPU の世界に干渉するには……
- プログラムオブジェクトがその役割を担う
- GPU 側(GLSL)の変数はロケーションを取得して捕捉する
- CPU からはロケーションを指定してデータを流し込む
GPU 側に GLSL で書かれたシェーダプログラムがある、ということを意識しよう
GLSL の変数
座学が長くて覚えないといけないことも多いのでちょっと大変ですが、実際にシェーダで利用されている変数の情報を取得する様子も、簡単に確認しておきましょう。
ここでは「GLSL によって記述されたシェーダプログラム」のなかで、ユーザーが独自に定義した「変数のロケーション」を取得します。
ロケーションが取得できてさえしまえば、その変数に向かって JavaScript からデータを流し込むことができるようになります。
GLSL の変数
GLSL では変数を宣言する際に、代表的なところでは以下のようなものを指定します。
- 変数のデータ型
- 変数の種類を表す修飾子
JavaScript とは違い型定義はかなり厳密です
GLSL の変数
- 変数のデータ型
変数のデータ型はもうそのままの意味です。
浮動小数点なのか整数なのか、あるいはベクトル型なのか……といった具合に、いくつかのデータ型を指定できます。
型を省略して暗黙の宣言を行ったりすることはできません。
GLSL の変数につくストレージ修飾子
- 変数の種類を表す修飾子
GLSL では変数の型とは別に、その変数の持つ意味合いを表現するための「修飾子」が用意されています。
実は、この部分がシェーダを理解する上で 非常に重要 です。
最初は大変だと思いますが、本当にこれがわからないとなにも始まらないというくらい大切なので、ここでしっかり把握しておきましょう。
GLSL の変数につくストレージ修飾子
attribute
attribute 修飾子は、アプリケーションから送られてきた 頂点の情報 が格納される変数に用いられます。
頂点シェーダは、頂点ひとつひとつに対してそれぞれ実行されます。頂点が個別に持っている座標や色などの情報は、頂点シェーダ内では attribute 修飾子付きの変数を参照することで取得できるわけですね。
attribute → 頂点の情報を持つ変数!
GLSL の変数につくストレージ修飾子
uniform
uniform 修飾子は、アプリケーション(javascript)から渡されるグローバル変数です。
attribute 修飾子付きの変数は、個々の頂点が持つ固有の情報を持つものだったのに対し、uniform 変数はグローバル変数のように振る舞うため、常にアプリケーションから送られてきた情報が格納されており、どの頂点に対しても均一に作用します。
uniform → 一種のグローバル変数!
GLSL の変数につくストレージ修飾子
varying
varying 修飾子付きの変数について考える際は、グラフィックスパイプラインの構造を思い出して考えてみましょう。
グラフィックスパイプラインでは、必ず頂点シェーダが先にありました。その後、フラグメントシェーダへと値が受け渡され、ピクセル単位での処理が行われるのでしたね。この「値の受け渡し」に使われるのが、他ならぬ varying 変数です。
varying → シェーダ間のやり取り用!
GLSL のストレージ修飾子まとめ
- attribute は、頂点それぞれが固有に持つ情報を格納
- uniform はアプリケーションから送られてくるグローバルな情報
- varying は頂点シェーダからフラグメントシェーダへの橋渡しをする
それぞれの役割は非常に重要なのでしっかり覚えておきましょう
ロケーション取得方法の基本
attribute 変数と uniform 変数の両者は、頂点に属するか、グローバルなのか、という点で意味が大きく異なります。
ただし、いずれもアプリケーションからデータを受け取る、という点では共通しています。
varying 変数はシェーダ同士のやりとりなので関係ありませんが、前者は JavaScript や C++ で記述されたアプリケーションからなにかしらの入力を受け取って、シェーダ内で利用するためのものなのですね。
ロケーション取得方法の基本
これらふたつの修飾子を持つ変数は、その 変数名を元にロケーションを取得 します。
大文字小文字はもちろん区別しつつ、変数名とまったく同じ文字列をプログラムオブジェクト経由で検索し、ロケーションを取得します。
ロケーション取得方法の基本
描画を実際に行う段階では、シェーダにデータを送ってやらないといけないわけですから、描画を開始する前の初期化処理のなかでロケーション情報を取得しておく必要があります。
取得したロケーションをどのように使うのかは、attribute 変数と uniform 変数で若干手順が異なるので紛らわしいのですが、最初にまとめて一気にロケーションを取得してしまうのがいいでしょう。
最初はすごく手間に感じるかもしれませんが地味に大事なロケーション取得処理
ロケーション取得から描画まで
さて、非常に説明することが多かったですが、ロケーションの取得まで行えたら、あとはもう適切にデータをシェーダに送り込んで、描画を行うだけです。
WebGL の処理部分にばかり言及する形で全然シェーダの話が出てこなかったのですが、具体的にコードを見ながら、考えていきましょう。
サンプル 001(再)
- 頂点の情報を定義している箇所に注目してみる
- 背景をクリアする色の指定は 0.0 ~ 1.0 で指定する
- ビューポート(viewport)が描画エリアされる矩形の定義
- どのプログラムオブジェクトを利用するかしっかり指定
drawArrays
を用いて GPU に転送済みの頂点を描画する(描画命令)- GLSL 側で
gl_
の接頭語が付いているものはビルトイン変数
GLSL 側では attribute にどんな値が入ってくるかイメージしよう
サンプル 002
- ブラウザの世界ではマウスカーソルの座標は「スクリーン座標系」
- GLSL の世界は -1.0 ~ 1.0 の範囲に限定された世界になっている
- ふたつの世界で座標の扱いが異なる状態であることに注意
- vec 系の変数は配列のようなもの
uniform 変数はグローバル変数みたいなもの、ということを意識しよう
サンプル 003
- 頂点属性、すなわち attribute 変数を追加し、頂点に色を持たせる
- 頂点の持つ色を、最終的に画面に出力できるのはフラグメントシェーダ
- 頂点シェーダは、あくまでも頂点の座標を変換するのが主な役割
- 色に関する処理はフラグメントシェーダに varying 修飾子つき変数で渡す
シェーダごとの役割の違いをしっかり意識しよう
サンプル 004
- インデックスを用いて描画する方法を確認しよう
- インデックスで指定できれば重複定義を減らすことができる
- 言い換えると、頂点の再利用が可能になる
- プリミティブタイプの指定は描画命令と同時に行っている
- 独自に頂点やインデックスを定義できればどのような形状も表現できる
自分の思い描いた幾何学形状(たとえば星型など)を描画してみよう
サンプル 005
- 最後に GLSL でよく利用される様々な概念を押さえよう
- 一度に暗記するのではなく、少しずつ自分なりにシェーダに取り入れていこう
- GLSL では特に間違いやすいところとしてデータ型の厳密さに注意すること
- シェーダだけで頂点を制御したり、色を制御したりできることをイメージできるように
次回へ向けて
さて、初回からなかなか覚えることが多くハードな内容だったかと思いますが、いかがでしたでしょうか。
シェーダという概念は、まずその存在としての意味の理解、そしてその構文などの知識の理解と、一度に覚えなければならないことが非常に多いです。どのようなプログラミングの世界でも同じだと思いますが、最初は、これらのことを地道に少しずつ理解していくしかありません。
次回へ向けて
大切なことは どんどん動かしてみる ことです。自分で修正した結果が、どのような形で描画結果へ反映していくのかをよく観察し、その結果を見ながらさらに考察を重ねていく。これが大切です。
シェーダを利用するようなグラフィックスプログラミングでは、まず絵が出るというところまでたどり着くことが非常に大変です。サンプルを活用すれば、一番の難所である「まず絵が出た状態」には最初から至っている状態ですので、あとはひたすらチャレンジしていくだけです。
次回へ向けて
次回からは、特別講師を招いてのプラスワン講義もスタートします。私が本講義で触れていないような、特殊な処理や面白い考え方もどんどんこれから出てくるでしょう。
まずは、今回の基礎の内容をしっかり身につけておきましょう。変数の型や、いくつかの用語の意味など、それらを事前に理解しているかどうかは今後の習熟度に大きく影響してきます。
最初はきついと思いますが、シェーダコーディングでもやっぱり最初が肝心です。一緒にがんばりましょう!
おまけ
本講義のスライドには、スライドの最後に理解度テストを添付します。
こちらは自身の理解度を測る指針として、よかったら活用してみてください。
question 1
このスクールで利用する GLSL のバージョンは……
question 2
次のうち WebGL + GLSL の組み合わせで記述できるシェーダは?
question 3
頂点シェーダが実行される回数は何と等しくなる?
question 4
フラグメントシェーダが実行される回数は何と等しくなる?
question 5
WebGL + GLSL の頂点シェーダに「必須ではない」ものはどれ?
question 6
attribute 変数の説明として間違っているのはどれ?
question 7
uniform 変数の説明として間違っているのはどれ?
question 8
次のうち、GLSL として正しい書き方はどれ?
question 9
次のうち、GLSL として正しくない書き方はどれ?
question 10
length
関数にベクトルを与えたときの戻り値は?
結果はいかに!?
結果が振るわなかったとしても、落ち込む必要はありません。ブラウザのコンソールに、どこの問題が間違えていたのかなどが表示されていますので、間違えた部分を重点的に復習しておきましょう。