第十一回 WebGLスクール「キューブ環境マッピング」


[wgld.org](http://wgld.org/)の管理人であるdoxasさん主催の[WebGLスクール](http://webgl.souhonzan.org/entry/?v=0003)の第十一回目です。
今回はポストエフェクトについてです。

### 前回までのまとめ
[第一回 WebGLスクール 「WebGLの概念」](http://qiita.com/konweb/items/a52ae0bc6aec2d2f3e6b)
[第二回 WebGLスクール 「WebGLの手続きと手順」](http://qiita.com/konweb/items/a1c58c1722c62a61f35d)
[第三回 WebGLスクール 「シェーダの基礎」](http://qiita.com/konweb/items/ec8fa8cd3bc33df14933)
[第四回 WebGLスクール 「行列とクォータニオンについて知る」](http://qiita.com/konweb/items/fd328be7f4d64e5ace02)
[第五回 WebGLスクール 「ライティングの基本」](http://qiita.com/konweb/items/21d76557f08ba24e6d3d)
[第六回 WebGLスクール 「テクスチャで画像データを使用する」](http://qiita.com/konweb/items/c848c5b96dc70aa46569)
[第七回 WebGLスクール 「ブレンドファクターとアルファブレンディング」](http://qiita.com/konweb/items/def443013c3b642f63fb)
[第八回 WebGLスクール 「シェーダエフェクトテクニック」](http://qiita.com/konweb/items/bd392cd492ff04a24236)
[第十回 WebGLスクール 「ポストエフェクトテクニック」](http://qiita.com/konweb/items/698892586b0df3759875)


## 環境マッピングとは
3Dの環境をモデルにマッピングさせるテクニックのこと。
周りの風景がモデルに映り込む完全な鏡面反射や、部分的な映り込み、ガラスのような屈折などを表現できる。


### スフィア環境マッピング
スフィア環境マッピングは元データが球体になっているもの。


### キューブ環境マッピング
キューブ環境マッピングは元データがキューブ状のデータを利用する。
キューブマッピングと呼ばれるサイコロを展開したような特殊なテクスチャが使われる。
**WebGL** においては、キューブ環境マッピングの方が扱いやすい。


### キューブテクスチャ定数
今までは、`gl.TEXTURE_2D`としていた箇所を`gl.TEXTURE_CUBE_MAP`とすると、WebGLが勝手に切り替えてくれる。

```
gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
```

シェーダ内の場合は、`sampler2D`ではなく`samplerCube`を利用する。

```
samplerCube cubeTexture;
```


## キューブ環境マッピングの実装

![webgl.png](https://qiita-image-store.s3.amazonaws.com/0/48677/6094951b-222b-8c93-2a6d-1ee54713110d.png "webgl.png")


### キューブ環境マッピングの考え方
モデルがレンダリングされる際に、キューブマップ用テクスチャを参照しならが色を合成する。
イメージとしては、世界全体が大きな箱になっていてその中にあるモデルにどのように箱の模様が映り込むのかを計算する感じ。


### キューブマップ用の元画像
キューブマップ用の画像は、立方体を展開したような形ではなく、正立方体全面分用意する必要がある(6枚)。
キューブマップ用の素材もある。
[Humus - Textures](http://www.humus.name/index.php?page=Textures)


### ターゲットを指定するための定数
どの面に対する処理なのかを指定する定数がある。
以下の定数でどの面で使用する画像なのかの割り当てを行う。

- gl.TEXTURE_CUBE_MAP_POSITIVE_X
- gl.TEXTURE_CUBE_MAP_POSITIVE_Y
- gl.TEXTURE_CUBE_MAP_POSITIVE_Z
- gl.TEXTURE_CUBE_MAP_NEGATIVE_X
- gl.TEXTURE_CUBE_MAP_NEGATIVE_Y
- gl.TEXTURE_CUBE_MAP_NEGATIVE_Z

![webgl01.png](https://qiita-image-store.s3.amazonaws.com/0/48677/79251581-6829-7b3c-ec8f-18c6b9e82a38.png "webgl01.png")


### キューブマップテクスチャの初期化
WebGLでは画像を使う場合、画像の読み込みが完了してから処理を実行する必要があった。
キューブマッピングの場合、6枚の画像を使用するため6枚すべての読み込みが完了してから処理を実行する処理を行う必要がある。


### シェーダ側の処理
シェーダ側では **reflect関数** を使用する。
reflect関数は、反射ベクトルを計算することができる。
第一引数に **元となるベクトル**、第二引数に **反射する面の法線ベクトル** を渡す。

```
vec3 reflectVector = reflect(eyeDirection, vNormal);
```

フラグメントシェーダで以下のようにして使用する。

```
void main(){
	vec3 eyeDirection = vPosition - eyePosition;
	vec3 reflectVector = reflect(eyeDirection, normalize(vNormal));
	vec4 envColor = textureCube(cubeTexture, reflectVector);
	gl_FragColor = envColor;
}
```


### 背景用ボックスモデルのレンダリングポイント
- ボックスモデルは大きくスケーリングする
- カリング面の設定に気をつける
- プロジェクション座標変換行列の far にも注意
- シェーダ内では処理を分岐する


### 映り込み表現の調整

![webgl02.png](https://qiita-image-store.s3.amazonaws.com/0/48677/e311ee42-d075-3a8a-e46c-78c6f0947b61.png "webgl02.png")


微妙な映り込みを表現したい場合は、モデルの頂点カラーの色とブレンドしてあげることで可能。
シェーダ側で処理を行う。

```
void main(){
	vec3 eyeDirection = vPosition - eyePosition;
	vec3 reflectVector = vec3(0.0);
	vec4 envColor = vec4(1.0);
	reflectVector = reflect(eyeDirection, vNormal);
	// mixを使って色をブレンド
	envColor = mix(textureCube(cubeTexture, reflectVector), vec4(1.0, 1.0, 10.0, 0.7), 0.4);
	gl_FragColor = envColor;
}
```

### シャボン玉シェーダ

![webgl03.png](https://qiita-image-store.s3.amazonaws.com/0/48677/ec2cea88-2d7c-2b47-aa12-78936fe813dc.png "webgl03.png")


シャボン玉のように半透明なモデルを表現することもできる。
半透明を実装するには、アルファブレンディングを使用するので、まずはブレンディングを有効化させる。

```
gl.enable(gl.BLEND);
```

次に、`gl.blendFuncSeparate()`を用いてブレンドを指定する。

```
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
```


## 屈折マッピング
屈折マッピングはキューブ環境マッピングを改造するテクニックのこと。
キューブ環境マッピングは **反射ベクトル** を算出したが、屈折マッピングの場合は **屈折ベクトル** を算出する。

### 屈折ベクトル
半透明の物体にはそれぞれ屈折率がある。そのため同じ角度から見ても景色が違って見える。
屈折ベクトルを算出するためには、屈折率を考慮してベクトルを変換しる必要がある。

### 屈折率の計算
WebGLには屈折率をを考えて算出してくれる **refract関数** が存在する。
`reflect`関数と似ているが別物の関数で、`refract`関数の第三引数には屈折率を渡し屈折ベクトルを算出できる。

シェーダ側を編集する。

```
void main(){
	vec3 eyeDirection = vPosition - eyePosition;
	vec3 reflectVector = vec3(0.0);
	if(reflection){
		// 屈折ベクトルの算出
		reflectVector = refract(eyeDirection, vNormal, 0.05);
	}else{
		reflectVector = normalize(vNormal);
	}
	vec4 envColor = textureCube(cubeTexture, reflectVector);
	gl_FragColor = envColor;
}
```

**屈折率0.05の場合**

![webgl04.png](https://qiita-image-store.s3.amazonaws.com/0/48677/e6d84556-3e9c-351a-6180-58b534f9b61b.png "webgl04.png")

**屈折率0.5の場合**

![webgl05.png](https://qiita-image-store.s3.amazonaws.com/0/48677/bdb035b5-6b8f-fcec-e407-77ba10abfba5.png "webgl05.png")


## 動的キューブ環境マップ
モデルの影響が環境マッピングで貼った景色に反映されるためには、キューブマップテクスチャを動的に生成しなくてはならない。

リアルタイムキューブマッピングの実装としては、風景すべてをフレームバッファにオフスクリーンレンダリングをして、それをキューブマップテクスチャとして使う。


### フレームバッファキューブテクスチャ
オフスクリーンレンダリングする際に、キューブテクスチャを割り当てることもできる。

オフスクリーンのフレームバッファをキューブマップテクスチャ割り当てで生成すると、六面全てに対して動的にレンダリングを行いオフスクリーンでキューブマップテクスチャを用意してやることも可能。

レンダリング回数は最低7パスで、初期化からレンダリングまでがとても長く難解。


### キューブマップ用フレームバッファ
`texImage2D`を使い6面すべてをブランクな状態にする。

```
for(var i = 0; i < target.length; i++){
	gl.texImage2D(target[i], 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
}
```


6面に対しての描画部分は以下のように処理を行う。

```
switch(cubeTarget[i]){
	case gl.TEXTURE_CUBE_MAP_POSITIVE_X:
		eyeDirections[i] = [ 1.0,  0.0,  0.0];
		cameraUppers[i]  = [ 0.0, -1.0,  0.0];
		break;
	case gl.TEXTURE_CUBE_MAP_POSITIVE_Y:
		eyeDirections[i] = [ 0.0,  1.0,  0.0];
		cameraUppers[i]  = [ 0.0,  0.0,  1.0];
		break;
	case gl.TEXTURE_CUBE_MAP_POSITIVE_Z:
		eyeDirections[i] = [ 0.0,  0.0,  1.0];
		cameraUppers[i]  = [ 0.0, -1.0,  0.0];
		break;
	case gl.TEXTURE_CUBE_MAP_NEGATIVE_X:
		eyeDirections[i] = [-1.0,  0.0,  0.0];
		cameraUppers[i]  = [ 0.0, -1.0,  0.0];
		break;
	case gl.TEXTURE_CUBE_MAP_NEGATIVE_Y:
		eyeDirections[i] = [ 0.0, -1.0,  0.0];
		cameraUppers[i]  = [ 0.0,  0.0, -1.0];
		break;
	case gl.TEXTURE_CUBE_MAP_NEGATIVE_Z:
		eyeDirections[i] = [ 0.0,  0.0, -1.0];
		cameraUppers[i]  = [ 0.0, -1.0,  0.0];
		break;
}
```

### メリット
動的キューブ環境マッピングのメリットは、モデルに別のモデルの周囲にある影響を与えることができる。
通常のマッピングではできないリアルタイムなマッピングが可能となる。

### デメリット
とにかく処理が **重くなってしまう。**
6面分の処理を記述しなくてはならないので、コードが冗長になってしまう。


## 感想
環境マッピングを行うことで、とてもリアルで楽しい描画が可能になる。が、やっぱりWebGLは本当にループ処理が苦手でコードの冗長さが気になるなーというところ。three.jsなどのライブラリを使うと楽なのかもしれないけど、そこは慣れるしかない。

WebGLスクールもいよいよ次回で最後です。次回はとてつもない大物の「シャドウマッピング」をやるそうです。鬼門中の鬼門らしいので大変そうです。