WebGL コンテキストの作成に成功したら、レンダリングを始めることができます。もっとも簡単にできることは、テクスチャが貼り付けられていない単純な 2D のオブジェクトを描画することですので、まずは正方形を描画するコードを作っていきましょう。
シーンを描画する
レンダリングを始める前に理解しておくべきもっとも重要なことは、今回の例では 2 次元のオブジェクトをレンダリングしますが、それは 3 次元の空間に描画されるということです。そのためオブジェクトを描画するのと同じように、シーンの外見を作成するシェーダーを立ち上げなければなりません。これは、スクリーン上で正方形がどのように見えるかを設定します。
シェーダーの初期化
シェーダーは OpenGL ES Shading Language を使用して指定します。コンテンツの管理や更新を簡単にするため、JavaScript ですべてを構築する代わりに、HTML 文書の中にあるシェーダーを読み込むようにコードを記述することができます。それでは、この役割を担う initShaders()
ルーチンを見てみましょう:
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("シェーダープログラムを初期化できません。"); } gl.useProgram(shaderProgram); vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); gl.enableVertexAttribArray(vertexPositionAttribute); }
このルーチンでは 2 つのシェーダープログラムを読み込みます。一つ目はフラグメントシェーダーで、ID 値が "shader-fs" である <script>
要素から読み込みます。二つ目はバーテックスシェーダーで、ID 値が "shader-vs" である <script>
要素から読み込みます。getShader()
関数については、次の章で確認します (このルーチンは、DOM からシェーダープログラムを引き出す操作を行っています)。
その後 WebGL オブジェクトの createProgram()
関数を呼び出すことでシェーダープログラムを作成し、2 つのシェーダーと接続してシェーダープログラムにリンクします。そして、プログラムが正常にリンクされたことを確認するために gl
オブジェクトの LINK_STATUS
パラメータをチェックします。チェックが通れば、新しいシェーダープログラムをアクティブにします。
DOM からシェーダーを読み込む
getShader()
ルーチンは、DOM から指定された名称のシェーダープログラムを取り出し、コンパイルされたシェーダープログラムを呼び出し元へ返すか、シェーダーの読み込みやコンパイルができなかった場合に null を返します。
function getShader(gl, id) { var shaderScript, theSource, currentChild, shader; shaderScript = document.getElementById(id); if (!shaderScript) { return null; } theSource = ""; currentChild = shaderScript.firstChild; while(currentChild) { if (currentChild.nodeType == currentChild.TEXT_NODE) { theSource += currentChild.textContent; } currentChild = currentChild.nextSibling; }
指定した ID の要素が見つかった場合は、そのテキストを変数 theSource
に格納します。
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; }
シェーダーのコードを読み込んだら、シェーダーオブジェクトがバーテックスシェーダー (MIME タイプ "x-shader/x-vertex") あるいはフラグメントシェーダー (MIME タイプ "x-shader/x-fragment") であることを判断するために MIME タイプの確認を行い、そして取り出したソースコードから適切なタイプのシェーダーを生成します。
gl.shaderSource(shader, theSource); // シェーダープログラムをコンパイル gl.compileShader(shader); // コンパイルが成功したかを確認 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { alert("シェーダーのコンパイルでエラーが発生しました: " + gl.getShaderInfoLog(shader)); return null; } return shader; }
最後に、ソースコードはシェーダーに渡され、そしてコンパイルされます。シェーダーのコンパイル時にエラーが発生した場合は、警告を表示して null 値を返します。エラーが発生しなかった場合は、コンパイル済みのシェーダーを呼び出し元に返します。
シェーダー
この後、文書を記述している HTML にシェーダープログラム自体を追加することが必要です。シェーダーがどのように動作するかの詳細はこの記事で扱う範囲を超えており、それはシェーダー言語の構文法になります。
フラグメントシェーダー
ポリゴン内の各ピクセルは、GL の用語でフラグメントと呼びます。フラグメントシェーダーは、各ピクセルの色を決定します。今回の例では、各ピクセルには単純に白色を割り当てます。
gl_FragColor
はフラグメントの色として使用する、GL の組み込み変数です。以下のように値を設定することで、ピクセルの色を定義します。
<script id="shader-fs" type="x-shader/x-fragment"> 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>
オブジェクトを作成する
正方形を描画する前に、その頂点情報を収めるバッファを作成しなければなりません。これは、initBuffers()
関数を呼び出して行います。WebGL のより高度な概念を探求すると、このルーチンはさらに多くの (かつ複雑な) 3D オブジェクトを作成するものに発展します。
var horizAspect = 480.0/640.0; function initBuffers() { squareVerticesBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer); var 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); }
今回の例でこのルーチンは、単純にシーンの基本的な性質を与えます。それは頂点情報を保存するバッファを作り出す機能を持つ、gl
オブジェクトの createBuffer()
メソッドを呼び出すことから始まります。そしてこのバッファは、bindBuffer()
メソッドを呼び出すことでコンテキストに結びつけられます。
以上の手続きを行ったら、正方形の各頂点の座標を収めている JavaScript の配列を作成します。この配列は WebGL 浮動小数点数の配列に変換され、さらに gl
オブジェクトの bufferData()
メソッドに渡されてオブジェクトの頂点情報が成立します。
シーンを描画する
シェーダーの確立とオブジェクトの構築を行ったら、実際にシーンを描画することができます。今回の例ではアニメーションを行いませんので、drawScene()
関数はとても単純なものになります。ここでは後ほど取り上げる、有用なルーチンをいくつか使用します。
function drawScene() { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); perspectiveMatrix = makePerspective(45, 640.0/480.0, 0.1, 100.0); loadIdentity(); mvTranslate([-0.0, 0.0, -6.0]); gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer); gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0); setMatrixUniforms(); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); }
まずは、コンテキストを背景色でクリアします。そしてカメラの遠近感を定義します。視野角を 45 度、縦横の比を 640/480 (canvas の寸法) に設定しています。また、描画するオブジェクトはカメラから 0.1 ~ 100 ユニットの範囲内のもののみにすることを指定します。
そして正方形の位置を、初期位置への読み込みおよびカメラから 6 ユニット分遠方へ移動したところに定義します。その後正方形の頂点バッファをコンテキストに結びつけ、設定を行い、そして drawArrays()
メソッドを呼び出してオブジェクトを描画します。
行列の実用的な操作法
行列の操作はとてもわかりにくいものです。行列を操作するコードのすべてを自分で記述したがる人はいません。幸い、JavaScript でベクトルや行列を操作するのに役立つライブラリである Sylvester があります。
今回のデモで使用している glUtils.js ファイルは、Web 上にある多くの WebGL デモで使用されています。これは由来がはっきりしていないようですが、特別な種類の行列を構築したり、それらを表示するための HTML を出力するメソッドを追加することで、Sylvester を簡単に使用できるようにします。
加えて、このデモでは特定の作業のためにこれらのライブラリとの橋渡しを行う、有用なルーチンをいくつか定義しています。これらが何を行っているかについては今回のデモで扱う範囲を超えていますが、インターネット上には行列に関する多くのすばらしい参考文書があります。そのいくつかが関連情報にありますのでご覧ください。
function loadIdentity() { mvMatrix = Matrix.I(4); } function multMatrix(m) { mvMatrix = mvMatrix.x(m); } function mvTranslate(v) { multMatrix(Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4()); } function setMatrixUniforms() { var pUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); gl.uniformMatrix4fv(pUniform, false, new Float32Array(perspectiveMatrix.flatten())); var mvUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); gl.uniformMatrix4fv(mvUniform, false, new Float32Array(mvMatrix.flatten())); }