ポリゴンに色を塗る(頂点色の指定)
今回のサンプルの実行結果
頂点属性の数だけ増えるもの
前回、やっとポリゴンをレンダリングするところまでテキストが進んだわけですが、前回のサンプルでは真っ白なポリゴンが一枚描画されるだけでした。
今回は、ポリゴンを形成する頂点に色という新たな属性を付加して、ポリゴンに色をつけてみます。とは言え、基本的にやっていることは前回とほとんど同じです。少し手順が増える程度のことです。ここは一つ、身構えることなく楽な気持ちで取り組んでいただければと思います。
まず、当サイトのテキストでも何度か説明している通り、頂点には様々な情報を持たせることができます(参考:頂点バッファの基礎)。そして、その頂点が持つ情報の一つ一つを頂点属性と呼ぶのでしたね。前回のサンプルでは、頂点は位置情報という頂点属性一つしか持っていませんでした。今回は、ここにさらに色という頂点属性をプラスします。
頂点属性が一つ存在すると、それと対になる形で必要になるものがありましたね。憶えているでしょうか。
答えは、頂点バッファ( VBO )ですね。前回は、位置情報を保持するための VBO を用意してレンダリングしましたが、今回はさらに VBO をもう一つ用意します。位置情報の VBO と色情報の VBO を用意し頂点シェーダに通知することで、ポリゴンに色が付くというわけです。
シェーダのソースを修正
さて、それではまずシェーダのソースから見ていきます。
今回は attribute
変数が二つ必要になります。位置と色、二つの頂点に関する情報を受け取るためですね。
頂点シェーダのソース
attribute vec3 position;
attribute vec4 color;
uniform mat4 mvpMatrix;
varying vec4 vColor;
void main(void){
vColor = color;
gl_Position = mvpMatrix * vec4(position, 1.0);
}
ご覧の通り attribute
変数が二つ宣言されています。 position
は前回と同じですが、その下にあるもう一つの attribute
変数である color
が頂点の色情報を扱うために使われます。
また、前回は登場しなかった varying
変数が今回は登場していますね。一応復習の意味で簡単に解説すると、 varying
変数は頂点シェーダとフラグメントシェーダの橋渡しをしてくれる変数タイプでした。 main
関数の中身を見ればわかりますが、 attribute
として入ってきた頂点の色情報を、そのまま varying
変数の vColor
に渡していますね。
このように処理すると、頂点シェーダに入ってくる頂点属性の情報を、フラグメントシェーダに渡すことができます。もちろん、頂点シェーダのなかで何かしらの処理を行なってからフラグメントシェーダへ送ることもできますし、そこはシェーダを自分で記述できるプログラマブルシェーダの利点でもあり醍醐味でもあります。
それでは、フラグメントシェーダのほうも見てみます。
フラグメントシェーダのソース
precision mediump float;
varying vec4 vColor;
void main(void){
gl_FragColor = vColor;
}
前回のテキストで使ったフラグメントシェーダでは gl_FragColor
に直接色情報のデータを代入していました。そのため、描画されるポリゴンは単色の三角形として表示されましたね。今回の場合には、頂点シェーダから送られた varying
型変数の vColor
を使うことになるので、頂点の色属性がポリゴンの色に影響します。※ちなみに頂点シェーダとフラグメントシェーダで同名の varying 変数を宣言していますが、双方は内部的には全く別物の変数です。
精度を指定する precision
今回のフラグメントシェーダの一行目には、 precision
という見慣れないワードが出てきています。この precision
は数値の精度を指定するためのキーワードで、 precision
に続けて精度修飾子を記述することで利用できます。
修飾子には、三つの種類があり、わかりやすく言ってしまえば上・中・下を指定します。実際には変数に使用されるビット数などが変化する(つまり扱える数値の桁数が増えるなどの変化がある)のですが、この辺は実行される環境によって結果がまちまちになってしまうのが現実のようです。
lowp
:精度低
mediump
:精度中
highp
:精度高
上記で掲載したフラグメントシェーダでは precision
に続けて mediump float
と記述されています。これは、このフラグメントシェーダ内の float
型の数値全てに mediump
を適用するという意味になります。
フラグメントシェーダでは、特別なことをやっているかどうかに関わらず、とりあえず precision
に関する記述をしておたほうがいいでしょう。そうでないとシェーダのコンパイルでエラーになることがあります。まぁ、魔法の呪文・おまじないのようなものですね。
頂点バッファ周りの処理
さて、シェーダの次は VBO です。こちらは少々やることが増えていますが、実際には同じことを二回繰り返しているだけですので、注意深く見ていけば前回とほとんど変わっていないことがわかるでしょう。
頂点データ配列を作成するところまでを抜粋
// attributeLocationを配列に取得
var attLocation = new Array(2);
attLocation[0] = gl.getAttribLocation(prg, 'position');
attLocation[1] = gl.getAttribLocation(prg, 'color');
// attributeの要素数を配列に格納
var attStride = new Array(2);
attStride[0] = 3;
attStride[1] = 4;
// 頂点の位置情報を格納する配列
var vertex_position = [
0.0, 1.0, 0.0,
1.0, 0.0, 0.0,
-1.0, 0.0, 0.0
];
// 頂点の色情報を格納する配列
var vertex_color = [
1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0
];
前回は頂点属性が一つだったので、頂点属性のインデックスを格納するのは純粋に変数でした。今回は、頂点属性が二つになりましたので、変数ではなく配列にインデックスを取得しています。また、色情報は RGBA の四つの要素によって表現されます。ですから、色情報に関する配列は[ 頂点数 x 4 ]になります。
頂点データを配列に格納できたら、次は配列から VBO を生成します。以下、抜粋コード。
頂点データ配列から VBO を生成するまでを抜粋
// VBOの生成
var position_vbo = create_vbo(vertex_position);
var color_vbo = create_vbo(vertex_color);
// VBOをバインドし登録する(位置情報)
gl.bindBuffer(gl.ARRAY_BUFFER, position_vbo);
gl.enableVertexAttribArray(attLocation[0]);
gl.vertexAttribPointer(attLocation[0], attStride[0], gl.FLOAT, false, 0, 0);
// VBOをバインドし登録する(色情報)
gl.bindBuffer(gl.ARRAY_BUFFER, color_vbo);
gl.enableVertexAttribArray(attLocation[1]);
gl.vertexAttribPointer(attLocation[1], attStride[1], gl.FLOAT, false, 0, 0);
VBO を生成したあとバインドし、登録するまでの処理を位置情報と色情報で二回繰り返しています。これを見てピンときた人もいるかもしれませんが、VBO 周りの実装としては、配列を引数として受け取るような関数を作ってしまうことで、だいぶ処理を完結に記述できるようになります。
まとめ
前回の内容からの変更点は以上です。要するに、シェーダが少しと、VBO 周辺の処理が少し、変わっただけなんですね。もし、頂点に新しい属性を付加したいと考えたときには、今回やったことと同様の手順で、頂点属性を自由に増やすことが可能です。
最後に、今回のサンプルのコード全文と、サンプルページへのリンクを貼っておきますので、参考にしてみてください。
次回はモデル変換行列を操作して、複数モデルのレンダリングにチャレンジします。
サンプルの HTML ソース
<html>
<head>
<title>WebGL TEST</title>
<script src="script.js" type="text/javascript"></script>
<script src="minMatrix.js" type="text/javascript"></script>
<script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
uniform mat4 mvpMatrix;
varying vec4 vColor;
void main(void){
vColor = color;
gl_Position = mvpMatrix * vec4(position, 1.0);
}
</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>
</body>
</html>
サンプルの javascript ソース
onload = function(){
// canvasエレメントを取得
var c = document.getElementById('canvas');
c.width = 300;
c.height = 300;
// webglコンテキストを取得
var gl = c.getContext('webgl') || c.getContext('experimental-webgl');
// canvasを初期化する色を設定する
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// canvasを初期化する際の深度を設定する
gl.clearDepth(1.0);
// canvasを初期化
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 頂点シェーダとフラグメントシェーダの生成
var v_shader = create_shader('vs');
var f_shader = create_shader('fs');
// プログラムオブジェクトの生成とリンク
var prg = create_program(v_shader, f_shader);
// attributeLocationを配列に取得
var attLocation = new Array(2);
attLocation[0] = gl.getAttribLocation(prg, 'position');
attLocation[1] = gl.getAttribLocation(prg, 'color');
// attributeの要素数を配列に格納
var attStride = new Array(2);
attStride[0] = 3;
attStride[1] = 4;
// 頂点の位置情報を格納する配列
var vertex_position = [
0.0, 1.0, 0.0,
1.0, 0.0, 0.0,
-1.0, 0.0, 0.0
];
// 頂点の色情報を格納する配列
var vertex_color = [
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 position_vbo = create_vbo(vertex_position);
var color_vbo = create_vbo(vertex_color);
// VBOをバインドし登録する(位置情報)
gl.bindBuffer(gl.ARRAY_BUFFER, position_vbo);
gl.enableVertexAttribArray(attLocation[0]);
gl.vertexAttribPointer(attLocation[0], attStride[0], gl.FLOAT, false, 0, 0);
// VBOをバインドし登録する(色情報)
gl.bindBuffer(gl.ARRAY_BUFFER, color_vbo);
gl.enableVertexAttribArray(attLocation[1]);
gl.vertexAttribPointer(attLocation[1], attStride[1], gl.FLOAT, false, 0, 0);
// minMatrix.js を用いた行列関連処理
// matIVオブジェクトを生成
var m = new matIV();
// 各種行列の生成と初期化
var mMatrix = m.identity(m.create());
var vMatrix = m.identity(m.create());
var pMatrix = m.identity(m.create());
var mvpMatrix = m.identity(m.create());
// ビュー座標変換行列
m.lookAt([0.0, 1.0, 3.0], [0, 0, 0], [0, 1, 0], vMatrix);
// プロジェクション座標変換行列
m.perspective(90, c.width / c.height, 0.1, 100, pMatrix);
// 各行列を掛け合わせ座標変換行列を完成させる
m.multiply(pMatrix, vMatrix, mvpMatrix);
m.multiply(mvpMatrix, mMatrix, mvpMatrix);
// uniformLocationの取得
var uniLocation = gl.getUniformLocation(prg, 'mvpMatrix');
// uniformLocationへ座標変換行列を登録
gl.uniformMatrix4fv(uniLocation, false, mvpMatrix);
// モデルの描画
gl.drawArrays(gl.TRIANGLES, 0, 3);
// コンテキストの再描画
gl.flush();
// シェーダを生成する関数
function create_shader(id){
// シェーダを格納する変数
var shader;
// HTMLからscriptタグへの参照を取得
var scriptElement = document.getElementById(id);
// scriptタグが存在しない場合は抜ける
if(!scriptElement){return;}
// scriptタグのtype属性をチェック
switch(scriptElement.type){
// 頂点シェーダの場合
case 'x-shader/x-vertex':
shader = gl.createShader(gl.VERTEX_SHADER);
break;
// フラグメントシェーダの場合
case 'x-shader/x-fragment':
shader = gl.createShader(gl.FRAGMENT_SHADER);
break;
default :
return;
}
// 生成されたシェーダにソースを割り当てる
gl.shaderSource(shader, scriptElement.text);
// シェーダをコンパイルする
gl.compileShader(shader);
// シェーダが正しくコンパイルされたかチェック
if(gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
// 成功していたらシェーダを返して終了
return shader;
}else{
// 失敗していたらエラーログをアラートする
alert(gl.getShaderInfoLog(shader));
}
}
// プログラムオブジェクトを生成しシェーダをリンクする関数
function create_program(vs, fs){
// プログラムオブジェクトの生成
var program = gl.createProgram();
// プログラムオブジェクトにシェーダを割り当てる
gl.attachShader(program, vs);
gl.attachShader(program, fs);
// シェーダをリンク
gl.linkProgram(program);
// シェーダのリンクが正しく行なわれたかチェック
if(gl.getProgramParameter(program, gl.LINK_STATUS)){
// 成功していたらプログラムオブジェクトを有効にする
gl.useProgram(program);
// プログラムオブジェクトを返して終了
return program;
}else{
// 失敗していたらエラーログをアラートする
alert(gl.getProgramInfoLog(program));
}
}
// VBOを生成する関数
function create_vbo(data){
// バッファオブジェクトの生成
var vbo = gl.createBuffer();
// バッファをバインドする
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
// バッファにデータをセット
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
// バッファのバインドを無効化
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// 生成した VBO を返して終了
return vbo;
}
};