点や線のレンダリング
今回のサンプルの実行結果
頂点によって描かれるもの
前回はクォータニオンを使って実現するビルボードについて解説しました。常にスクリーンに平行な姿勢を維持し、カメラに対して垂直に映る板状のポリゴンをレンダリングすることで、あたかもそこにモデルが存在するかのように振舞うことができるのでしたね。
ビルボードを使うことで、少ない頂点数で擬似的にモデルが存在するように見せることができ、負荷の軽減が期待できます。その反面、行列に関する演算が追加で発生したり、演出方法を間違えるとそれがビルボードであることがバレバレになってしまったりと、いくつかの難点もありました。
今回紹介するのは、WebGL による点や線の描画です。
いまさらどうして点や線のレンダリングが必要なのかと思うかもしれませんが、これはこれで活用できる場面はいろいろ考えられます。単なる点や線の描画なので派手さはありませんが、習得しておくことは損にはならないはずです。
今回のテキストでは、純粋な点や線のレンダリングのほか、レンダリングされる点のサイズを変更するところまでやってみます。これらの技術を習得しておくことによって、ポイントスプライトというあらたな技術へステップを進めることが可能になります。がんばって取り組んでみてください。
さて、それでは具体的に点や線をレンダリングする手法について見ていきましょう。
プリミティブの種類
WebGL では、レンダリングするのがポリゴンモデルだろうが、点や、あるいは線であろうが、結局は全て頂点によって表現されます。
ポリゴンは最低三つの頂点によって三角形を表現しますが、点であれば一つの頂点で、線であれば最低二つの頂点で、それを表現します。描く頂点の単位が違うだけで、やることは基本的には同じです。VBO を用意したり、場合によっては IBO を用意したりしながら、必要な情報を頂点属性に与えた後レンダリングします。
点をレンダリングする場合は、あくまでも一つの点を描画することになるので、頂点の数がイコール描画される点の数ということになります。これは簡単ですね。
点をレンダリングするには、頂点をレンダリングする drawArrays
メソッドなどの呼び出しにおいて、点を描画するための正しいプリミティブタイプを指定する必要があります。
プリミティブタイプとは、頂点をどのように扱うかを決めるための指定で、プリミティブタイプの種類によっては同じ頂点数でもレンダリング結果が全く違ったものになることがあります。たとえば今までのサンプルで頻繁に利用してきたのは gl.TRIANGLES
というプリミティブタイプでした。これは[ 三つの頂点を使って一つの三角形ポリゴンを表現する ]という意味の指定です。
gl.TRIANGLES を指定している例
gl.drawElements(gl.TRIANGLES, idx.length, gl.UNSIGNED_SHORT, 0);
上記の記述例においてメソッドの第一引数に指定されているのがプリミティブタイプですね。
点をレンダリングする場合には、このプリミティブタイプの指定に gl.POINTS
を利用します。これは、純粋に一つの頂点を一つの点としてレンダリングするという意味の指定になります。
これを図にすると以下のような感じですね。
gl.POINTS のプリミティブタイプを指定した場合
点をレンダリングするには、プリミティブタイプに gl.POINTS
を指定してやればそれだけで OK です。しかし、線の場合にはちょっと違ったことが起こります。WebGL には線を描画するときのプリミティブタイプとして、三つのオプションが存在します。
仮に、レンダリングしようとしている頂点が四つあるとします。
このとき、線のレンダリングに関する三つのプリミティブタイプを指定した場合のレンダリング結果はそれぞれ以下のようになります。
gl.LINES のプリミティブタイプを指定した場合
gl.LINE_STRIP のプリミティブタイプを指定した場合
gl.LINE_LOOP のプリミティブタイプを指定した場合
同じ頂点の数で、どれも同様に線をレンダリングしていますが、結果は全く異なっていることがわかると思います。線のレンダリングにおいては、プリミティブタイプとして gl.LINES
・ gl.LINE_STRIP
・ gl.LINE_LOOP
の三つのうちどれかを指定します。これらのどれを選択したのかによって、同じ頂点数であってもレンダリングされる結果が大きく変化することに注意しましょう。
点のサイズを変更する
プリミティブタイプを正しく指定してレンダリングを行なえば、それだけで点や線を描画することができます。
今回は、点を描画するときに利用できる技術の一つとして、点のサイズを変更する方法についても解説しておきます。
レンダリングされる点のサイズを変更するには、シェーダ内でポイントサイズの指定を行ないます。今までは、頂点の位置を示すシェーダ組み込みの変数 gl_Position
に、座標変換を行なった後の頂点情報を渡していただけでしたが、これと同じように gl_PointSize
という組み込み変数に値を設定します。
ポイントサイズを指定するシェーダの例
attribute vec3 position;
uniform mat4 mvpMatrix;
uniform float pointSize;
void main(void){
gl_Position = mvpMatrix * vec4(position, 1.0);
gl_PointSize = pointSize;
}
ポイントサイズは float
型、つまり浮動小数点数として指定します。javascript プログラムのほうから uniform 変数として値をシェーダに渡すことで、自由にポイントサイズを調整することができるわけですね。
ただし、ここで一点注意しなければならないのは、ポイントサイズの範囲はハードウェア性能に依存するという点です。
これはどういうことかというと、実行環境によって指定できるポイントサイズの最小~最大範囲が変動するということです。安易に大きなポイントサイズを指定するプログラムを書いてしまうと、実行環境によってはそのポイントサイズを表示できない可能性があります。
この、指定できるポイントサイズの範囲は、WebGL コンテキストのメソッドである getParameter
に適切な引数を指定して呼び出すことで調べることが可能です。指定可能なポイントサイズの範囲を調べる場合には getParameter
メソッドに、組み込み定数 gl.ALIASED_POINT_SIZE_RANGE
を指定して呼び出します。
戻り値は配列になっており、0 番目の要素に最小ポイントサイズが、1 番目の要素に最大ポイントサイズが入ります。以下のように記述すれば、コンソールに最小・最大ポイントサイズを出力することができます。
指定可能なポイントサイズの範囲を調べる
// 点の最大ピクセル数をコンソールに出力
var pointSizeRange = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE);
console.log('pointSizeRange:' + pointSizeRange[0] + ' to ' + pointSizeRange[1]);
実際にポイントサイズを変更するプログラムを記述する場合には、このポイントサイズの範囲をあらかじめ調べておいて、必要に応じて処理を分岐するようにしたほうが無難です。現状では、恐らく 64 ピクセルくらいまでが安心して指定できるポイントサイズだと考えたほうがいいでしょう。
仮に、指定範囲外の値を設定した場合には、指定できる範囲内に強制的に収められますので注意しましょう。
シェーダと HTML ソース
それでは今回のサンプルプログラムを見ていきます。
今回のサンプルでは、四つの頂点を使った線のレンダリング、さらに、球体の形成する頂点データを使って、点をレンダリングしてみます。
また、クォータニオンを使ったカメラ制御を組み合わせ、自由な角度からレンダリング結果を眺めることができるようにしています。
まずはシェーダのソースから見てみます。
頂点シェーダ
attribute vec3 position;
attribute vec4 color;
uniform mat4 mvpMatrix;
uniform float pointSize;
varying vec4 vColor;
void main(void){
vColor = color;
gl_Position = mvpMatrix * vec4(position, 1.0);
gl_PointSize = pointSize;
}
頂点シェーダでは、頂点の座標位置、頂点の色を attribute 変数として受け取ります。また、座標変換行列とポイントサイズを uniform 変数として受け取り、フラグメントシェーダには頂点の色情報を渡します。
先ほど解説した gl_PointSize
に値を設定しているのがわかると思います。
続いてはフラグメントシェーダ。こちらでは特に難しいことはしていませんね。
フラグメントシェーダ
precision mediump float;
varying vec4 vColor;
void main(void){
gl_FragColor = vColor;
}
ただ単に、頂点の色をフラグメントに設定しているだけですね。
これらを踏まえて、シェーダを含めた HTML ソースは以下のようになります。
サンプルの HTML ソース
<html>
<head>
<title>wgld.org WebGL sample</title>
<script src="minMatrixb.js" type="text/javascript"></script>
<script src="script.js" type="text/javascript"></script>
<script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
uniform mat4 mvpMatrix;
uniform float pointSize;
varying vec4 vColor;
void main(void){
vColor = color;
gl_Position = mvpMatrix * vec4(position, 1.0);
gl_PointSize = pointSize;
}
</script>
<script id="fs" type="x-shader/x-fragment">
precision mediump float;
varying vec4 vColor;
void main(void){
gl_FragColor = vColor;
}
</script>
</head>
<body>
<canvas id="canvas"></canvas>
<p>
<input id="point_size" type="range" value="10" min="10" max="100"> point size (1 to 10 pixels)
</p>
<p>
<input id="lines" type="radio" name="line" checked> gl.LINES
</p>
<p>
<input id="line_strip" type="radio" name="line"> gl.LINE_STRIP
</p>
<p>
<input id="line_loop" type="radio" name="line"> gl.LINE_LOOP
</p>
</body>
</html>
今回は、表示されるウェブページ上で点のポイントサイズ、線のプリミティブタイプを指定できるようにしています。そのための input タグがいくつか記述されている状態になっています。
javascript プログラム
さぁ、続けてどんどんいきましょう。
今回のサンプルの javascript プログラム内部では、minMatrixb.js に実装されている球体モデルを出力する関数を使った点を描画するための頂点情報のほか、線を描画するための四つの頂点の情報を定義しています。その部分だけを抜粋してみます。
頂点情報の定義
// attributeLocationを配列に取得
var attLocation = new Array();
attLocation[0] = gl.getAttribLocation(prg, 'position');
attLocation[1] = gl.getAttribLocation(prg, 'color');
// attributeの要素数を配列に格納
var attStride = new Array();
attStride[0] = 3;
attStride[1] = 4;
// 点のVBO生成
var pointSphere = sphere(16, 16, 2.0);
var pPos = create_vbo(pointSphere.p);
var pCol = create_vbo(pointSphere.c);
var pVBOList = [pPos, pCol];
// 線の頂点位置
var position = [
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, 1.0, 0.0
];
// 線の頂点色
var color = [
1.0, 1.0, 1.0, 1.0,
1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0
];
// 線のVBO生成
var lPos = create_vbo(position);
var lCol = create_vbo(color);
var lVBOList = [lPos, lCol];
今回使うのは頂点属性のうち、頂点の座標情報と色の二つだけです。また、今回は描画命令として drawArrays
を使いますので、IBO は生成していません。
線を描画するための頂点は、奥からアルファベットの Z を描くような形で配置されるようにしています。
続いて、uniform 変数をシェーダにプッシュするためにロケーションも取得しておきます。
uniformLocation の取得
// uniformLocationを配列に取得
var uniLocation = new Array();
uniLocation[0] = gl.getUniformLocation(prg, 'mvpMatrix');
uniLocation[1] = gl.getUniformLocation(prg, 'pointSize');
これはポイントサイズを指定するためですね。
今回のサンプルでは先述のとおり、ポイントサイズや線のプリミティブタイプを指定するために HTML 内の input 要素を参照します。参照した結果から処理を分岐して、実際にレンダリングまでを行なっているのが以下のコードです。ちなみに、今回はループ処理のなかで毎回リアルタイムに値を反映できるようにしています。
エレメントから取得した値を使ってレンダリング
// 点のサイズをエレメントから取得
var pointSize = ePointSize.value / 10;
// 点を描画
set_attribute(pVBOList, attLocation, attStride);
m.identity(mMatrix);
m.rotate(mMatrix, rad, [0, 1, 0], mMatrix);
m.multiply(tmpMatrix, mMatrix, mvpMatrix);
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.uniform1f(uniLocation[1], pointSize);
gl.drawArrays(gl.POINTS, 0, pointSphere.p.length / 3);
// 線のプリミティブタイプを判別
var lineOption = 0;
if(eLines.checked){lineOption = gl.LINES;}
if(eLineStrip.checked){lineOption = gl.LINE_STRIP;}
if(eLineLoop.checked){lineOption = gl.LINE_LOOP;}
// 線を描画
set_attribute(lVBOList, attLocation, attStride);
m.identity(mMatrix);
m.rotate(mMatrix, Math.PI / 2, [1, 0, 0], mMatrix);
m.scale(mMatrix, [3.0, 3.0, 1.0], mMatrix);
m.multiply(tmpMatrix, mMatrix, mvpMatrix);
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.drawArrays(lineOption, 0, position.length / 3);
// コンテキストの再描画
gl.flush();
変数の名称などが若干わかりにくいかもしれませんが、よくコードを見て考えてみてください。
まとめ
今回のサンプルを実行すると、デフォルトでは二本の線と球体を表すいくつかの点がレンダリングされます。
HTML 内に記述された input 要素の値を変更することで、ポイントサイズや線のプリミティブタイプをリアルタイムに変更できますので、どのように挙動が変化するのか試してみてください。
ポイントサイズの指定は先ほども書いたように実行環境に依存します。今回はアンパイなところをとって 1 ~ 10 ピクセルの範囲で指定できるようにしています。実際にはもっと大きな数値を指定することもできるはずですが、環境によって変わってきますので自分でソースコードに手を加えるなどしてテストしてみるのもいいでしょう。
サンプルを実行して、ある程度大きなポイントサイズを指定すると、勘のいい人なら[ あること ]に気が付くはずです。
この[ あること ]を有効に活用すると、前回行なったビルボード処理を、点を描画して実現するポイントスプライトという技術への足がかりを掴むことができます。
次回は、このポイントスプライトについて解説します。お楽しみに。