WebGL Lesson 1 - 三角形と四角形

オリジナル(英語)のLesson 1はこちら

もし翻訳後の文章に間違いや気になる点など有りましたら、お気軽にこのページの下のコメント欄に書き込んでください。
できる限り対応します。


<< レッスン 0                                                                   レッスン 2>>

ようこそ私の最初のWebGLチュートリアルへ!この最初のレッスンはNeHe OpenGLチュートリアルのNo.2をもとにしています。
NeHe OpenGLチュートリアルはゲーム開発者向けの3Dグラフィックスの学習手段として人気です。
今回はWebページ上に三角形と四角形を出す方法を解説します。
それ自体はそれほどエキサイティングでは無いかもしれませんが、これはWebGLの基礎の大切な導入部になります。
今回のプログラムを理解出来たら、残りはきっととても簡単なはず・・・

これはWebGLをサポートしたブラウザで走らせた時に見える今回のレッスンの画像です。
A static picture of this lesson's results
WebGLに対応したブラウザを使っているなら、ここをクリックすれば生のWebGLバージョンが見れます。
もし対応したブラウザをもっていなければこちらから手に入れられます。

どうやって動いているか、詳細を見ていきましょう。

ちょっとした警告:これらのレッスンはプログラミングの十分な知識があって3Dグラフィクスの経験が無い人たちをターゲットにしています。
その狙いはあなたができる限り早くあなた自身の3DをつかったWebページを作れるように、
コードのなかで何が起こっているかを理解してレベルアップしてもらうことです。

私はこれを私自身がWebGLを学習しながら書いています。だからきっと間違いがあります。あなた自身のリスクで利用してください。
もしなにか間違いを見つけたらコメント欄で教えて下さい。教えて頂いたら、間違いや勘違いは修正しますので。

このサンプルのソースコードを手に入れる方法は2つあります。
実際にサンプルが動いてるところから”ソースを表示”を選ぶか、GitHubを使ってるならリンク先のリポジトリからCloneで取得できます。
(その場合には先のレッスンも同時に取れます)
どちらの場合でも取得したらお好みのテキストエディタで開けばいいです。
最初に見るときにはOpenGLの知識があったとしても、圧倒されるかもしれません。

始まってすぐに2つのshaderを定義しています。
shaderは一般的に高度と思われていますが・・絶望しないで。
実のところ見た目よりもずっと簡単です。

他の多くのプログラムと同じようにこのWebGLのページはいくつかの低レベルな関数の定義から始まります。
それらはコードの下の方にある上位の関数から呼ばれます。
プログラムの説明は下の方から上に向かって行います。もしソースコードを見ているなら、一番下へ飛んで下さい。

次のようなHTMLコードが見えます。

<body onload="webGLStart();">
<a href="http://learningwebgl.com/blog/?p=28">&lt;&lt; Back to Lesson 1</a><br />

<canvas id="lesson01-canvas" style="border: none;" width="500" height="500"></canvas>

<br/>
<a href="http://learningwebgl.com/blog/?p=28">&lt;&lt; Back to Lesson 1</a><br />
</body>


これはページのbodyの全部分です。- 他は全てJavaScriptのなかにあります。 
(もし"View Source"でコードを見ているなら、他にいくつかのサイトの解析に利用する余分なコードが見えるかもしれないけど無視できます)

もちろん、<body>タグの中にたくさんの普通のHTMLタグを置くことができ、普通のWebページの中に、
WebGLのイメージを表示することができます、しかし、このシンプルなデモでは、
WebGLとブログに戻るリンクと3Dグラフィックを表示する<canvas>タグがあるだけです。

CanvasはHTML 5.0のための新しいものです。- これはWebページに2Dと(WebGLを通して)3DをJavaScriptを使って描画する新しい手段です。
今回は<canvas>タグではレイアウトを調整する簡単な属性以外は指定していません。
代わりに全てのWebGLのJavaScriptのセットアップはページが読み込まれたときに一度だけ呼ばれるwebGLStart関数から呼ばれます。
それでは少し上にあるwebGLStart()を見てみましょう。
function webGLStart() {
var canvas = document.getElementById("lesson01-canvas");
initGL(canvas);
initShaders();
initBuffers();

gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clearDepth(1.0)
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);

