GLSLで炎エフェクトを作ってみた

[WebGL Advent Calendar 2015](http://qiita.com/advent-calendar/2015/webgl)の13日目の記事です。

GLSLを教わる機会があったので、力試しに炎エフェクト作ってみました（[glslsandbox](http://glslsandbox.com/e#27806.0)のものを参考にしてます）
形状がいまいちですが、炎のカラーバージョンを2つ作ってみました！
![001.gif](https://qiita-image-store.s3.amazonaws.com/0/15028/00da0079-496f-1a52-793b-e14b2a036f56.gif)

![002.gif](https://qiita-image-store.s3.amazonaws.com/0/15028/d5bc9dcd-ca58-e3e6-091f-b615339cfd3e.gif)

コードはこちら　→　[GLSL Editor - 炎エフェクトDEMO](http://goo.gl/8JZ4OS)

# プログラムの解説
炎のエフェクトは、メタボールを横揺れ用に8個 x 縦揺れ用に8個を描画しています。
メタボール1つだけ表示するとこんな感じです。
<img width="400" alt="meta.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/30bc7a74-a1fc-8a00-2234-5aa76d4f0f5e.png">
始めのコードは以下のようなシンプルなものになります。

```js:メタボール1つの場合
// メタボール生成
float metaball(vec2 pos, vec2 offset, float scale){
	// ポジション修正
	pos = pos - offset;
	float len = length(pos);
	float draw = 0.0;
	// 円を描く範囲
	if(len < scale){
		draw = (1.0 - len / scale);
	}
	return draw;
}

// 各ピクセルの原点からの距離を計測し色として出力する
void main(){
	// gl_FragCoord.xy = 0〜512 → *2.0で0〜1024 → -resoで-512〜512 → /resoで-1.0〜1.0座標
	vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / resolution;
	float ball = 0.0;
	float x = 0.0;	// 後で使う
	float y = 0.0;	// 後で使う
	float moveX = x;
	float moveY = y;
	float scale = 0.56;
	// メタボール生成
	ball += metaball(p, vec2(moveX, moveY), scale);
	// blue color
	gl_FragColor = vec4(vec3(ball*0.25+ball*0.1, ball*0.45+ball*0.1, ball), 1.0);
}
```

上記コードを修正し、メタボール8個を横揺れで動かすとこんな感じです。
それぞれのメタボールはY軸を固定、X軸は-0.3〜0.3をループ移動しています。
![003.gif](https://qiita-image-store.s3.amazonaws.com/0/15028/ae25b1de-8d9b-e7e8-0158-af3aa66328c4.gif)

```js:メタボール8個で横揺れ炎の場合
void main(){
	// gl_FragCoord.xy = 0〜512 → *2.0で0〜1024 → -resoで-512〜512 → /resoで-1.0〜1.0座標
	vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / resolution;
	float ball = 0.0;
	float speed = 10.0;	// 炎のスピード
	float yPos = 0.8;	// Y軸の調整

	// 横揺れの炎8つ
	for(int i = 1; i <= 8; i++){
		float x = float(i) * 1.0;
		float y = float(8-i) * 0.18;
		float moveX = cos(x+time*speed) * 0.31;
		float moveY = y - yPos;
		float scale = 0.56;
		// メタボール生成
		ball += metaball(p, vec2(moveX, moveY), scale);
	}
	// blue color
	gl_FragColor = vec4(vec3(ball*0.25+ball*0.1, ball*0.45+ball*0.1, ball), 1.0);
}
```
縦揺れの炎も同様に8個のメタボールで動作させています。
![004.gif](https://qiita-image-store.s3.amazonaws.com/0/15028/a45223da-e9bd-f8f3-9d76-7dba5256f981.gif)

```js:メタボール8個で縦揺れ炎の場合
void main(){
	// gl_FragCoord.xy = 0〜512 → *2.0で0〜1024 → -resoで-512〜512 → /resoで-1.0〜1.0座標
	vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / resolution;
	float ball = 0.0;
	float speed = 10.0;	// 炎のスピード
	float yPos = 0.8;	// Y軸の調整

	// 縦揺れの炎8つ
	for(int i = 1; i <= 8; i++){
		float x = float(i) * 0.0;
		float y = float(8-i) * 0.22;
		float moveX = x;
		float moveY = y - yPos + sin(float(i)+time*speed) * 0.2;
		float scale = 0.4;
		// メタボール生成
		ball += metaball(p, vec2(moveX, moveY), scale);
	}
	// blue color
	gl_FragColor = vec4(vec3(ball*0.25+ball*0.1, ball*0.45+ball*0.1, ball), 1.0);
}
```

最後に縦揺れと横揺れのコードを結合させます。
ここではballという変数に両方の描画結果を加算するだけで、描画内容がブレンドされます。
こういう部分はシェーダーって素晴らしいですね♪

```js:縦揺れと横揺れ結合の場合
// 横揺れの炎8つ
for(int i = 1; i <= 8; i++){
	float x = float(i) * 1.0;
	float y = float(8-i) * 0.18;
	float moveX = cos(x+time*speed) * 0.31;
	float moveY = y - yPos;
	// moveX = 0.0;
	// moveY = 0.0;
	float scale = 0.56;
	// メタボール生成
	ball += metaball(p, vec2(moveX, moveY), scale);
}
// 縦揺れの炎8つ
for(int i = 1; i <= 8; i++){
	float x = float(i) * 0.0;
	float y = float(8-i) * 0.22;
	float moveX = x;
	float moveY = y - yPos + sin(float(i)+time*speed) * 0.2;
	// moveX = 0.0;
	// moveY = 0.0;
	float scale = 0.4;
	// メタボール生成
	ball += metaball(p, vec2(moveX, moveY), scale);
}

// blue color
gl_FragColor = vec4(vec3(ball*0.25+ball*0.1, ball*0.45+ball*0.1, ball), 1.0);
```
炎の動きの調整が少し難しいですが、以外と簡単にできたので興味ある方はぜひ試してみて下さい！

明日は、takahito-tejimaさんよろしくです。