WebGL 2.0の概要


# はじめに

この記事では、WebGL 2.0について概要レベルで説明します。
「あれ？　いつもみたいに細かくやらないの？」というツッコミもあるかもしれませんが、本記事、実は私が最近始めた「[WebGL Learning Path JP](https://emadurandal.github.io/WebGL_Learning_Path/)」というWebGL学習支援紹介サイトのコンテンツの一部としてリンクすることを前提にしているものですので、そんな流れになっています。
　そのうち細かい説明も別の記事でやるかもしれませんが…、ひとまず今回は概要レベルです。
　
# すでに概要を説明されている先人がいた…。

　皆さんご存知、WebGLといえば学習スクール等も運営されている @doxas さんが、かなり昔に[WebGL 2.0についてのスライド](https://wgld.org/o/tools/slide/doc/005/)を公開されています。
　当時から目をつけてたって、すごいよ…。
　
# WebGL 2.0の概要：「WebGL 2.0 は WebGL 1.0の増強版だ！」

　@doxasさんのスライドと同じくらいの粒度で説明してもただのコピーになってしまうので、本記事ではもう少し深掘りして概説しようと思います。
　
　WebGL 2.0は、WebGL 1.0の進化系といえばそうなのですが、どちらかというとOpenGL ES 3.0のWeb版という位置付けです。OpenGL ES 3.0はWebGL 1.0の元となったOpenGL ES 2.0と後方互換性を持っていますので、その結果としてWebGL2.0もWebGL1.0の後方互換性を持っているというような感じです。
　
　![OpenGL.png](https://qiita-image-store.s3.amazonaws.com/0/12753/f4e0df6e-5162-4d7b-8a4b-f90731bb430d.png "OpenGL.png")

　WebGL 2.0は、WebGL 1.0と何が違うのでしょうか？
　
　まず、コンセプト的にはほとんど違いがありません。
　WebGL 1.0（とその元になったOpenGL ES 2.0）は、プログラマブルシェーダーという目玉機能を引っさげて登場しました。しかし、WebGL 2.0（OpenGL ES 3.0）では、同時期に登場したWindows専用3D APIのDirectX 10などと違い、「ジオメトリシェーダー」などの新しい種類のプログラマブルシェーダーの搭載は見送られています。
　
　つまり、GPUの処理パイプラインの構造はWebGL 1.0のものをほとんど踏襲しているということです。
　
　![スクリーンショット 2016-06-19 12.46.03.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/05df998b-879d-4d23-8743-1836a1eea2f8.jpeg "スクリーンショット 2016-06-19 12.46.03.jpg")

　とはいえ、WebGL 2.0には幾つかの「中規模」の新機能が搭載されています。また、WebGL1.0で存在した制限事項の幾つかが取り除かれています。

　そういう意味では、WebGL 2.0はWebGL 1.0の「増強版」と言えると思います。
　（私エマの体感としては、このWebGL 2.0を持って、ようやく3D APIの表現力としてPlayStation3/Xbox360世代に並ぶことができた、と感じています。あ、表現力は、ですよ。処理速度はまた別の話です。）
　
# 大きめの新機能

　ざっくり言って、以下の５つです。

* MRT（Multiple Render Target）
* Transform Feedback
* Geometry Instancing
* <del>Vertex Texture Fetching</del>（←追記：私の勘違いでした。このVertex Texture FetchingはWebGL 1.0からすでにサポートされているようです）
* 3D Texture
* 浮動小数点テクスチャ

## MRT（Multiple Render Target）

　これは、複数のテクスチャ（レンダーターゲット）に対して同時にピクセル出力ができる機能です。
　と言っても、ちんぷんかんぷんな人もいらっしゃると思うので順を追って説明しましょう。
　
　まず、WebGLで普通に３Dモデルを表示させると、それはHTMLのcanvasタグと紐付いているフレームバッファ（画面に表示すべきピクセルデータを保持するメモリバッファのこと）に最終的なレンダリング結果の画像データが書き込まれます。
　
　これを、オンスクリーンレンダリングと呼びます。「バッファに書き込んだ画像データがすぐにスクリーンに表示される」から「オンスクリーン」です。
　
　一方、「オフスクリーンレンダリング」と呼ばれるレンダリングもあります。「オフ」と書いていあるだけあり、こちらは「（プログラマが別途命じるまで）スクリーンには表示されません」。
　「オフスクリーンレンダリング」におけるレンダリング先（レンダーターゲットとも言います）は、canvasと紐付いているフレームバッファではなく、WebGLのテクスチャになります。
　
　そうなんです。テクスチャというと、画像を貼り付けるための読み込み専用のピクセル保持領域というイメージが強いですが、実は書き込み（レンダリング先として指定）もできるのです。
　
　これができると何が嬉しいのかというと、例えば、「オフスクリーンレンダリング」で周りの景色をテクスチャにレンダリングし、その景色の絵が書き込まれたテクスチャを、例えば鏡や水面といった３Dオブジェクトに貼り付けることで、景色の反射を演出する、といったことができますね。
　
　それだけでなく、実はWebGLにおいて物体の「影」を表現するのにも、この「オフスクリーンレンダリング」が駆使されていたりします（仕組みについて詳しく知りたい人は、「シャドウマッピング」というキーワードでGoogle検索してみてください）。
　
　さて、この「オフスクリーンレンダリング」はWebGL 1.0から使うことができます。
　ただし「一度に書き込める（レンダリング先に指定できる）テクスチャは同時には１つのみ」という制約がありました。
　
　WebGL 2.0ではこの制限が取り払われ、「複数のテクスチャに同時書き込み（レンダリング先として複数のテクスチャを指定）」することができるようになりました。これがMRT（Multiple Render Target）機能です。
　
　これまでは同じようなことをしようとすると、例えば４つのテクスチャに書き込むなら、その都度、レンダリング先のテクスチャを切り替えながら、同じ３Dモデルについて４回も描画を命令しなくてはいけませんでした。これは大変な処理コストになります。一方、MRT（Multiple Render Target）だと、１回の描画で同時に４つのテクスチャに書き込むことができます。

（以下のコードで問い合わせることで、最大いくつのテクスチャを同時レンダリング先にできるかを調べることができます）

```
gl.getParameter(gl.MAX_DRAW_BUFFERS); // WebGL2の場合
gl.getParameter(ext.MAX_DRAW_BUFFERS_WEBGL); // WebGL1の場合
```

　ただし、それぞれのレンダリング先のテクスチャごとに、３Dモデルの位置を変えることはできません。投影される３Dモデルの位置はどのテクスチャでも同じです。フラグメントシェーダーでそれぞれの出力色を変えられるだけです。
　（もし、投影される３Dモデルの位置を変えたい場合は、MRTを使うのではなく、先程言ったように普通にその都度3Dモデルの描画命令を行う必要があります）
　
　このMRTの利用例として「ディファードシェーディング（Deferred Shading）」という技法があります。これは結構高度な話になるので詳細はここでは触れませんが、この技法をやる上で必要となる、スクリーン上のテクスチャ色、法線、位置情報といった各種の情報をMRTで４つのテクスチャに同時書き込みしていきます。
　（このように、オフスクリーンレンダリングでテクスチャに書き込むのは、何も色だけではありません。数学的な数値データを書き込んだって良いのです）
　
　ちなみにWebGL 1.0でも、ブラウザによっては拡張機能でこのMRTを使える場合があります。

## Transform Feedback

　記事冒頭で、「WebGL 2.0の処理パイプラインはWebGL 1.0と変わらない」と言いましたが、この「Transform Feedback」機能を使うと、ちょっと事情が変わってきます。
　
　この機能は何かというと、「頂点シェーダで計算した結果を（ラスタライザ以降に渡さずに）VBO（頂点バッファオブジェクト）に書き込むことができる」という機能です。その書き込まれたVBOのデータを、再び描画命令で頂点シェーダに流し込むこともできます。
　
　![スクリーンショット 2016-06-19 15.12.56.png](https://qiita-image-store.s3.amazonaws.com/0/12753/5340fefb-22f5-99ff-630b-6e18bbb637f7.png "スクリーンショット 2016-06-19 15.12.56.png")
　
　この機能を使うことで、例えば「粒子の重力物理シミュレーション」といったような科学的な計算をGPUで行うことができるようになります。
　今までは、同じようなことをやろうとすると、CPU側で粒子の座標値を計算する必要がありました（その計算済みの位置を頂点シェーダに渡し、可視化を行う感じです）。まぁ当然パフォーマンスが出ないわけです。
　Transform Feedbackを使うことで、この計算をほぼGPUのみで計算させることができます。

### 疑問：Transform Feedbackを使わなくてもGPUで座標計算できるんじゃないの？
　まぁ、頂点シェーダーがあるわけですから、計算そのものはできます。
　
　しかし、Transform Feedbackを使わない場合、問題があります。「一度計算した結果を受けとる手段がない」ということです（一応、フレームバッファやレンダーターゲットのテクスチャに、ピクセル値として色の出力はされますが、それはもはや、粒子の座標値とは違いますよね）。
　
　粒子の動きなどの物理現象を計算するときには、物理現象を司る支配方程式を離散化し、導出した時間についての差分式を繰り返し繰り返し計算していくことがほとんどです。

　ところが、Transform Feedbackを使わずにGPU側で粒子の座標値を計算させようとした場合、その繰り返し計算の最初の一回目の計算しかできず、一回目の計算結果を受けて二回目以降の計算を行うことができないのです。
　
　Transform Feedbackは、計算結果をVBOに書き込んでくれます。そのVBOのデータをまた頂点として頂点シェーダに流すことで、二回目以降の繰り返し計算を行うことができるのです。
　
　Transform Feedbackが物理シミュレーションなどに最適なのは、こうした理由からなのです。

## Geometry Instancing

これについては、すでに私が[詳しい説明記事](http://qiita.com/emadurandal/items/1812198d562bd1216c70)を書いていますので、そちらをご覧ください。

「その記事読むのが面倒」という方のために、一言だけでこの機能を説明すると、「これまでは、同じ３Dモデルを１万個描画するのに、描画命令を１万回マジメに呼ぶ必要があったのに対し、Geometry Instancing機能を使えば、これがたった１回の描画命令ですむ」というものです。

　ちなみにWebGL 1.0でも、多くのブラウザで拡張機能としてこのGeometry Instancingを使うことができます。

## Vertex Texture Fetching

　（追記：私の勘違いでした。このVertex Texture FetchingはWebGL 1.0からすでに標準機能としてサポートされているようです。WebGL 1.0のベースになったOpenGL ES 2.0では未サポートだったので、その感覚でちゃんと確かめずに書いてしまいました。お詫びして訂正いたします。便利な機能なので、一応記述はここに残しておきます。）
　（追記２：コメントでご指摘を受けました。Vertex Texture FetchingはWebGL 1.0の規格内ではあるものの、実際には利用できるテクスチャ数が０で、実質的に使えない環境もある様です。WebGL 1.0では「オプショナル」的なものと考えた方がいいのかもしれません。WebGL 2.0の環境なら、どの環境でも問題なく使えるようです。）

　これは一言で言うと、頂点シェーダーからテクスチャ読み込みができる機能です。
　
　テクスチャの値を使って頂点の位置を変異させたりすることができます。ちょっと古いネタですが、セガ社の人気格闘ゲーム「バーチャファイター５」では、水面や雪原の表現などにこの機能を使っているようです。
　
　「[3Dゲームファンのための「バーチャファイター5」グラフィックス講座　PCベースのアーケードシステムで実現される至極のリアルタイム3Dグラフィックスの秘密](http://game.watch.impress.co.jp/docs/20061025/3dvf5.htm)」（Game Watch）
　
## 3D Texture

　その名の通りです。３Dテクスチャが使えるようになります。物体を切ったり破壊した時の断面の模様表現や、何らかの計算に必要なデータが三次元的な情報だった場合の格納先としてなど、様々な応用が考えられます。
　（残念ながら、３DテクスチャをレンダーターゲットにすることはWebGL 2.0ではできません）

　また、3Dテクスチャとは違いますが、類似のものとして２Dテクスチャ配列というものもサポートされています。
　
## 浮動小数点テクスチャ

　浮動小数点の値を格納するテクスチャタイプがサポートされました。浮動小数点と言っても、単精度ですらありません。半精度ともFP16とも言われる、16bit長の浮動小数点フォーマットが使われるのが、リアルタイムCGにおいては通常です（少なくとも、リアルタイムCGにおいては必要十分な精度なのです）。
　
　これがサポートされたことで、テクスチャにあらゆる汎用的な数値データを格納したり、書き込んだりすることが可能になりました。もはやテクスチャは、３Dモデルに模様を貼るだけの存在ではありません。
　すでに先進的なユーザーによって、浮動小数点テクスチャを活用した多くの優れたデモ（物理シミュレーション、高度なライティング処理等）が制作されています。
　
　ちなみにWebGL 1.0でも、ブラウザによっては拡張機能でこの浮動小数点テクスチャを使える場合があります。
　
# 小規模な機能

## Vertex Array Object(VAO)

これも、私が詳しい説明の記事を書いています。[この記事部分](http://qiita.com/emadurandal/items/0bb83b545670475f51a3#上級編余談その１vertex-array-object)をお読みください。

一言でいうと、「頂点シェーダーのアトリビュート変数とVBOとのマッピングや、インデックスバッファのバインド状態などを記録してくれるオブジェクト」です。
このVAOにそうした情報を記録させておけば、それらの状態を切り替える際、実際にはVAOをバインドしなおすだけで同等のことができてしまいます。

　ちなみにWebGL 1.0でも、ブラウザによっては拡張機能でこのVertex Array Object(VAO)を使える場合があります。

## ２の累乗以外のサイズのテクスチャのフルサポート

　WebGL 1.0ではテクスチャの本来のフル機能を使うには、そのサイズが２の累乗（例えば、128x256など）である必要がありました。
　一応、それ以外のサイズが全く使えないわけではないのですが、その場合使える機能に制約があったりしました。この制約については [@kyasbal_1994 さんのこちらの記事](http://qiita.com/kyasbal_1994/items/74db3b45139bf9d2b088)をご覧ください。
　
　WebGL 2.0 では、そうした制限がなくなり、２の累乗以外のサイズでも、テクスチャとしてのフル機能が使えるようになりました。
　
## Uniform Buffer Object

　これは、Uniform変数と同様のものを、WebGLでよくある~~~~~BufferObjectと同じように扱えるようにしたものです。Uniform変数のように変数ごとにいちいち一つ一つ設定する必要がなく、大型の構造体のように値を設定でき、更新もそのObjectを一発でGPUに伝えることができるため高速です。
　
　また、このUniform Buffer Objectは通常のUniform変数と異なり、異なるシェーダープログラム間で共有することができるのもメリットです。
　
　構造体のように扱える、と言いましたが一つ気をつけないといけないことがあって、標準では、これにアクセスする際のインデックスやオフセットが処理系依存になります。しかし、std140というフォーマットを指定することによって、これらの仕様をどの処理系でも共通なものに固定することができます。
　詳しくは[こちらのサイト様の記事](http://ramemiso.hateblo.jp/entry/2014/01/14/140716)をご覧ください。

## Sampler Object

　WebGLでテクスチャを作る時、皆さんgl.NEARESTとかgl.LINEARとか、gl.REPEATとかgl.CLAMP_TO_EDGEみたいなパラメータを指定しませんでしたか？
　そうした情報をサンプラー情報というんですが、それがWebGL 2.0ではSampler Objectとしてテクスチャとは分離した形で生成・管理することができるようになりました。
　
　同じテクスチャに対して、異なるサンプラー設定を使いたい時には、このSamplerオブジェクトを切り替えれば良いことになります（それまでは、テクスチャを作り直すか、サンプラー設定が異なる別々のテクスチャとして複数用意する必要があったのです…）。

# 気をつけるべき点

## シェーダー言語GLSLの仕様が変わっている

　結構書き方が変わっています。目立つところとしては、attribute指定がin指定に。varying指定がinまたはout指定になっていたりなどです。
　一応、WebGL1.0の書式で書くことも許されてはいるのですが、WebGL 2.0ならではのシェーダー機能を使う場合、WebGL2のGLSLの書式で書かざるをえなくなります。
　とはいえ、多くは単語の置き換えで済むレベルで、文法が変わってしまうような話ではないので、そんなに心配する必要はありません。

### 配列の動的インデックスが可能に！

　まー、こういった書式がちょこっと変わったとかは別にいいんです。
　それよりもWebGL 2.0で嬉しいのが、配列の動的な添え子アクセスが可能になったということ！

　え、なんのこっちゃ？
　
　実はですね。WebGL 1.0（と元になったOpenGL ES 2.0）で採用されているES GLSL 1.0では、
　配列の添え字にUniform変数やvarying変数のような、動的に値が変わりうる変数を使うことができませんでした。
　定数などしか使えなかったんです。
　（例外的に、for文のインデックス変数だけは使用可能です。ただし、このfor文も繰り返し数は固定でないといけません[^1]）

　何としても配列の動的アクセスっぽいことをしたい場合は、配列データをテクスチャ画像に詰めて、テクスチャ座標を操作してテクスチャフェッチとして取り出す、という回避策を取らざるをえませんでした。
　ちょっとシェーダーで複雑なことをやろうとすると、皆WebGL 1.0のこのGLSLシェーダーの制限でハマります。
　もう、すごく不便です。
　
　それが、WebGL 2.0ではようやくこの制限が取り払われる、というわけです。もうこれだけでもWebGL 2.0に移行したくなりますね！
　
[^1]: WebGL 1.0 ではループ回数（ループ条件部分の指定値）は固定でないといけません。しかし、実質的に動的に繰り返し回数を変更できる裏技があります。それはif文とbreak文を使って、途中で抜けることです。

# WebGL 2.0のサンプル

　先駆的な方々はいらっしゃるもので、WebGL 2.0の大量のサンプルを公開されているサイトがあります。

　Github:[https://github.com/WebGLSamples/WebGL2Samples](https://github.com/WebGLSamples/WebGL2Samples)

　サンプルページ：[http://webglsamples.org/WebGL2Samples/](http://webglsamples.org/WebGL2Samples/)

　サンプルページを見るときは、以下を参考にしてChrome CanaryかFirefoxでWebGL2.0を有効化してからみてください。

## FirefoxでWebGL 2.0を有効化する

アドレスバーに`about:config`と打ち込み、遷移した設定画面で`webgl.enable-prototype-webgl2`という項目を探し、その値をtrueにします。

## Chrome CanaryでWebGL 2.0を有効化する

アドレスバーに`about:flags`と打ち込み、遷移した設定画面で以下のような`WebGL 2.0プロトタイプ`という項目を探し、有効化します。

![blob.png](https://qiita-image-store.s3.amazonaws.com/0/12753/60bf4fa2-f26a-fd6f-5d82-20d030a60d90.png "blob.png")


# 最後に

　いかがでしょうか。WebGL 2.0は、特大級のバージョンアップとは言えないかもしれませんが、追加された中規模の新機能を駆使すれば、それでもかなり表現の幅は広がると思います。
　表現力・処理能力ともに、向上が図られているのがWebGL 2.0です。2016/06/19現在ではまだ正式リリースになっていませんが、ChromeやFirefoxの開発版で先行的に実装されており、今からでもその機能を試すことができます。
　皆さんも、ぜひチャレンジしてみてください。

# 謝辞

 GLSLの制限等については、 @yvt さん、 @gam0022 さんから貴重な情報提供を頂きました。お礼申し上げます。