setInterval(drawScene, 15);
}

この関数は3Dの描画先となるcanvas elementを渡してWebGLと先ほど触れたshaderを初期化します。
またinitBuffers関数を呼び出していくつかのbufferの初期化も行います。
Bufferはこれから描く三角形と四角形に関する情報を保持するのに使われます。
- これらについては後ですぐに解説します。
次はGLのengineにcanvasに書かれているものを完全に黒く消去するための色とdepth testのための値について基本的な設定を行います。
(depth testは後ろにあるものが、前にあるものによって隠されるために使います)
これらの設定はgl objectのメソッドを呼ぶ事で実装されています。(どのように初期化されるかは後で見ます)
最後にsetIntervalを利用してdrawScene関数が15msce毎に呼ばれるようにします。
これは(名前からも予想できるように)bufferを利用してオブジェクトを描画します。

どのようにWebGLが動作するかの理解のために重要なinitGLとinitShadersは後で見ます。
でも最初はinitBuffersとdrawSceneを見てみましょう。
var triangleVertexPositionBuffer;
var squareVertexPositionBuffer;

bufferを保存するための2つのグローバル変数を定義します。
(実際のWebGLのページを作るときにシーン中のobject毎に異なるグローバル変数を使うべきではありません。
しかし最初のレッスンでは説明を簡単にするために使います)
続き:
function initBuffers() {
triangleVertexPositionBuffer = gl.createBuffer();

三角形の頂点座標のためのbufferを生成します。(Vertices:不規則な複数形はお嫌い?)
頂点(vertex)はこれから描こうとする図形の3D空間での座標です。
三角形のために3つの頂点を持つことになります。(値はすぐ後で設定します)
bufferは実のところグラフィクスカード上のメモリーです。
頂点座標を初期化中にカードに送っておくことで、再描画の時にはWebGLに先ほど渡した情報を描画してくれと伝えるだけで良いので、
プログラムを非常に効率的にできます。
もちろん今回はたったの3つの頂点しか送っていませんし、これをグラフィクスカードに転送するコストは殆ど掛かりません。
- しかし膨大な頂点を含む巨大なモデルを扱うようになったときには、この方法はものすごいメリットがあります。

gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);

この行はWebGLに以降のbufferに対する操作は全てこの引数として指定したbufferに対して行うように伝えます。
この操作時に対象のbufferを指定するのではなく、あらかじめ"current array buffer"を指定して
そこに対して操作を行うというコンセプトは常について回ります。
奇妙ですね。でもパフォーマンス上の理由が裏にあるんです。

var vertices = [
0.0, 1.0, 0.0,
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0
];

次にJavaScriptの配列として頂点座標を定義します。
座標を見れば中心が(0,0,0)の二等辺三角形の頂点だってわかります。

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

今度は先ほどのJavaScriptの配列をもとにFloat32Array objectを作ってWebGLに"Current Buffer"にこの値を設定すると伝えます。
もちろん"Current Buffer"はtriangleVertexPositionBufferです。
Float32Arrayについては今後のレッスンで解説します。
とりあえず今知っておかなければならないことは、この方法で配列をWebGLのbufferに設定出来るということです。

triangleVertexPositionBuffer.itemSize = 3;
triangleVertexPositionBuffer.numItems = 3;

bufferに対する最後の操作は2つの新しいプロパティを追加する事です。
これはWebGLの組み込み機能ではありません。
でも後でとても役に立ちます。

JavaScriptの素晴らしい点(悪い点という人もいるかも)の1つに、objectに個々のプロパティを設定するのに、
明示的に宣言しておく必要がない、というのがあります。

