第八回 WebGLスクール 「シェーダエフェクトテクニック」



[wgld.org](http://wgld.org/)の管理人であるdoxasさん主催の[WebGLスクール](http://webgl.souhonzan.org/entry/?v=0003)の第八回目です。
2015年最初となる今回は、シェーダエフェクトテクニックの中の **トゥーンレンダリング** というレンダリング手法についてです。


### 前回までのまとめ
[第一回 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)


## トゥーンレンダリング
- 陰影の付きかたがくっきりと分かれている。
- 独特の雰囲気がある。
- 今までの基礎技術を応用して実現できる。


## トゥーン実現のステップ
トゥーンレンダリングを実現するためには、ステップが必要。

- アウトラインを出す
- 極端に階調が変化する陰影の表現


### アウトラインの描画
アウトラインの描画には色々な方法があるが、今回は **カリング** を使った方法。
**同じモデルを二回描画** することでアウトラインを描画できる。
どういうことかと言うと、裏側と表側を描画して、裏側のモデルを黒く塗り少し膨らますことでアウトラインを実現する。

裏側を初めに描画するためにカリング面を反転させる

```
gl.cullFace(gl.FRONT);
```

頂点シェーダの`gl_Position`を以下のように修正

```
gl_Position = mvpMatrix * vec4(position + normal * 0.05, 1.0);
```


## 段階的陰影付け
トゥーンレンダリングの特徴である段階的な陰影を表現するには、テクスチャを使用する。
今までは、法線とライトベクトルで内積を計算し、最終的な色の出力を行っていた。その計算方法のままディフェーズの値をテキスチャ座標に変更させる。

フラグメントシェーダの`shadeColor`を以下のようにする。

```
vec3 shadeColor = (texture2D(shadeTexture, vec2(diff, 1.0))).rgb;
```



## 深度フォグ
霧がかかったような、モデルが背景に溶け込むような実装を実現できる。水中や森の中のシーンに使われることが多い。
WebGLの基本機能には深度フォグは存在しないが、比較的簡単に実現できる。

### 深度フォグの有無の比較
左が深度フォグ無し、右が深度フォグ有り。

![](http://school.souhonzan.org/201411/document/009/image/005.jpg)

### 深度フォグの実装
上記のようなモデルを描画し、深度フォグを実装してみる。
モデルがたくさん並んでいるが、これは`render()`関数内でループ処理をかけているだけ。
色については、minMatrix.jsの`hsva`関数を使い、こちらもループの中で値を変更している。

```
var color = hsva(色相, 彩度, 明度, アルファ);
// 色相は 0 以上の整数、他は 0 ～ 1 の範囲で値を渡す
```

### 頂点シェーダの変更
頂点シェーダでは、これといった変更点はなく、varying変数で 頂点の **モデル座標変換適用後の位置** をフラグメントシェーダに渡しているだけ。

### フラグメントシェーダの変更点
- フラグメントシェーダに`const`という修飾子を追加する。
- `const`は修飾子を使って宣言されている定数。
- 中身が変化しないものに関しては、定数を使用した方がパフォーマンスが良い
- `main()`関数内に深度フォグの処理を加える

```
// m.perspectiveで指定した値 (奥行きをどこまで表示させるか)
const float far = 35.0;
const float fogCoef = 1.0 / far;
const vec3  fogColor = vec3(0.0, 0.7, 0.7);

void main(){
	vec3 inverseLight = normalize(invMatrix * vec4(lightDirection, 1.0)).xyz;
	vec3 eyeDirection = eyePosition - centerPoint;
	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);
	vec3  dest = vColor.rgb * diff * modelColor.rgb + spec;
	
	// vPosition 頂点の位置
	// eyePosition カメラの位置
	float fog = (eyeDirection.z - vPosition.z) * fogCoef;
	// 線形補間
	vec3  mixColor = mix(dest, fogColor, fog);

	gl_FragColor = vec4(mixColor, vColor.a * modelColor.a);
}
```

**mix関数**
この関数は線形補間を行う関数。色の線形補間や合成に使える。

```
// 係数が0に近いほど色Aが強く、1に近いほど色Bが強く出る。
mix(色A, 色B, 係数); // 係数は 0 ～ 1 の範囲で指定
```

### 深度フォグの実装のヒント
- 定数で何を計算しているか
- カメラと頂点の距離を求める
- `min()`関数の意味を考える


## 感想
今回はエフェクトテクニックとしてトゥーンレンダリングについて学びました。
新たに学ぶというよりも今までの応用といった内容でした。そのため前回までにやった内容をある程度理解していないと、今回の内容は理解できないと思う。

シェーダ側とJS側のどの部分でどういった処理が行われているのかというのを、ちゃんと理解しておきたい。

次回は、フレームバッファについてです。

### 続き
[第十回 WebGLスクール 「ポストエフェクトテクニック」](http://qiita.com/konweb/items/698892586b0df3759875)
[第十一回 WebGLスクール「キューブ環境マッピング」](http://qiita.com/konweb/items/262b107b2ea9a6113776)