iconDartについて書かれている記事は少ない。WebGLについて書かれている記事も少ない。両方となると......?2DグラフィックスにおけるWebGLのパフォーマンスの記事ではDartを使ってプログラムを記述していたものの、何の説明もしないというのは少しマズいのではないかと感じたので、DartからWebGLを利用する方法について簡単に解説することにした。WebGLの利用方法に焦点を当てたいので、3Dグラフィックスの仕組みや線形代数については取り扱わない。基礎編(この記事)3D編アニメーション編テクスチャ編WebGLとはWebGLはブラウザ上で3DCGを扱うための標準規格である。2.0がベースとなっており、全体としての仕様もそれに準拠する。GPUの恩恵を受けた高速な処理が可能で、レンダリングはcanvas要素に対して行われる。現状で正式サポートしているのはChromeとFirefoxのみだが、IE12やSafari8でもサポート予定となっている。DartからWebGLを使うWebGLは複雑なAPIなので、まずは単純なところから始めよう。下の図のような四角形を描画することを目標にする。動作サンプルを開く前準備WebGLの使用を始める前にいくつかの準備がある。今回使うcanvas要素。WebGL用のコンテキストを取得する。通常はcanvasと同じサイズに設定する。*/}WebGLを扱うためにはdart:web_glとdart:typed_dataライブラリが必要になるので、それぞれimportしておく。レンダリング対象としてのcanvasも必要なので作成しておこう。canvasが準備できたら、getContext3d()でRenderingContextオブジェクトを取得する。そのコンテキストを操作することでWebGLが利用できる。WebGLに未対応のブラウザならおそらくnullが返ってくるが、今は未対応ブラウザについては考えないこととする。コンテキストが取得できたら、まずはビューポートの設定を行っておきたい。特別の事情がないかぎり、canvas要素と同じ大きさでかまわないだろう。シェーダの準備コンテキストを取得できたのでさっそく描画と行きたいところだが、WebGLには描画のための固定機能パイプラインが用意されておらず、プログラマブルシェーダで記述する必要がある。言え、用意しなければならないシェーダは2つだけなので、凝ったことをしなければ大した手間にはならないだろう。ひとつはバーテックスシェーダ(頂点シェーダ)と呼ばれているもので、頂点情報の処理を担当する。3D座標を画面上の座標へと変換するのが主な仕事になる。ひとつはフラグメントシェーダ(ピクセルシェーダ)と呼ばれていて、ピクセルレベルの処理を担当する。ポリゴンに色を付けたり、テクスチャを貼り付けるのが主な仕事だ。Language)という言語で記述する。C言語に似た構文なので、Dart利用者ならばすぐに飲み込めるだろう。細かな文法については見ればわかると思うので説明しない。シェーダプログラムは外部ファイルとして保存しておくのが普通ではあるが、読み込み処理などを省きたいので、ここではDartプログラムに直に埋め込むことにする。使用するシェーダは以下の2つ。バーテックスシェーダ。attributeは頂点ごとの変数。varyingはフラグメントシェーダへ渡す値。gl_Positionに頂点座標の演算結果を代入する。ここでは受け取った座標をそのまま代入しているだけ。座標はxyzwの4次元になるが、今のところはw=1.0と固定して考えても良い。フラグメントシェーダ。初めにfloatの精度の指定が必要になる。lowp・mediump・highpなどがある。gl_FragColorに値を代入することでピクセルに色が書き込まれる。ここではバーテックスシェーダから受け取った色をそのまま書き込んでいる。vColor;}""";バーテックスシェーダではプログラムから受け取った頂点座標を変換し、gl_Positionへと変換結果を代入する。ここでは特に変換などは行わず、頂点座標をそのまま代入している。座標はxyzwの4次元で指定するが、今のところはw=1.0で固定していて良い。フラグメントシェーダはバーテックスシェーダからいくつか情報を受け取り、それを元にgl_FragColorへピクセルを書き込む。ここでは頂点色をそのまま書き込んでいる。mat2x3などの行列がある。varyingの3種類が存在する。attribute変数は頂点ごとに異なる値を扱うのに使用する。座標などは頂点ごとに異なるのでattribute変数として宣言する。今回は色も頂点ごとに変化させたいのでattribute変数にしている。バーテックスシェーダのみから利用できる。uniform変数は頂点に依存しない共通の値を扱うのに使用する。変換行列やテクスチャ画像データなどがこれにあたる。バーテックスシェーダ・フラグメントシェーダの両方から利用できる。varying変数はバーテックスシェーダからフラグメントシェーダへ値を受け渡すのに使用する。フラグメントシェーダはattribute変数にアクセスできないので、バーテックスシェーダ側で受け取りvarying変数を使ってフラグメントシェーダへ送ろう。シェーダプログラムの作成WebGLではコンパイル済みバイナリがサポートされていないので、実行のたびにシェーダをコンパイルする必要がある。それぞれのシェーダをコンパイルしよう。コンパイルが終わったら、コンパイルが成功したか否かを確認しておく。シェーダ作成→ソースを渡す→コンパイルの流れ。コンパイルが成功しているか確認しておこう。フラグメントシェーダも同様に。$message");}シェーダの準備ができたら、次はシェーダプログラムを作成する。シェーダプログラムを作成するために、コンパイルの済んだバーテックスシェーダとフラグメントシェーダをリンクする。シェーダのコンパイルと同様、シェーダプログラムのリンクが成功したかどうかを確認しておこう。シェーダの準備が済んだらプログラムを作成する。プログラムのリンクが成功したか確認する。$message");}シェーダプログラムのリンクがうまくいったら、このプログラムを使用することを指定しよう。問題なければこのプログラムを使用する。context.useProgram(program);これでシェーダの準備は終わりだ。ところでWebGLのAPIはかなり奇妙に見えるのではないだろうか。これはC言語由来のAPIであるためだ。何もかもをコンテキストオブジェクトのメソッドで操作するので、慣れるまでは読み書きに苦労するはずだ。この記事では素のWebGLAPIを直接利用するが、実際にWebGLを利用したアプリケーションを作成するときには、オブジェクト主体で操作できるようなラッパを自作するか、有志の作成したライブラリを使用するのがいいだろう。バッファの準備シェーダの準備が済んだので、あとは必要なデータをGPUに流し込んで描画するだけだ。データを転送するにはバッファという仕組みを使用する。今回バーテックスシェーダで使用するのは頂点座標と色情報なので2つのバッファを用意する。context.createBuffer();バッファが用意できたら、次は各バッファをバーテックスシェーダのattribute変数に結びつける。結びつけるにはシェーダプログラムからattributeの位置を取得する必要がある。バッファの操作は、操作対象のバッファをバインドしてから行う。バッファの形式にはARRAY_BUFFERとELEMENT_ARRAY_BUFFERがあるが、通常は前者を使う。あとはattribute変数を有効化して、バインドしたバッファとattribute変数を結びつける。これでバッファを経由してシェーダにデータを送る準備ができた。attribute変数の位置を取得する。バッファを操作する前には必ずバインドする必要がある。ARRAY_BUFFER形式としてバインドしている。現在バインドしているバッファと変数を結びつける。サイズはvec3を指定してるので3。型はFLOATを使用する。0);vertexAttribPointerには引数が6つ存在するが、今のところは最後の3つについては考えなくても良い。第2引数で指定するサイズは、バーテックスシェーダで指定した変数のサイズと食い違わないように注意しよう。データの描画バッファに書き込む準備ができたので、次は描画データを用意しよう。3Dグラフィックスでは三角形が基本単位となるので、それを組み合わせて描画する。データはTypedDataとして用意する。WebGLでは慣習として右手座標系を使用することが多い。ここでもそれに従うことにする。よってZ軸はプラス方向が手前になる。デフォルトの設定では、頂点を反時計回りに配置すると表向きとなる。試しに四角形を描画してみよう。四角形を描画するには三角形が2つ必要なので、6頂点分のデータを用意すれば良い。Dartにfloatは無いのでdoubleで代用することになる。vec3で宣言しているので、xyzxyzxyz...と並べていく。vec4で宣言してるのでrgbargbargba...と並べていく。attribute変数なので頂点と同じ数だけ必要。1.0]);データの準備ができたらバッファへ転送していこう。バインドしてデータを転送する。STATIC_DRAW);bufferDataTypedの第三引数ではバッファの使用方法を指定する。ただの指標でしかないのでどれを指定してもかまわないが、WebGLが最適化する際のヒントになるので、できるだけ実際の用途に近いものを選ぼう。データの転送が終わったら、描画命令を呼び出す。WebGLに描画を促す。context.flush();描画の際には、頂点をどのような図形で描画するかを、プリミティブと呼ばれる基本図形の中から選ぶ。TRIANGLE_FANが存在する。ここでは三角形を描画したいのでTRIANGLESを選択している。三角形を2つ描画するので頂点数は6になる。flush()メソッドはWebGLに描画を強制させる命令だ。WebGLはある程度の描画命令をため込んでから一度に実行することがあるので、必要な描画命令を全て発行した後は、明示的に描画の開始を命令しておいた方がいい。ここまでが描画までの一連の流れとなる。あとは実行すればブラウザに四角形が表示されるはずだ。今回使うcanvas要素。WebGL用のコンテキストを取得する。普通はcanvasと同じサイズに設定する。バーテックスシェーダ。attributeは頂点ごとの変数。varyingはフラグメントシェーダへ渡す値。gl_Positionに頂点座標の演算結果を代入する。ここでは受け取った座標をそのまま代入しているだけ。座標はxyzwの4次元になるが、今のところはw=1.0と固定して考えても良い。フラグメントシェーダ。初めにfloatの精度の指定が必要になる。lowp・mediump・highpなどがある。gl_FragColorに値を代入することでピクセルに色が書き込まれる。ここではバーテックスシェーダから受け取った色をそのまま書き込んでいる。シェーダ作成→ソースを渡す→コンパイルの流れ。コンパイルが成功しているか確認しておこう。フラグメントシェーダも同様に。シェーダの準備が済んだらプログラムを作成する。プログラムのリンクが成功したか確認する。問題なければこのプログラムを使用する。データを転送するためのバッファを作成する。attribute変数の位置を取得する。バッファを操作する前には必ずバインドする必要がある。現在バインドしているバッファと変数を結びつける。サイズはvec3を指定してるので3。型はFLOATを使用する。vec3で宣言しているので、xyzxyzxyz...と並べていく。vec4で宣言してるのでrgbargbargba...と並べていく。attribute変数なので頂点と同じ数だけ必要。バインドしてデータを転送する。WebGLに描画を促す。無事に四角形を描画することができたが、このプログラムには無駄が多い。そこで、いくつかの最適化を行いたい。最適化と言ってもまだデータが小さいため効果を実感することはできないが、早めのうちに学んでおくべきだろう。インターリーブドバッファattribute変数へデータを転送する際に2つのバッファを作成したが、変数ごとにバッファを作成すると、ひとつの頂点についての情報がバラバラに散らばってしまう。いった状況は、メモリアクセスの関係でパフォーマンスに悪影響があると言われている。そこで、インターリーブという手法を用いて複数のデータをひとつのバッファに纏める方法を紹介する。通常のバッファでは一種類だけのデータを並べていたが、インターリーブされたバッファでは複数のデータを頂点ごとにまとめて配置する。非インターリーブバッファインターリーブバッファこのときstrideとoffsetという値が重要になる。strideは頂点ひとつあたりのデータサイズを表している。今回の場合であれば、vec3とvec4をあわせたサイズがstrideになる。offsetは先頭からのサイズを表している。Positionのoffsetは0で、Colorのoffsetはvec3ひとつ分になる。データを並べる順番に決まりはないので、自分がやりやすいようにすると良い。以上のことを踏まえて、プログラムへ反映していこう。バッファを2つ作成していたところを、ひとつだけ作成するように変更しよう。データを転送するためのバッファを作成する。context.createBuffer();ここから片方を削除する。データを転送するためのバッファを作成する。context.createBuffer();次に変更が必要なのはバッファとattribute変数を結びつける箇所になる。バッファを操作する前には必ずバインドする必要がある。現在バインドしているバッファと変数を結びつける。サイズはvec3を指定してるので3。型はFLOATを使用する。0);これを次のように変更する。変更前はそれぞれ違うバッファを用いていたが、変更後はインターリーブされたひとつのバッファへ両方のattribute変数を結びつけている。strideはvec3+vec4で7。poisionがvec3なのでcolorのoffsetは3。バッファをバインドする。attribute変数と結びつける。第5引数にstride、第6引数にoffsetを指定する。COLOR_OFFSET);strideとoffsetの値はバイト数で指定することに注意しよう。次に頂点データを変更する。頂点座標と色で別の配列を用意していたが、これをひとつにまとめる。vec3で宣言しているので、xyzxyzxyz...と並べていく。vec4で宣言してるのでrgbargbargba...と並べていく。attribute変数なので頂点と同じ数だけ必要。1.0]);順番や要素数を間違えないようにまとめていく。rgba...のインターリーブ。1.0]);最後に不要になった部分を削って完成だ。バインドしてデータを転送する。バインドしてデータを転送する。STATIC_DRAW);変更が終わったらプログラムを動かして同じ結果が得られるか確認してみよう。以下は全ての変更を適用したコード。今回使うcanvas要素。WebGL用のコンテキストを取得する。普通はcanvasと同じサイズに設定する。バーテックスシェーダ。attributeは頂点ごとの変数。varyingはフラグメントシェーダへ渡す値。gl_Positionに頂点座標の演算結果を代入する。ここでは受け取った座標をそのまま代入しているだけ。座標はxyzwの4次元になるが、今のところはw=1.0と固定して考えても良い。フラグメントシェーダ。初めにfloatの精度の指定が必要になる。lowp・mediump・highpなどがある。gl_FragColorに値を代入することでピクセルに色が書き込まれる。ここではバーテックスシェーダから受け取った色をそのまま書き込んでいる。シェーダ作成→ソースを渡す→コンパイルの流れ。コンパイルが成功しているか確認しておこう。フラグメントシェーダも同様に。シェーダの準備が済んだらプログラムを作成する。プログラムのリンクが成功したか確認する。問題なければこのプログラムを使用する。データを転送するためのバッファを作成する。attribute変数の位置を取得する。strideはvec3+vec4で7。poisionがvec3なのでcolorのoffsetは3。バッファをバインドする。attribute変数と結びつける。第5引数にstride、第6引数にoffsetを指定する。rgba...のインターリーブ。バインドしてデータを転送する。WebGLに描画を促す。context.flush();}インデックスバッファ四角形を描画するには6頂点が必要だった。本来ならば4頂点あれば十分なはずなので、2頂点分の無駄が発生していることになる。これを解決するのがインデックスバッファと呼ばれるバッファだ。インデックスバッファは各頂点へ番号を振り、描画する頂点を座標の値などではなく番号で指定する。1-3-2などと番号で指定した頂点データを使用することができる。これをプログラムに反映してみよう。インデックスバッファに使用するバッファを作成する。データを転送するためのバッファを作成する。context.createBuffer();ここでバッファをひとつ追加する。データを転送するためのバッファを作成する。context.createBuffer();次に6頂点分あった頂点データを4頂点分に減らそう。rgba...のインターリーブ。1.0]);4番目、6番目はそれぞれ2番目、3番目と同じなので削除できる。rgba...のインターリーブ。1.0,]);次にインデックスを作成しよう。1-3-2に置き換えることができる。描画に使うインデックス。2]);インデックスはuint型で指定することに注意したい。Dartにはuint型はないので、int型となる。インデックスが作成できたらバッファへ転送する。バインドしてデータを転送する。STATIC_DRAW);ここへインデックスバッファへデータを転送する処理を加えよう。バインドしてデータを転送する。STATIC_DRAW);インデックスバッファにはARRAY_BUFFERではなくELEMENT_ARRAY_BUFFERを使用する。他は普通のバッファと同じだ。最後に、インデックスバッファを使用するときは描画命令に手を加える必要がある。VERTEX_NUMS);ここでdrawArraysではなくdrawElementsに変更する。0);大きくは変わらないが、引数の順番が異なることや、インデックスバッファの形式を指定する必要があることに注意。shortを使用している。インデックスはリストの長さがそのまま頂点数と一致するので、頂点数を指定するときにもlengthを取得して使うといいだろう。ここまでの変更ができたらもう一度実行してみよう。四角形が表示されるはずだ。以下は全ての変更を適用したコード。今回使うcanvas要素。WebGL用のコンテキストを取得する。普通はcanvasと同じサイズに設定する。バーテックスシェーダ。attributeは頂点ごとの変数。varyingはフラグメントシェーダへ渡す値。gl_Positionに頂点座標の演算結果を代入する。ここでは受け取った座標をそのまま代入しているだけ。座標はxyzwの4次元になるが、今のところはw=1.0と固定して考えても良い。フラグメントシェーダ。初めにfloatの精度の指定が必要になる。lowp・mediump・highpなどがある。gl_FragColorに値を代入することでピクセルに色が書き込まれる。ここではバーテックスシェーダから受け取った色をそのまま書き込んでいる。シェーダ作成→ソースを渡す→コンパイルの流れ。コンパイルが成功しているか確認しておこう。フラグメントシェーダも同様に。シェーダの準備が済んだらプログラムを作成する。プログラムのリンクが成功したか確認する。問題なければこのプログラムを使用する。データを転送するためのバッファを作成する。attribute変数の位置を取得する。strideはvec3+vec4で7。poisionがvec3なのでcolorのoffsetは3。バッファをバインドする。attribute変数と結びつける。第5引数にstride、第6引数にoffsetを指定する。rgba...のインターリーブ。描画に使うインデックス。バインドしてデータを転送する。WebGLに描画を促す。その4:オブジェクト指向ブラウザ上で動く独自のイベントシステムを組むRSSプロフィール古都ことふるみやことです。フロントエンドとかいろいろやるよ。