buffer objectにはitemSizeとnumItemsプロパティはありませんでしたが、これでプロパティを持つようになりました。
このプロパティで9要素からなるbufferが3つの頂点(numItems)を持ち、それぞれは3つの数値(itemSize)から成り立つことを表せます。

三角形のためのbufferのセットアップが終わったので次は四角形です。

squareVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
vertices = [
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
squareVertexPositionBuffer.itemSize = 3;
squareVertexPositionBuffer.numItems = 4;
}

やってることは明らかですね。- 四角形は4つの頂点を持つので三角形の時よりnumItemsの値が大きくなっています。

OK,上記の関数でやらなければならなかったことは、2つの図形の頂点情報をグラフィックカードに送ることでした。
今から、これらのbufferを利用して実際に図形を描画するdrawScene関数を1行づつ見ていきましょう。

function drawScene() {
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);

最初のステップはviewport関数を使ってWebGLにcanvasの大きさを伝えることです。
何故この処理が重要なのかについては(かなり)後のレッスンで戻ってきます。
現時点で最低限覚えておかなければならないことは、描画を始める前にcanvasのサイズとともにこの関数を呼ばなければならないと言うことです。
次に描画の準備としてcanvasを消去します。
・・・それから
perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);

ここでシーンをどのように見せたいかの設定とともにperspectiveを設定します。
デフォルトではWebGLは近くのものと遠くのものを同じ大きさで描画します。
(3Dの用語で(orthographic projection)正射投影法といいます)
遠くのものが小さく見えるようにするためにWebGLにperspectiveについての設定が少し必要になります。
ここではシーンの(垂直)視野角として45度、canvasの幅と高さの比、視点にこれより近づいたら表示しない距離として0.1、
これ以上離れたら表示しないという距離として100を設定しています。

このperspective関数は非常に便利ですがWebGLに組み込まれていません。
そこでユーティリティ関数としてプログラムの上の方で定義しています。
この関数の働きについての詳細は後で説明しますが、幸いなことに細かいことは知らなくても簡単に使う事ができます。

perspectiveの設定が終わったので、表示処理に取りかかることが出来ます。

loadIdentity();

最初のステップは3Dシーンの中心に移動することです。
OpenGLではシーンを描画するときに、対象の物体毎に、"current"の位置と回転を伝えます。
 - たとえば、「前に20進んで32度回転してロボットを描画して」という具合に。
最後の複雑な部分は「どれだけ進んで、どれだけ回転して、それから描画して」という指示の中にあります。

これは”ロボットを描画する”コードを1つの関数にカプセル化できるのでとても便利です。
そして関数を呼ぶ前に移動や回転を変えることで簡単にロボットを移動させることができます。

currentの位置と回転はmatrixの中に保持されています:あなたが以前学校で習ったようにmatrixは
移動(どこからどこへ移動するか)、回転、その他の幾何学的な変形を表すことが出来ます。
詳細には踏み込みませんが、4x4の行列(3x3ではなく)1つで3D空間中のいくつもの変形を表せます。
最初はidentity matrix(単位行列)から始めます - identity matrixには変形が全く含まれていません。
 - そこに最初の変形を表すmatrixを掛けます。そして2番目の変形を表すmatrixを掛け、以下同じように続きます。
合成されたmatrixは1つで全ての変形を表します。
このcurrentの移動と回転を表すのに使われるmatrixをmodel-view matrixと呼びます。
たぶんすでに気がついていると思いますが、loadIdentity関数はこれからmodel-view matrixに
移動と回転を掛け合わせる準備のためにidentity matrixをセットします。
言い換えると、3Dの世界を描画するために移動可能な場所から原点に戻ったとも言えます。

注意深い読者は気がついたかもしれないですが、このmatrixの話の始めに「WebGL」ではなく「OpenGL」ではと書きました。
これはperspective関数と同じようにWebGLには、matrixのための組み込み関数がないためです。
またしても、私たちは自前で実装するか、すでに存在するutility関数をコピーする必要があります。
Utility関数の働きの詳細については後で説明しますが、詳細は知らなくても使う事はできます。

