キューブ環境バンプマッピング

実行結果

今回のサンプルの実行結果

テクニックの合わせ技

前回はキューブ環境マッピングについて解説しました。

キューブ環境マッピングを用いると、まるで磨きぬかれた金属のような映り込みのあるモデルをレンダリングできるのでしたね。今回は、キューブ環境マッピングと以前紹介したバンプマッピングの合わせ技をやってみたいと思います。

キューブ環境マッピングとバンプマッピングを同時に適用すると、ポリゴン数を抑えたまま、複雑な凹凸のある(ように見える)キューブマッピングを行うことができます。

バンプマッピングのときにやったのと同じように、今回も法線マップを参照して法線を操作することになります。バンプマッピングでは、この法線マップの参照を行なう上で[ 接空間 ]での計算を行なう必要がありました。しかし、キューブ環境マッピングでは[ 視線空間 ]での計算を行なう必要があります。この二つの空間上での変換をいかにして行うのかが、今回のキューブ環境バンプマッピングの肝になります。

シェーダの記述

今回のキューブ環境バンプマッピングは、既に今までのテキストで解説してきた概念を、それぞれに応用することで実現できる処理です。ですから概念的な解説は割愛して、シェーダのソースの解説からやっていきます。

通常のキューブ環境マッピングでは、あくまでも映り込みを表現するテクニックです。ですから頂点属性としてのテクスチャ座標は必要ありませんでした。法線と視線ベクトルから反射ベクトルを計算すればそれでよかったからです。

しかしバンプマッピングでは法線マップを参照するために、頂点属性としてテクスチャ座標を定義しておく必要があります。さらに、頂点法線をもとに接線ベクトルや従法線ベクトルの算出も行なわなければなりません。

今回のサンプルでは、頂点シェーダ内で接線ベクトルの算出まで行なっておきます。それを踏まえてソースを見てみましょう。

頂点シェーダ

attribute vec3 position;
attribute vec3 normal;
attribute vec4 color;
attribute vec2 textureCoord;
uniform   mat4 mMatrix;
uniform   mat4 mvpMatrix;
varying   vec3 vPosition;
varying   vec4 vColor;
varying   vec2 vTextureCoord;
varying   vec3 tNormal;
varying   vec3 tTangent;

void main(void){
	vPosition     = (mMatrix * vec4(position, 1.0)).xyz;
	vColor        = color;
	vTextureCoord = textureCoord;
	tNormal       = (mMatrix * vec4(normal, 0.0)).xyz;
	tTangent      = cross(tNormal, vec3(0.0, 1.0, 0.0));
	gl_Position   = mvpMatrix * vec4(position, 1.0);
}

頂点属性としては四つの要素が入ってきます。頂点位置、頂点法線、頂点色、頂点のテクスチャ座標の四つですね。uniform 変数としてはモデル座標変換行列と、モデル×ビュー×プロジェクション座標変換行列の二つがシェーダに送られてきます。

フラグメントシェーダに送る varying 変数が今回は結構多いですね。

頂点シェーダでは先述の通り接線ベクトルの算出までを行なった上で、フラグメントシェーダに送ります。接線ベクトルの算出には、バンプマッピングのサンプルで行なったのと同じように、法線と Y 軸との外積をとる方法を使っています。

それでは続いてフラグメントシェーダを見てみます。

フラグメントシェーダ

precision mediump float;

uniform vec3        eyePosition;
uniform sampler2D   normalMap;
uniform samplerCube cubeTexture;
uniform bool        reflection;
varying vec3        vPosition;
varying vec4        vColor;
varying vec2        vTextureCoord;
varying vec3        tNormal;
varying vec3        tTangent;

void main(void){
	vec3 tBinormal = cross(tNormal, tTangent);
	mat3 mView     = mat3(tTangent, tBinormal, tNormal);
	vec3 mNormal   = mView * (texture2D(normalMap, vTextureCoord) * 2.0 - 1.0).rgb;
	vec3 ref;
	if(reflection){
		ref        = reflect(vPosition - eyePosition, mNormal);
	}else{
		ref        = tNormal;
	}
	vec4 envColor  = textureCube(cubeTexture, ref);
	vec4 destColor = vColor * envColor;
	gl_FragColor   = destColor;
}

今回のフラグメントシェーダにおいては、キューブ環境マッピングを行なう上で必要となる視線ベクトルを算出するため、uniform 変数としてカメラの座標位置を受け取ります。また、法線マップから抜き出したバンプマッピング用の法線情報を視線空間上へと変換するために 3 x 3 の行列をシェーダ内で生成しています。

手順としては、まず varying 変数として入ってきた法線と接線ベクトルを使って従法線ベクトルを算出します。この三つのベクトルを用いて mat3 型の変数 mView を定義します。この mat3 型という変数の型は、文字通り 3 x 3 の行列です。この行列を法線マップから抜き出した法線情報に掛け合わせることで、接空間上にある法線情報を視線空間へと変換します。

あとは、キューブ環境マッピングで行なったのと同じ要領で反射ベクトルを算出し、キューブマップテクスチャを参照しフラグメントの色を決定します。

まとめ

メインプログラムとなる javascript プログラムのほうでは、シェーダへ送るべき情報を正しく処理するだけなのでそれほど難しいことはやっていません。むしろ、今までやってきたことをそのままやっているだけです。

今回のサンプルで一番わかりにくいのは、フラグメントシェーダ内で行なっている変換作業だと思いますが、シェーダ内での行列生成の部分さえ間違えなければ、あとはそれほど難しくないと思います。

キューブ環境バンプマッピングは、それほど使う場面が多いテクニックではないと思いますが、見た目は非常にきれいです。今回のサンプルでもクォータニオンによるカメラの制御を行なっていますので、いろんな方向からレンダリングされたモデルを眺めることができます。実際にサンプルを動作させて、確かめてみてください。

サンプルへのリンクはいつものように以下にあります。

entry

PR

press Z key