テクスチャマッピング
今回のサンプルの実行結果
WebGL でテクスチャ
前回は点光源によるライティングをフォンシェーディングで実装する方法について解説しました。
フラグメントシェーダで光の計算をすることによって、陰影やハイライトが非常に綺麗に表現されるようになり、3D シーンのリアリティが大幅に向上します。頂点色を併用すればモデルに色をつけることも可能ですし、前回までの内容をしっかり理解できていれば、既にそこそこクオリティの高い 3D レンダリングが行なえるようになっているはずです。
今回は、さらなるレベルアップを目指していよいよテクスチャを利用したレンダリングを行なってみます。テクスチャとは、簡単に言うとポリゴンに貼り付けることができるイメージデータのことで、WebGL でももちろん利用することができます。
WebGL では、HTML で一般的に利用できる画像フォーマット(gif、jpg、pngなど)のイメージデータをそのままテクスチャとして利用できます。また、工夫次第では canvas をテクスチャとして利用するなど、ちょっと変わった方法でレンダリングを行うこともできます。
今回はまず、テクスチャを使った基本的な描画についてしっかり覚えてしまいましょう。
WebGL テクスチャの制約
先ほども書いたように WebGL では HTML で一般的に使われる画像フォーマットをテクスチャとして利用することが可能です。これはどういうことかと言うと、要は HTML でウェブページに埋め込むことが可能な画像データを、WebGL がそのまま流用できるということを意味します。
ただし WebGL のテクスチャにはちょっとした注意点があり、利用できる画像データのサイズが2 の累乗である必要があります。縦横のピクセルサイズが 32 x 32 や、128 x 128 など、2 の累乗となっている画像データが必要なのですね。
ちょっとした工夫をすれば 2 の累乗以外の画像データを使うことも可能ですが、基本的にはテクスチャとして利用する画像データは 2 の累乗サイズでなければならないので忘れずに覚えておきましょう。
また、普通にウェブブラウジングをしていれば体感的にわかると思いますが、ウェブ上の画像データを読み込むのには多少の時間がかかります。テクスチャに画像データを適用する処理は、画像の読み込みが完了してから行なう必要があり、このあたりはちょっと特殊な記述を行なわなければなりません。javascript の記述に慣れていないと少し戸惑う部分もあるかもしれませんが、そこは落ち着いて考えましょう。
テクスチャの生成とイメージの適用
さて、それでは実際にテクスチャを利用するための手順を解説していきます。
テクスチャは、WebGL の中でテクスチャオブジェクトとして扱われます。このテクスチャオブジェクトを生成するためのメソッドが createTexture
メソッドです。
createTexture の記述例
var tex = gl.createTexture();
このメソッドは引数を持ちません。純粋に、空のテクスチャオブジェクトを返してきます。上記のような記述をすると、変数 tex
に空のテクスチャオブジェクトが入るわけですね。
テクスチャオブジェクトを生成することができたら、次にこのテクスチャオブジェクトを WebGL にバインドします。
思い出してみてほしいのですが、WebGL ではバッファオブジェクトなどを利用する際にも、このように WebGL にバインドすることによって処理を行なっていましたよね。VBO を使う場合にも、今回と同じようにバッファをバインドする処理が必要でした。これと同じように、テクスチャオブジェクトに操作を加える場合には、まず始めに対象のテクスチャオブジェクトを WebGL にバインドします。その後、テクスチャに操作を加えるメソッドなどを呼び出すと、バインドされているテクスチャオブジェクトに対して処理が適用されるようになっています。
テクスチャオブジェクトを WebGL にバインドするには、 bindTexture
メソッドを利用します。
bindTexture の記述例
gl.bindTexture(gl.TEXTURE_2D, tex);
このメソッドは引数を二つ取ります。第一引数にはテクスチャの種類を表す組み込み定数を指定しますが、いわゆる普通の二次元画像フォーマットであればこの引数には gl.TEXTURE_2D
を常に指定します。第二引数にはバインドするテクスチャオブジェクトを指定すれば OK です。ここまでは簡単ですね。
さて、ここまでの段階で WebGL にテクスチャオブジェクトがバインドされましたが、肝心の画像データがテクスチャにはまだ適用されていません。テクスチャに画像データを紐付けるために必要となるのが texImage2D
メソッドです。
texImage2D の記述例
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
上記の記述例を見ると、なんじゃこりゃーと思う人もいるかもしれませんね。このメソッドは引数を六つ取りますので気後れしそうになりますが、まぁ、意外と簡単です。
第一引数は bindTexture
でも使ったテクスチャの種類を指定します。ここでも組み込み定数 gl.TEXTURE_2D
を使えば問題ありません。第二引数はミップマップのレベルを指定するのですが、とりあえず現段階では何も考えずに 0 を指定しておけば問題ありません。また、第三引数と第四引数には同じ組み込み定数 gl.RGBA
が指定されていますが、ここも、現段階では深く考えずにこれを指定すれば大丈夫です。
同様に、第五引数の gl.UNSIGNED_BYTE
についても特別な理由がない限りこのままで大丈夫です。要は、現段階では第一引数から第五引数までは上記の記述例を丸写しで大丈夫です。このメソッドで最も大切なのは最後の第六引数、ここに画像のデータを指定します。この第六引数に指定されて入ってきた画像データが、その時点でバインドされているテクスチャに割り当てられます。
画像のロード時間を考慮する
先ほども書いたとおり texImage2D
メソッドを使えばテクスチャに画像データを割り当てることが可能です。ただし、ウェブページの画像データがそうであるように、オンラインでは画像の読み込みに時間がかかります。
ここで注意しなければならないのは、 texImage2D
メソッドが呼び出されたとき、既に画像の読み込みが完了していなければならないという点です。先述のとおり texImage2D
メソッドの第六引数にはテクスチャに割り当てる画像データを渡しますが、画像データが完全に読み込まれていない状態でメソッドを実行しても、正しくテクスチャへの画像の割り当てが行なわれません。
そこで、画像データの読み込みが完了した時点で発生するイベントをトリガーとして処理が実行される仕組みを実装します。
具体的には、まず javascript のプログラム上でイメージオブジェクトを生成します。イメージオブジェクトには、画像の読み込みが完了したことを通知する onload
イベントがありますので、これにテクスチャ関連の処理を割り当てておきます。最後に、このイメージオブジェクトに元画像のアドレス(ソース)を指定して画像の読み込みをスタートします。
ここで重要となるのは、画像の読み込みを開始する前に、あらかじめ onload
イベントにテクスチャ関連の処理を割り当てておくことです。そうすることで、画像が読み込まれたと同時にテクスチャに関する処理が自動的に実行されるようになります。
ここまで解説してきたことを踏まえて、テクスチャを生成する関数を作成してみます。
テクスチャ生成関数
function create_texture(source){
// イメージオブジェクトの生成
var img = new Image();
// データのオンロードをトリガーにする
img.onload = function(){
// テクスチャオブジェクトの生成
var tex = gl.createTexture();
// テクスチャをバインドする
gl.bindTexture(gl.TEXTURE_2D, tex);
// テクスチャへイメージを適用
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
// ミップマップを生成
gl.generateMipmap(gl.TEXTURE_2D);
// テクスチャのバインドを無効化
gl.bindTexture(gl.TEXTURE_2D, null);
// 生成したテクスチャをグローバル変数に代入
texture = tex;
};
// イメージオブジェクトのソースを指定
img.src = source;
}
この自作関数 create_texture
は、テクスチャオブジェクトに割り当てる画像のアドレス(ソース)を引数として受け取ります。関数内部では、まず始めにイメージオブジェクトを新たに生成していますね。このイメージオブジェクトに画像をロードすることになるわけですが、画像の読み込みを開始する前にあらかじめオンロードイベントに処理を登録しておきます。
オンロードイベントに登録する処理の中では、WebGL のメソッドを使ってテクスチャオブジェクトの生成、またテクスチャをバインドする処理やイメージデータを適用する処理を仕込んでおきます。
関数内部で texImage2D
メソッドが実行されテクスチャへイメージを適用した後、なにやら怪しげなメソッドが実行されているのですがわかるでしょうか。ミップマップを生成するために必要となる generateMipmap
メソッドの実行がそれです。
ミップマップというのは、あらかじめ複数の大きさのイメージデータを内部的に用意しておく仕組みのことで、WebGL だけでなく、3D プログラミング全般に登場する一般的な概念です。ミップマップは、実際にテクスチャが使われるシーンにおいて、テクスチャイメージを縮小表示する際に力を発揮します。あらかじめ小さく縮小されたイメージデータを内部的に用意しておき適切に切り替えながらレンダリングすることで、イメージが極端に小さく縮小されたとしてもレンダリング結果が綺麗に表示されるように調整してくれます。
このように generateMipmap
メソッドが実行されることによって、ミップマップが生成されます。このメソッドは引数を取りますが、 bindTexture
などと同様に gl.TEXTURE_2D
を指定しておけば問題ありません。
VBO のときと同様、WebGL にはバインドできるテクスチャが同時に一つしかないため、最後にバインドを解除しています。最終的に生成されたテクスチャオブジェクトは、今回はグローバルな変数に代入するようにしていますが、これは、オンロードイベント自体が戻り値を返すことができないためです。上記の例で言うと、変数 texture
は create_texture
関数が参照できるスコープにある変数でなければいけません。
オンロードイベントに対する仕込が終わったら、最後にイメージオブジェクトにソースを指定します。先にオンロードイベントが仕込んであるので、画像の読み込みが完了した時点で、自動的にオンロードイベントが実行されテクスチャが正しく生成されるというわけです。
テクスチャ座標と頂点属性
さて、これでテクスチャオブジェクトを生成する方法はわかりましたね。しかし、肝心なのはこのテクスチャをどうやってポリゴンに貼り付けるかです。
ポリゴンにテクスチャを貼り付けるためには、ポリゴンを形成している頂点に対して、テクスチャをどのように貼り付けたらいいのかという情報を持たせておく必要があります。これは、頂点に対して新しい頂点属性を付加しなければならないということを意味します。
以前のテキスト(頂点バッファの基礎)で詳しく解説していますが、頂点に情報を付加するためには VBO を新しく用意しなければなりませんね。そして今回新たに追加する VBO には、頂点のテクスチャ座標を格納します。テクスチャ座標はその名の通り、テクスチャのどの座標を使えばいいのかということを表すために使われます。
テクスチャ座標は、0 ~ 1 の範囲で指定するようにします。そして、横方向と縦方向、二つの要素によって成り立っています。ですからテクスチャ座標を表現する際は (0.0, 0.0) のように、二つの要素を持つデータとして表します。
そしてここからが少しややこしいのですが、普通、画像データの座標系は左上を原点として考えます。これを図にすると、次のような感じですね。
左上を原点とし、右に行くほど、あるいは下に行くほど、X と Y の数値が大きくなるわけです。これに対し、WebGL のテクスチャの座標系は次のようになっています。
座標系の上下が逆転しているのがわかりますね。テクスチャ座標では左下が原点となり、縦方向の位置を表す数値が上に行くほど大きくなります。ただ、二つの図を見比べてみればわかりますが、テクスチャ座標を指定するときに、通常は深く考えすぎる必要はありません。なぜなら、中身のイメージも上下反転していますよね。ですから、画像データの左上が原点であるのと同じような考え方で直感的にテクスチャ座標を指定しても、結果的にはキチンとイメージは表示されます。
現時点ではただ単に、テクスチャ空間上では座標が上下反転しているということをなんとなく知っていれば大丈夫です。※将来的にテクスチャを使って特殊なことをしようとする段階でこの辺の知識が必要になってくることもあります
javascript を修正
さて、それではプログラムをテクスチャが利用できる状態に修正していきましょう。
まず前提として、今回はライティングを全部切っておきます。また、レンダリングするのはテクスチャを貼り付けた板状のポリゴンだけです。トーラスや球体でやってもいいのですが若干紛らわしい部分もありますので、初心に戻り簡素なポリゴンモデルでやってみます。※単なる板ポリ一枚描画するだけですので今回はカリングも切っておきます
まず最初にするべきことはモデルの頂点データを準備することです。今回は先述のとおり、テクスチャ座標を格納する新たな頂点属性が増えます。逆に、ライティングは行ないませんので法線などの情報は今回使いません。
頂点データの準備
// attributeLocationを配列に取得
var attLocation = new Array();
attLocation[0] = gl.getAttribLocation(prg, 'position');
attLocation[1] = gl.getAttribLocation(prg, 'color');
attLocation[2] = gl.getAttribLocation(prg, 'textureCoord');
// attributeの要素数を配列に格納
var attStride = new Array();
attStride[0] = 3;
attStride[1] = 4;
attStride[2] = 2;
// 頂点の位置
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, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0
];
// テクスチャ座標
var textureCoord = [
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0
];
// 頂点インデックス
var index = [
0, 1, 2,
3, 2, 1
];
// VBOとIBOの生成
var vPosition = create_vbo(position);
var vColor = create_vbo(color);
var vTextureCoord = create_vbo(textureCoord);
var VBOList = [vPosition, vColor, vTextureCoord];
var iIndex = create_ibo(index);
// VBOとIBOの登録
set_attribute(VBOList, attLocation, attStride);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iIndex);
四つの頂点からなる四角形ポリゴンを定義しています。頂点の位置を格納している部分をよく見ればわかると思いますが、この四角形は原点を中心に、アルファベットの Z を描くような順序で頂点を定義しています。また、頂点の色は全て不透明な白としています。
テクスチャ座標は縦と横、二つの要素を持つ頂点属性として定義します。これは先ほども書いたとおりですね。
頂点データが配列として準備できたら、いつものように VBO と IBO を生成して登録しておきます。この辺は今までやってきたこととまったく同じです。これで頂点に関する処理、つまりシェーダ内で attribute 変数として処理されるデータの準備は OK です。
さて、続いては uniform 変数としてシェーダ内で扱われるデータの準備です。
今回は何度も言うようにライティングを行ないませんので、座標変換行列を準備すれば頂点を処理することが可能になります。逆行列やライトの位置など、前回まで使っていたようなライティングに関するデータはいりません。
ただし、uniform 修飾子付き変数の特徴を考えると、もう一つ、uniform 変数としてデータを送らなければならないということに気が付くはずです。uniform 修飾子付き変数は、全ての頂点に対して一律に使われるデータを扱うためのものでしたよね。ということは、全ての頂点で同じように利用するテクスチャデータは、uniform 変数としてシェーダに送らなければならないことがわかります。
uniform 関連処理
// uniformLocationを配列に取得
var uniLocation = new Array();
uniLocation[0] = gl.getUniformLocation(prg, 'mvpMatrix');
uniLocation[1] = gl.getUniformLocation(prg, 'texture');
今回利用する uniform 変数は二つ。一つは座標変換行列を扱うために使います。そしてもう一つはテクスチャオブジェクトのデータをシェーダにプッシュするために使います。
テクスチャを有効にする
テクスチャにはユニットという概念があります。これはテクスチャに番号をつけて管理するためのもので、既定では 0 番のテクスチャユニットが有効になります。テクスチャユニットは、複数のテクスチャを扱う場合などに力を発揮しますが、今回はとりあえず使うテクスチャが一つなので、そのまま 0 番目のユニットを使います。
特定のテクスチャユニットを有効化するには、 activeTexture
メソッドを使います。
テクスチャユニットを有効にする
// 有効にするテクスチャユニットを指定
gl.activeTexture(gl.TEXTURE0);
このとき引数に指定されている gl.TEXTURE0
という組み込み定数は、そのままテクスチャのユニット番号を表しています。1 番目のテクスチャユニットを有効化したければ gl.TEXTURE1
とすればいいわけですね。ただ、特別な理由がない限りは、テクスチャユニットは小さい数値から順番に使っていくようにしましょう。
テクスチャユニットの最大数(上限値)
複数のテクスチャを同時に利用する場合に必要となるテクスチャユニットですが、その最大ユニット数は実行される環境によって異なります。WebGL はパソコンなどのハードのほかに、モバイル機器などでも動作する可能性があるので、何番目のテクスチャユニットまで使って大丈夫なのかは非常に判断が難しい部分です。
ハードウェアの性能に左右される最大ユニット数ですが、事前に調べて処理を分岐させることは可能です。実行環境の最大テクスチャユニット数を調べるには getParameter
メソッドを使います。
記述例:
gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
無駄に長い組み込み定数 gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS
を使って getParameter
メソッドを実行し、返ってきた整数値が利用できる最大テクスチャユニット数になります。もし戻り値が 10 だったとすれば、 gl.TEXTURE0
~ gl.TEXTURE9
までの合計 10 個のテクスチャユニットが使えることになります。
テクスチャデータをシェーダに送る
さてそろそろ大詰めです。適切なテクスチャユニットを有効にしたら、次にするべきはテクスチャを WebGL にバインドすることです。これは、テクスチャにイメージデータを適用する際にもやりましたので簡単ですね。
テクスチャを WebGL にバインドする
// テクスチャをバインドする
gl.bindTexture(gl.TEXTURE_2D, texture);
そして、バインドしたテクスチャの情報をシェーダに送るために、uniform 変数としてシェーダに送る処理が必要です。あらかじめ取得してあった uniformLocation を使って、テクスチャデータを送る処理は次のようになります。
テクスチャデータをシェーダに送る
// uniform変数にテクスチャを登録
gl.uniform1i(uniLocation[1], 0);
ここでのポイントは、行列やベクトルをシェーダに送るのとは違い、あくまでもテクスチャユニットの番号を送っているだけという点です。 uniform1i
メソッドは、その名称からもわかるとおり、一つの整数値をシェーダに送るために使われるメソッドです。第二引数を見ればわかるとおり、シェーダに送られるのは 0 という整数値一つだけです。これは有効化されているテクスチャユニットの番号と一致していますね。
シェーダを修正する
さて続いてはシェーダのソースを修正します。まずは頂点シェーダ。
頂点シェーダのソース
attribute vec3 position;
attribute vec4 color;
attribute vec2 textureCoord;
uniform mat4 mvpMatrix;
varying vec4 vColor;
varying vec2 vTextureCoord;
void main(void){
vColor = color;
vTextureCoord = textureCoord;
gl_Position = mvpMatrix * vec4(position, 1.0);
}
頂点シェーダでは、頂点の位置、頂点の色、そして頂点のテクスチャ座標を attribute
修飾子付き変数としてプログラムから受け取ります。また、フラグメントシェーダには頂点の色とテクスチャ座標をそのまま渡します。
頂点シェーダに関してはそれほど難しいことをやっているわけでもないので、問題ないでしょう。続いてフラグメントシェーダ。
フラグメントシェーダのソース
precision mediump float;
uniform sampler2D texture;
varying vec4 vColor;
varying vec2 vTextureCoord;
void main(void){
vec4 smpColor = texture2D(texture, vTextureCoord);
gl_FragColor = vColor * smpColor;
}
フラグメントシェーダでは uniform
修飾子付き変数としてテクスチャデータが入ってきます。このとき、変数の型として sampler2D
が使われることに注意しましょう。サンプラーというのはサンプリングされたテクスチャということを意味する言葉で、とりあえずは、要するにテクスチャのデータなのだと考えておけば実用的には問題ありません。
そして、テクスチャデータからフラグメントの情報を抜き出すために使われるのが texture2D
関数です。この関数は引数を二つ取り、第一引数にサンプラー型のテクスチャデータを、第二引数にテクスチャ座標を表す vec2
型のデータを渡します。
今回のサンプルの場合、頂点シェーダに attribute
変数として入ってきた頂点のテクスチャ座標を、頂点シェーダからフラグメントシェーダに varying
変数として渡しています。フラグメントシェーダ側では、頂点シェーダから渡されたテクスチャ座標を使って texture2D
関数を呼び出しているわけですね。
このように texture2D
関数を使ってテクスチャの色情報を取り出したあと、頂点の色を表す varying
変数 vColor
と掛け合わせた色が最終的に出力される色となります。
まとめ
さて、テクスチャを使う方法について駆け足で見てきましたが、理解できましたでしょうか。
テクスチャ周りの実装は非常に冗長になりがちですが、要は、テクスチャ座標とテクスチャオブジェクト、さらにそれを処理するためのシェーダの実装さえあれば対応できます。テクスチャにはユニットという番号を使った概念があり、これらを適切に操作することで、複数のテクスチャを使うことも可能になります。
今回は非常に基本的な部分だけの実装ですが、変更点もそれなりに多いので一応コードの全文を掲載しておきます。また、テキストの最後にはいつものようにサンプルへのリンクも貼っておきます。
次回は、テクスチャを複数使ってレンダリングする方法を解説します。
index.html
<html>
<head>
<title>wgld.org WebGL sample</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;
attribute vec2 textureCoord;
uniform mat4 mvpMatrix;
varying vec4 vColor;
varying vec2 vTextureCoord;
void main(void){
vColor = color;
vTextureCoord = textureCoord;
gl_Position = mvpMatrix * vec4(position, 1.0);
}
</script>
<script id="fs" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D texture;
varying vec4 vColor;
varying vec2 vTextureCoord;
void main(void){
vec4 smpColor = texture2D(texture, vTextureCoord);
gl_FragColor = vColor * smpColor;
}
</script>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
script.js
onload = function(){
// canvasエレメントを取得
var c = document.getElementById('canvas');
c.width = 500;
c.height = 300;
// webglコンテキストを取得
var gl = c.getContext('webgl') || c.getContext('experimental-webgl');
// 頂点シェーダとフラグメントシェーダの生成
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();
attLocation[0] = gl.getAttribLocation(prg, 'position');
attLocation[1] = gl.getAttribLocation(prg, 'color');
attLocation[2] = gl.getAttribLocation(prg, 'textureCoord');
// attributeの要素数を配列に格納
var attStride = new Array();
attStride[0] = 3;
attStride[1] = 4;
attStride[2] = 2;
// 頂点の位置
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, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0
];
// テクスチャ座標
var textureCoord = [
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0
];
// 頂点インデックス
var index = [
0, 1, 2,
3, 2, 1
];
// VBOとIBOの生成
var vPosition = create_vbo(position);
var vColor = create_vbo(color);
var vTextureCoord = create_vbo(textureCoord);
var VBOList = [vPosition, vColor, vTextureCoord];
var iIndex = create_ibo(index);
// VBOとIBOの登録
set_attribute(VBOList, attLocation, attStride);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iIndex);
// uniformLocationを配列に取得
var uniLocation = new Array();
uniLocation[0] = gl.getUniformLocation(prg, 'mvpMatrix');
uniLocation[1] = gl.getUniformLocation(prg, 'texture');
// 各種行列の生成と初期化
var m = new matIV();
var mMatrix = m.identity(m.create());
var vMatrix = m.identity(m.create());
var pMatrix = m.identity(m.create());
var tmpMatrix = m.identity(m.create());
var mvpMatrix = m.identity(m.create());
// ビュー×プロジェクション座標変換行列
m.lookAt([0.0, 2.0, 5.0], [0, 0, 0], [0, 1, 0], vMatrix);
m.perspective(45, c.width / c.height, 0.1, 100, pMatrix);
m.multiply(pMatrix, vMatrix, tmpMatrix);
// 深度テストを有効にする
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
// 有効にするテクスチャユニットを指定
gl.activeTexture(gl.TEXTURE0);
// テクスチャ用変数の宣言
var texture = null;
// テクスチャを生成
create_texture('texture.png');
// カウンタの宣言
var count = 0;
// 恒常ループ
(function(){
// canvasを初期化
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// カウンタを元にラジアンを算出
count++;
var rad = (count % 360) * Math.PI / 180;
// テクスチャをバインドする
gl.bindTexture(gl.TEXTURE_2D, texture);
// uniform変数にテクスチャを登録
gl.uniform1i(uniLocation[1], 0);
// モデル座標変換行列の生成
m.identity(mMatrix);
m.rotate(mMatrix, rad, [0, 1, 0], mMatrix);
m.multiply(tmpMatrix, mMatrix, mvpMatrix);
// uniform変数の登録と描画
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);
// コンテキストの再描画
gl.flush();
// ループのために再帰呼び出し
setTimeout(arguments.callee, 1000 / 30);
})();
// シェーダを生成する関数
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;
}
// VBOをバインドし登録する関数
function set_attribute(vbo, attL, attS){
// 引数として受け取った配列を処理する
for(var i in vbo){
// バッファをバインドする
gl.bindBuffer(gl.ARRAY_BUFFER, vbo[i]);
// attributeLocationを有効にする
gl.enableVertexAttribArray(attL[i]);
// attributeLocationを通知し登録する
gl.vertexAttribPointer(attL[i], attS[i], gl.FLOAT, false, 0, 0);
}
}
// IBOを生成する関数
function create_ibo(data){
// バッファオブジェクトの生成
var ibo = gl.createBuffer();
// バッファをバインドする
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
// バッファにデータをセット
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(data), gl.STATIC_DRAW);
// バッファのバインドを無効化
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
// 生成したIBOを返して終了
return ibo;
}
// テクスチャを生成する関数
function create_texture(source){
// イメージオブジェクトの生成
var img = new Image();
// データのオンロードをトリガーにする
img.onload = function(){
// テクスチャオブジェクトの生成
var tex = gl.createTexture();
// テクスチャをバインドする
gl.bindTexture(gl.TEXTURE_2D, tex);
// テクスチャへイメージを適用
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
// ミップマップを生成
gl.generateMipmap(gl.TEXTURE_2D);
// テクスチャのバインドを無効化
gl.bindTexture(gl.TEXTURE_2D, null);
// 生成したテクスチャをグローバル変数に代入
texture = tex;
};
// イメージオブジェクトのソースを指定
img.src = source;
}
};