それでは、canvasの左側に三角形を描画する部分のコードを見ていきましょう。

mvTranslate([-1.5, 0.0, -7.0]);

loadItendity関数で3D空間の中心に移動したので、左に1.5(X軸に沿ってマイナス方向)、奥に7(見ている人から遠ざかる:Z軸に沿ってマイナス方向)
(mvTranslateは予想したかもしれませんが、指定したパラメタに従った移動のためのmatrixをmodel-view matrixに掛ける低レベルの移動のための関数です)

次のステップでは実際に描画処理を開始します。

gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

覚えていると思いますが、bufferを使うときにはgl.bindBufferでcurrent bufferをセットしてからbufferを操作するコードを呼び出します。
ここではtriangleVertexPositionBufferを指定してWebGLに頂点座標として使われる値を伝えます。
後でどのように動作するかもう少し詳しく説明しますが、
今のところはbufferの中の各要素が3つの数値からなることを、bufferにセットしたitemSizeプロパティを利用して指定しているということがわかるはずです。

次は・・

setMatrixUniforms();

これはWebGLにcurrentのmodel-view matrix(それとprojection matrixもだけど、それはまた後で)を利用するように伝えます。
この関数はWebGLの組み込み関数としては存在していないので自分で用意する必要があります。

この(model-vew matrix)働きを見る方法としてはmvTranslateを使っていろいろと動かしてみればいいのですが、
これは全部JavaScriptの中だけで行われます(WebGL機能は使いません)。

setMatrixUniformsはこれをグラフィックカードに送ります。

ここまでの処理が終わると、WebGLは頂点として扱われるべき座標の配列とmatrixを持っています。
次のステップはこれらを使って何をするべきかを伝えます。

gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);

別の言い方をするなら「先ほど渡した頂点配列で三角形を描画しなさい。頂点は配列の0番の要素からnumItems番目の要素まで使いなさい」

ここまで終わればWebGLは三角形を描画しているでしょう。次は四角形を描きます。

mvTranslate([3.0, 0.0, 0.0])

model-view matrixを右へ3動かすところから始めます。
思い出してください。現時点で既に左に1.5、スクリーンから7.0奥にいるので、この結果右に1.5、奥に7の位置になります。

次は・・

gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

WebGLに四角形用の頂点bufferを使う事を伝えます・・

setMatrixUniforms();

model-view matrixとprojection matrixを再び送ります。
(最後に行ったmvTranslateの結果が反映されるように)
これでやっと最後の処理ができます。

gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);

描画を行いなさい(と指示をだします)。
ここでtriangle stripが何か気になりましたか?
ええ、これは三角形の連らなったものです。とっても便利ですよ。
最初に3つの頂点を指定して三角形を描画して、次に最後に利用した2つの頂点に新しい1つの頂点を追加して次の三角形を描画する、
そしてもう一点追加して・・という事を続けます。
このケースでは四角形を指定する素早く分かりにくい方法かもしれません。
しかしもっと複雑なケースでは複雑な形状を複数の三角形で表現するとても便利な方法となります。

とにかく、ここまで終わればdrawScene関数はおしまいです。

}

もしあなたがここまでたどり着いたなら、実験をする準備は確実にできています。
ソースコードをGitHubか実際に動いているソースのどちらからかローカルファイルとしてコピーして下さい。
もし後者の方法を選ぶならindex.htmlとsylvester.jsとglUtils.jsが必要になります。
実行して正しく動作することを確認して下さい。
そうしたら、いくつかの頂点の座標を変更してみて下さい。
今のシーンは完全に平らですが、得に四角形のZの値を2とか-3とか変えてみてみると、位置が前後するので大きくなったりします。
あるいは1つか2つの頂点のみ変更して見え方がゆがむのを見てみて下さい。
クレイジーに行くんだ。私に気兼ねすることはないですよ。楽しみにしています。

・・・

なたが戻ってきたことだし今まで見てきたプログラムを可能にするサポート関数を見てみましょう。

