wgld.orgの管理人であるdoxasさん主催のWebGLスクールの第六回目です。
今回の本題はテクスチャを使いモデルに画像データを適用するというものです。テクスチャを使うと一気に表現できるものの幅が広がるので、しっかりと理解しておきたいところです。
前回までのまとめ
第一回 WebGLスクール 「WebGLの概念」
第二回 WebGLスクール 「WebGLの手続きと手順」
第三回 WebGLスクール 「シェーダの基礎」
第四回 WebGLスクール 「行列とクォータニオンについて知る」
第五回 WebGLスクール 「ライティングの基本」
環境光と反射光
前回のライティングでは、拡散光を表現しました。今回は環境光と反射光を取り入れてみます。
いずれの処理もフラグメントシェーダ側で実装します。
環境光について
環境光とは、光がまわりにあるものに反射して全体に広がることです。色々な光の表現がある中で環境光を再現するのが一番大変とのこと。今でもお偉い学者さんたちの間で討論されているくらいが難しいのが環境光です。
今回の内容では、一番簡単な環境光の実装方法を実践しました。考え方としては、シーン全体に均一にぼんやりと照らし出す光を表現する。つまり、uniformで送った環境光のデータをライティング係数に追加する。
フラグメントシェーダの記述は以下のようになる。一般的に環境光はambient
で表されます。
precision mediump float;
uniform mat4 invMatrix;
uniform vec3 lightDirection;
uniform vec3 ambientColor; // ← 追加された変数
varying vec3 vNormal;
varying vec4 vColor;
void main(){
vec3 inverseLight = normalize(invMatrix * vec4(lightDirection, 1.0)).xyz;
float diff = clamp(dot(inverseLight, vNormal), 0.1, 1.0);
// ambientColorを足す
gl_FragColor = vec4(vColor.rgb * diff + ambientColor, vColor.a);
}
適用結果は以下のようになります。ぼんやりと全体が明るくなりました。
反射光
反射光は、視点の影響を考慮し、つるつるとした表面を再現することができる。
光線がモデルに反射し、カメラまで一直線に向かう表現をするため カメラの位置 が重要なポイントになります。
シェーダにカメラの位置を割り出すために カメラの位置とカメラの注視点 を送ります。
環境光と同じようにフラグメントシェーダに処理を追加していきます。反射光は一般的にはspecular
と表されます。
precision mediump float;
uniform mat4 invMatrix;
uniform vec3 lightDirection;
uniform vec3 ambientColor;
uniform vec3 eyePosition; // ← 追加された変数 カメラの位置
uniform vec3 centerPoint; // ← 追加された変数 カメラの注視点
varying vec3 vNormal;
varying vec4 vColor;
void main(){
vec3 eyeDirection = eyePosition - centerPoint; // 注視点からカメラまでの視線ベクトル
vec3 inverseLight = normalize(invMatrix * vec4(lightDirection, 1.0)).xyz;
vec3 inverseEye = normalize(invMatrix * vec4(eyeDirection, 1.0)).xyz; // カメラ位置に逆行列を掛ける
vec3 halfLightEye = normalize(inverseLight + inverseEye); // ハーフベクトル 反射ベクトルと近似の値を求める
float diff = clamp(dot(inverseLight, vNormal), 0.1, 1.0);
float spec = pow(clamp(dot(halfLightEye, vNormal), 0.0, 1.0), 20.0); // 累乗計算をする
// specを足す
gl_FragColor = vec4(vColor.rgb * diff + spec + ambientColor, vColor.a);
}
ハーフベクトルは今の段階で深く理解する必要は無いようです。
注視点とカメラの位置を減算して視線ベクトルを求め、clamp
で内積を用いた結果にpow
関数を使い累乗計算をしgl_FragColor
に足します。結果を見ると分かるようにつるつるとした表面を再現できます。
点光源の扱いについて
平行光源はライトベクトルが常に同じだったのに対し、点光源の場合は、 光源の位置と頂点の位置 によりライトベクトルが変化する。つまり、動的にライトベクトルを求める必要がある。
シェーダ内でどの状態の頂点情報を利用すればいいのかを把握する。頂点の位置から光源の位置とを比較し、動的にライトベクトルを算出する。
テクスチャ
テクスチャとは、WebGLで画像データを扱うためのオブジェクト。WebGLではあらゆるものがオブジェクトとして管理されるため、テクスチャもテクスチャオブジェクトとして扱う必要があります。
テクスチャに利用できるもの
テクスチャをして扱うことができる画像データは、一般的なウェブサイトと同じくjpgやpngが利用できます。
WebGLでレンダリングした結果をテクスチャとして利用できるなど、幅広く使うことができる。
テクスチャの扱い
他のオブジェクトと同じように、テクスチャもまずは生成から始まる。生成後にいろいろなデータを割り当てていく。注意しなくてはいけないポイントとして、 完全にロードしてからテクスチャにデータを割り当てる という点がある。
ロード感知
完全にロードしてから処理をしなくてはならないので、javascriptのonload
イベントを使用する。
実際のコードは以下のような感じ
function create_texture(source, number){
// イメージオブジェクトの生成
var img = new Image();
// データのオンロードをトリガーにする
img.onload = function(){
~ ロード完了後の処理 ~
}
}
用語と知識
ミップマップ
ミップマップとは、あらかじめ小さく縮小した画像を用意しておき効率よく描画を行う仕組みのこと。
カメラから遠ざかるほど解像度の低い小さな画像が描画され、カメラに近いほど高解像度な画像が描画される。
累乗
テクスチャの画像データは、一辺のサイズが 二の累乗 でないとならない。これは知らないとけっこうハマってしまいそうなので、しっかりと覚えておきたいポイント。具体的には、128/256/512/1024などですね。将来的にはこのルールがなくなるかもしれないようですが、現時点では、二の累乗のサイズを厳守しないといけない。
クロスオリジン問題
ローカル環境からテクスチャを使用したWebGLを実行すると、画像ファイルが外部から取得されたと見なされ、上手くマッピングされなくなってしまいます。画像データが同じ階層にあろうがローカル環境ではテクスチャが適用されません。そのためテクスチャを扱う場合は、ローカルサーバーを立てて実行する必要があります。
テクスチャ座標
モデルにテクスチャを適用するには、個々の頂点どのようにテクスチャを張るかの固有の情報が必要になる。
一般的にテクスチャの座標は頂点属性として持たせることが多いので、頂点属性を追加する必要があります。
minMatrix.jsのモデル出力関数を使うためには、object.t
を用います。モデル出力関数を取得したら、新たにVBOを生成します。
var sphereData = sphere(64, 64, 1.0, [1.0, 1.0, 1.0, 1.0]);
var vTexCoord = sphereData.t;
// VBOの生成
var attVBO = [];
attVBO[3] = create_vbo(vTexCoord);
テクスチャ座標は、 読み込まれると自動的に上下が反転される という特徴があり、原点は左下になります。
つまり、読み込ませる前の画像データのままのイメージで原点(0.0,0.0)を指定すると左上を指定することになります。
テクスチャユニット
テクスチャを扱うためには、 テクスチャユニットの番号 を指定する汎用データを追加する必要があります。
テクスチャユニットとは、複数のテクスチャがある状態でどのテクスチャを使用するのかを指定するための番号です。テクスチャユニットを指定しないと自動的に0番目が指定される。
テクスチャユニット用の変数
テクスチャユニットを受け取るために、シェーダ側でuniform変数を追加します。
uniform sampler2D textureUnit;
sampler2D
という型を指定するこでテクスチャユニット番号を受け取ることができます。
シェーダ側でuniformを追加したら、今度はjavascript側でロケーションを取得しプッシュします。
// ロケーションの取得
uniLocation[7] = gl.getUniformLocation(prg, 'textureUnit');
// データをプッシュ
gl.uniform1i(uniLocation[7], 0); // ← 1i を使って整数を一つ送る
テクスチャはドローコールが実行されるまでに必ずバインドしておくのがポイントです。
テクスチャの色情報
テクスチャの色情報を取得するには、texture2D
という関数を使用します。どの座標の色を拾うのかを指定するために、頂点の持つテクスチャ座標を利用する。
// textureUnit テクスチャユニット
// vTexCoord テクスチャ座標
vec3 texColor = (texture2D(textureUnit, vTexCoord)).xyz;
複数テクスチャの利用
テクスチャはもちろん複数使いことが可能。いくつか方法がありようですが、今回は以下の2つ方法で複数テクスチャを扱ってみました。
- バインドするテクスチャを切り替える方法
- 利用するテクスチャユニットは0番のみ。
- バインドするテクスチャを切り替えて複数使用する
- 複数のテクスチャをバインドしておく方法
- 複数のテクスチャを同時にバインドする。
- gl.activeTexture(gl.TEXTURE0)を用いて利用するユニット番号を指定する。
- 0以外のユニット番号は、指定しないと利用できない。
ちなみに、gl.TEXTURE0
でユニット番号をしていしましたが、指定できるユニット番号はgl.TEXTURE32
で32までが限界の様です。限界というのは厳密には端末が対応していれば、それ以上もいけるようです。ちょっと気になったのでスクールの講師であるdoxasさんに質問してみたところ丁寧に回答してくれました。
@konweb ちなみに、32枚が最大上限ということではなくて端末が対応していればそれ以上の枚数を利用することもできます。ただし、gl.TEXTURE○○ という定数の上限は31までみたいですね。それ以上は、たとえば34016などのように直接数値で指定する感じでいけるっぽいです。
— h_doxas (@h_doxas) 2014, 12月 10
テクスチャ30枚弱で限界??たくさんテクスチャ使いたい場合どうするんだろう・・・?
こちらも気になったので質問して回答をいただきました。doxasさんありがとうございます!
@konweb 一枚のテクスチャでも、テクスチャ座標をうまく頂点に設定してやることができれば、それでテクスチャ枚数自体は節約できます。例⇒ http://t.co/CF1ufqWFEM こういう技術を使って、少ないテクスチャで複数のモデルにいろんな外見を設定してるんだと思います。
— h_doxas (@h_doxas) 2014, 12月 10
テクスチャパラメータ
テクスチャには、品質や性質を自由に設定できる仕組みがある。
テクスチャのパラメータの変更には、gl.texParameteri()
を使います。第二引数で 何に対して、第三引数で どのような指定をするか を指定します。
テクスチャのパラメータは予備知識として覚えておくと良いそうです。
画素の補完方法
画素の補完の選択肢は、とても多く拡大時と縮小時のそれぞれを指定する。
- 拡大時の指定(第二引数に): gl.TEXTURE_MAG_FILTER
- 縮小時の指定(第二引数に): gl.TEXTURE_MIN_FILTER
- 補間方法(第三引数に): gl.NEAREST や gl.LINEAR
テクスチャ座標の扱い
- 横方向について(第二引数に): gl.TEXTIRE_WRAP_S
- 縦方向について(第二引数に): gl.TEXTIRE_WRAP_T
- 値の扱い方(第三引数に): gl.REPEAT や gl.CLAMP_TO_EDGE
感想
今回は、前回のライディングの続きをテクスチャについて学びました。画像データを扱えるようになると一気に何でも出来そう感が出てきます。しかし、テクスチャは使いすぎるとかなり負荷がかかる処理の様なので、使い方をしっかり覚えないと。スクールの内容がどんどん面白くなってきている反面、前半の基礎の部分をもう一度復習しないとだめだな〜とも感じます。(実際にまとめていても忘れている箇所がちらほら)
次回はブレンディングについてです。半透明オブジェクトを生成できたり、色を混ぜたりとまた面白そうです。
続き
第七回 WebGLスクール 「ブレンドファクターとアルファブレンディング」
第八回 WebGLスクール 「シェーダエフェクトテクニック」
第十回 WebGLスクール 「ポストエフェクトテクニック」
第十一回 WebGLスクール「キューブ環境マッピング」