以前に言ったように、もし詳細を気にしない幸せな人ならサポート関数をコピぺするだけでいいです。
それらはinitBuffer関数の上にあります。
たぶんあなたは詳細に踏み込まないでも面白いWebGLページを作れます。(たとえ白黒でもね - カラーについては次回のレッスン)
けれど詳細は理解し難いということはないし、どのように動作しているかを理解することで
きっとより良いWebGLのプログラムを後で書けるようになると思います。

まだ私とともにやっていくかい? ありがとう。
一番退屈な関数を最初に終わらせてしまおう。
最初はwebGLStartから呼ばれるinitGL関数です。これはwebページの一番上の方にあります。
ここに参照のためにコピーを載せます。

var gl;
function initGL(canvas) {
try {
gl = canvas.getContext("experimental-webgl");
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
} catch(e) {
}
if (!gl) {
alert("Could not initialise WebGL, sorry :-(");
}
}

これはとても簡単。あなたも気がついているかもしれないけど、
initBuffersとdrawSceneで呼ばれる関数にはしばしばglというオブジェクトが参照されていました。
これはWebGLの中心となる何かの単なる参照です。
この関数ではその何かを取得します、何かとはWebGL contextと呼ばれるもので、standard context nameを使ってcanvasからそのcontextが得られます。

(たぶん気がついていると思うけど、どこかの時点で"experimental-webgl"から"webgl"へcontextの名前は変わるはずです。
その時にはそれに関するblogやlessonを修正します)
contextが出来たらJavaScriptのobjectにどんなpropertyも追加できるという素敵な機能を利用して利用するcanvasの幅と高さを保存しておきます。
この値はdrawScene関数の始めの部分でviewportとperspectiverを設定するのに使えます。
それが済めば、私たちのGL contextは用意できました。

initGL()を呼び出した後にはwebGLStart関数はinitShaders()を呼び出します。
もちろんこの関数はshaderを初期化します。(当たり前だねw)
そのコードは後で見るとして、まずはmodel-view matrixを扱うutility関数群を見ていきましょう。
これがそのコードです。

var mvMatrix;

function loadIdentity() {
mvMatrix = Matrix.I(4);
}

function multMatrix(m) {
mvMatrix = mvMatrix.x(m);
}

function mvTranslate(v) {
var m = Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4();
multMatrix(m);
}

model-view matrixを保持するためのmvMatrixという変数を定義しています。
それとmvMatrixを利用するloadIdentity、mvTranslateという関数も定義します。
mvTranslateではmultMatrixというutility関数も利用しています。
もしJavaScriptに精通しているなら行列演算に利用している関数がJavaScriptに組み込まれたAPIでは無いことを知っているでしょう。
これらの関数はHTMLの先頭のすぐ下で組み込んでいる、すぐ後で触れる2つのサポートJavaScriptファイルを組み込む事で提供されます。

<script src="sylvester.js" type="text/javascript"></script>
<script src="glUtils.js" type="text/javascript"></script>

1つめはSylvesterこれはmatrixとvectorの代数演算をJavaScriptで行うオープンソースのライブラリです。
2つめは(私の考えでは)Vladimir Vukićevićによって作られたSylvesterの拡張機能です。

ともかく、これらの簡単な関数とヘルパーライブラリーのおかげでmodel-view matrixを取り扱うことができます。
それ以外にも説明が必要なmatrixが有ります。一つは前の方で少しほのめかした - projection matrixです。
覚えてるかもしれないけどperspective関数はWebGLの組み込み関数ではありません。
model-view matrixが物体の移動や回転をカプセル化できるのと同じように、
遠くに行った物体が距離に比例して近くにあるときよりも小さくなるのを表すのにもmatrixはとても適しています。
そしてあなたがおそらく推測したようにprojection matrixがそれを行います。
これがコードです。

var pMatrix;
function perspective(fovy, aspect, znear, zfar) {
pMatrix = makePerspective(fovy, aspect, znear, zfar);
}

makePerspective関数は先ほど組み込んだglUtils.jsで定義されている、
引数で指定した透視法を適用するために必要な特殊なmatrixを返す関数です。

はい。これで先ほど説明したmodel-view matrixとprojection matrixをJavaScriptからWebGLへ送る
setMatrixUniforms関数から派生した全てを見てきました。
それと恐ろしいshaderに関する事も。
それらは内部で関連しています。なのでバックグラウンドから見ていきましょう。

shaderとは何かと、疑問に思っています?
えっと、3Dグラフィックの歴史のいくつかのポイントではそれらはこんな感じに思われていたんじゃないかな
 - 画面に書かれるパーツにどのように陰をつけるか、どんな色にするかをシステムに伝えるコードの固まり。
しかしながら、時間が経過してどんどん機能が拡張されて今では描画の前にやりたいことは何でも可能なコードの固まりと呼べそうです。

そしてこれは実のところかなり役に立ちます。
(a)グラフィクスカード上で動作するので非常に高速に動作が可能
(b)それらが行える変換処理はこのような簡単なサンプルでも本当に便利に扱える

このような簡単なWebGLのサンプル(少なくともOpenGLチュートリアルの”中間物”です)に
shaderを導入する理由はWebGLの仕組みを利用するためです。
幸運なことにshaderはグラフィクスカード上で動作するので、私たちのmodel-view matrixとprojection matrixをシーン中の
全ての点と頂点に対して(相対的に)低速なJavaScript上で適用する必要が無くなります。
これは信じられないぐらい便利で追加のオーバーヘッドに見合った価値が有ります。

それではセットアップの部分です。
webGLStart関数はinitShaders関数を読んでいましたね。それでは1つずつ見ていきましょう。

var shaderProgram;
function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");

shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);

if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}

gl.useProgram(shaderProgram);

見て分かるように、getShader関数を使って"fragment shader"と"vertex shader"の二つを取得して、
WebGLの"program"と呼ばれるものに両方ともくっつけています。
ProgramはWebGLのシステム側に配置されるコードの一部です。
これはグラフィクスカード上で動作する何かを指定する方法と見て取れます。
予想したかもしれませんがprogramには複数のshaderを関連づけられます。
それぞれのshaderはprogram中のコードの断片とわかるでしょう。
それぞれのprogramは1つのfragment shaderと1つのvertex shaderを保持できます。
それについては少し後で見ます。

shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

programがセットアップされてshaderがアタッチされると、"attribute"への参照の取得を行います。
それはprogram上のvertexPositionAttributeという新しいフィールドに保存されます。
もう一度言いますがJavaScriptのどんなobjectにでもどんな新しいフィールドを追加できるという特徴があります。
それを利用してprogramにデフォルトでは存在しないvertexPositionAttributeというフィールドを追加します。
必要な情報を纏めておくことは便利なので、"attribute"をprogramの新しいフィールドにしてしまいます。

ところで、vertexPosionAttributeは何のためにあるのでしょう?
覚えてるかもしれないけど、drawScene関数の中で利用しました。
もし三角形の頂点座標を適切なbufferからセットするコードに戻れば、bufferとattributeを関連づけているのが見られるでしょう。
きっともう少し後でその意味するところが分かります。
今のところはgl.enableVertexAttribArray関数を使ってWebGLに対して、配列からattributeに値を提供したいことを、伝えている部分だけに注目しておきましょう。

shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
}

initShaderの最後の部分はprogramから2つの値を取得しています。
uniform variableと呼ばれるもののlocationです。
後ですぐに解説しますので今のところは、attributeと似たものだと思っておきましょう。
その値を便利に扱えるようにprogram objectに保存しておきます。

それではgetShaderを見ていきましょう。

function getShader(gl, id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}

var str = "";
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3)
str += k.textContent;
k = k.nextSibling;
}

var shader;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}

gl.shaderSource(shader, str);
gl.compileShader(shader);

if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}

return shader;
}

この関数も見かけよりはずっと簡単です。
ここでやっていることは引数として渡されたIDと一致する要素をHTMLの中から探して、
中身を取り出して、fragmentかvertexのどちらかのshaderをそれが持つtype(typeの違いについては将来のレッスンで)に応じて作成して、
WebGLにグラフィクスカード上で動くようにコンパイルを掛けさせることです。
次にコードにエラーが無いかを調べて完了です。
もちろんshaderをJavaScriptのコードの中に文字列として組み込むことも出来ましたし、その場合にはHTMLから取り出す部分は不要になります。
 - しかし今回の方法ではshaderはWebページ中のスクリプトとなるのでJavaScriptと同じように読みやすくなります。

それを踏まえてshaderのコードを見ていきましょう。

<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision highp float;
#endif

void main(void) {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>

<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;

void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
}
</script>

覚えておかなければならない最初のことは、言語の起源が明らかに同じであるとしても、これらはJavaScriptで書かれていないと言うことです。
実のところ、これらはC言語の影響を強く受けた(もちろんJavaScriptからも)特別なshader言語で書かれています。
最初のはfragment shaderですが、殆ど何もしていないですね。
このコード中にはグラフィクスカードにfloatの精度を指定するための、必ず含まないといけない定型部分があります。
その後で描画される全てのものが単に白色で描画されるように指定しています。(色に関する事は次のレッスンで取り上げます)
次のshaderはもっと面白いです。これはvertex shader です。覚えているでしょうが、
vertex shaderはvertexを使ってかなりいろんな事ができるグラフィクスカード上で動作するコードです。

それに関連して、ここにはuMVMatrixとuPMatrixと呼ばれる2つのuniform変数があります。
uniform変数はshaderの外部からアクセスできるので役に立ちます。
 - 特に関係するのは、programを持っているところ、多分覚えているでしょうがinitShadersの中でlocationを取り出したところ、
(もう気がついているでしょうが)次に見るmodel-viewとprojection matrixをセットするところ。
あなたはshaderプログラムを(Object指向での)objectと、uniform変数をfieldと見なしたいと考えるかもしれませんね。
全ての頂点に対してshaderが呼ばれます。
そして頂点はdrawScene内部でvertexPositionAttributeを使ってattributeとbufferを関連づけることでshaderにaVertexPositionとして渡されます。
vertex shaderのmain関数内部の小さなコードでやっていることは
頂点にmodel-view matrixとprojection matrixを掛けた結果を最終的な頂点座標として出力することです。

纏めると、webGLStartはinitShadersを呼び、その中ではgetShaderを使ってfragmentとvertex shaderをwebページのscriptから取得し、
コンパイルを行ってWebGLに渡されて3Dシーンを描画するときに使われる、という事です。

ここまできて、あとは説明が残っていることはsetMatrixUniformsだけです。
これは今までの内容を知ってしまえばとっても簡単です。

function setMatrixUniforms() {
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, new Float32Array(pMatrix.flatten()));
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, new Float32Array(mvMatrix.flatten()));
}

ここでは、initShader関数で取得したprojection matrixとmodel-view matrixを表すuniform変数への参照を利用して
WebGLにJavaScript形式のmatrixを渡します。

ヒュー。初回のレッスンにしてはかなり量が多かったですね。しかし幸運なことにあなた(と私)は
これからもっと面白い事を行うために必要に有るであろう基本事項のすべてを理解しました。
 - 色、動き、本当の3DのWebGLモデル。
それらを知るためにはレッスン2へ進んで下さい。

<< レッスン 0
レッスン 2>>

謝辞:明確に私はこのレッスンのスクリプトについて、NeHeのOpenGLチュートリアルに恩義を感じています。
それ以外にも、Benjamin DeLillo と Vladimir Vukićević にも感謝したいです。
彼らのコードを研究し、分析し、多分誤解し、変更して、最終的にはこの投稿の元となりました。
James Coglanにもオープンソースの素晴らしいSylvester libraryを作ってくれたことをとても感謝します。