魔法使いになりたい人のためのシェーダーライブコーディング入門

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/d39782dc-e834-a0d9-2791-44c10bbfa196.png)

**とりあえずこの映像を見てください。**

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">昨日の勉強会の懇親会中に20分間のライブコーディングでシェーダーを作りました！<br>初めて人前でコーディングをしたんですが、めちゃくちゃ楽しかったです！！<br><br>（当日動かなかったpmod修正済です...）<br>差分<br>- q.x = abs(p.x ) - 10.;<br>+ q.x = abs(q.x ) - 10.;<a href="https://t.co/LH3TT4YzSU">https://t.co/LH3TT4YzSU</a><a href="https://twitter.com/hashtag/klab_meetup?src=hash&amp;ref_src=twsrc%5Etfw">#klab_meetup</a> <a href="https://t.co/k61c3O2ZA1">pic.twitter.com/k61c3O2ZA1</a></p>&mdash; かねた (@kanetaaaaa) <a href="https://twitter.com/kanetaaaaa/status/1141480732180619264?ref_src=twsrc%5Etfw">2019年6月19日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

20分間のライブコーディング映像フル
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">先日の <a href="https://twitter.com/hashtag/klab_meetup?src=hash&amp;ref_src=twsrc%5Etfw">#klab_meetup</a> の懇親会で行った20分のライブコーディング映像を公開しました！<br>実況解説は<a href="https://twitter.com/gam0022?ref_src=twsrc%5Etfw">@gam0022</a> さんと<a href="https://twitter.com/songofsaya_?ref_src=twsrc%5Etfw">@songofsaya_</a>さんです<br>突発ながら面白い実況で場を盛り上げてくださって非常に楽しかったです！<br>動画でもこの空間の楽しさが伝わると思うので是非ご覧ください！<a href="https://t.co/1CDeXMfJlT">https://t.co/1CDeXMfJlT</a></p>&mdash; かねた (@kanetaaaaa) <a href="https://twitter.com/kanetaaaaa/status/1141987474824036353?ref_src=twsrc%5Etfw">2019年6月21日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


この映像はGLSLという言語を利用して20分間のライブコーディングで***無***から作成されたものです。

20分という短い時間で無からこのような映像を作成する**魔法の習得方法**をこの記事ではお伝えします。

# はじめに
6/19に[KLab TechMeetup #4](https://techplay.jp/event/733454)でシェーダーライブコーディングのすすめという題で登壇をしてきました。

この記事は**その発表を記事向けにブラッシュアップしたもの**と、その日の懇親会で実際に行った**20分ライブコーディングのテクニック解説**記事になります。

当日利用した資料はこちらです
https://docs.google.com/presentation/d/1n_L8VJZs-Ig4FrlaUe5X2dhFB77ZPbiDBirjkoM4Afc/edit#slide=id.g1276827f2e_0_5

# この記事を見てほしい人
この記事を見てほしい人は以下のような人たちです！

- **シェーダー好きな人！**
- **シェーダーだけで絵を出すのは難しそう...と思っている人！**
- **ゲーム制作中にうっかりシェーダーばかり書いてしまって進捗が出ない人！**

こんな人たちに向けて、シェーダーライブコーディングという一生遊べる**遊び**をご紹介します！

ちなみにこれは自己紹介なので、この記事は自分に刺さる記事となっております！！

**本記事はある程度シェーダーを触ったことがあるという人向け**です。

まだシェーダーを書いたことがない... でもシェーダーライブコーディングがしたい！！という方は

こちらの連載を一通り読んでから本記事を読み進めていただくと理解できるようになるはずです。
https://qiita.com/doxas/items/b8221e92a2bfdc6fc211

# シェーダーライブコーディングとは？
短時間のシェーダーライブコーディングで映像を作るということをします。

VJのパフォーマンスや**競技**として行われており、いわゆる[シェーダー芸](https://qiita.com/kaneta1992/items/7fe9b47cc6c0836222af)です。

## 競技？
この記事でおすすめするシェーダーライブコーディングは競技として行われているものです。

シェーダーライブコーディングは国内外で大会が行われており、レギュレーションによって差異はありますが、トーナメント形式等によって**観客を湧かせるシェーダーを競います**。

競技の大きな特徴であり人を惹きつける部分は、**制限時間がある**という点と、**必要な道具はすべて競技中に実装する**という点だと僕は思っています。

できるだけ速く、できるだけ幅広い表現を突き詰めていくというのがシェーダーライブコーディングの醍醐味で、私たちはこれを目指して日々精進します。

## そもそもシェーダー芸とは？
> シェーダー（英: shader）とは、3次元コンピュータグラフィックスにおいて、シェーディング（陰影処理）を行うコンピュータプログラムのこと。「shade」とは「次第に変化させる」「陰影・グラデーションを付ける」という意味で、「shader」は頂点色やピクセル色などを次々に変化させるもの（より具体的に、狭義の意味で言えば関数）を意味する。（引用元：wikipedia

wikipediaからシェーダーの定義を引用するとこのようなものです。

本来シェーダーとはこのような処理を記述するのに利用されます。（一例です）

- **世界にオブジェクトを配置するための座標変換（頂点シェーダー）**
- **形状に色を付けるためのシェーディング（フラグメントシェーダー）**
- **描画結果に味付けするための画像処理（ポストエフェクト）**

かなり省略していますが、通常は上記のようなシーンで使用されるものです。

この記事を見ている大半の方もこのような認識だと思いますが、**シェーダー芸はこのような通常のシーン以外の使われ方をしているもの**を指します。

## 競技におけるシェーダー芸とは？
シェーダー芸にもいろいろタイプがありますが、競技のシェーダーライブコーディングで行われているのは、**短時間のフラグメントシェーダーコーディングで映像を作る**ということです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/03203516-7d33-ce41-820c-fd891e513cf2.png)

正直これだけ聞いてもさっぱりわからないと思いますが、詳しい内容については後程「**入門レイマーチング**」の項で実際にどういったことをやっているのか説明します。

# シェーダーライブコーディング4つの面白ポイント
## 面白ポイントその１「シェーダーのみで完結する」
私のようにゲームを作るよりもシェーダーを優先してしまう人間にとって、**シェーダーの実装のみに注力してルックを作成できる**点が最高です。

もちろんコーディングのみということはマウスがなくても遊べるので、私のような**GUIアレルギーの方でも安心して遊ぶことができます**！

## 面白ポイントその２「まるで魔法に見える！」
テクニックを覚えていくことでどんどん表現の幅が広がります。また**覚えたスキルがすぐに見た目で反映される**ので実際に**RPGで魔法を覚えているように自分の成長を実感できます**。

正直今でも、**その場でどんどんルックが完成されていく様は魔法のように見えます**。

## 面白ポイントその３「隙間時間に遊べる！」
レギュレーションによって異なりますが、**競技時間は大体30分前後**です。

私の場合だと60分を目安に遊んでいます。

移動時間などの隙間時間で暇を潰せてよい感じです！

## 面白ポイントその４「宴会芸に使えるかも！？」
エンジニアの飲み会で急な宴会芸の振りに使えるかもしれません。

**私もイベント当日の懇親会で、酒の勢いに任せて実際に20分間のライブコーディングを行いました**！！

# 入門レイマーチング
そんな楽しいライブコーディングですが、**3D表現のためにレイマーチングという技術が現在主流**となっています。

ここからは、皆さんにシェーダーライブコーディングを入門してもらうため、レイマーチングで使用される**スフィアトレーシング**と**距離関数**について解説します。

## そもそもフラグメントシェーダーのみで映像を作るとは？
**スクリーンを覆う板ポリに対してフラグメントシェーダーを適用**してピクセル毎にシェーディング以外にもよしなな計算を行うということをします。

板ポリにシェーダーを適用するというと、大半の方はシェーダーの基本的な使い方の一つである**ポストエフェクト**を思い浮かべると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/4bd1b780-32d5-9e58-382f-27f275b55a09.png)

実際**ポストエフェクト**と**フラグメントシェーダー芸**の処理の流れは似ています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/b027315d-ebc9-630d-ed92-c1c2d34bf803.png)

ポストエフェクトは**事前に描画した結果をテクスチャから参照**するのに対して、シェーダー芸では**その場で描画結果と同等のものを作成**します。

そのためには**形状の定義**と**トレース**を行う必要があります。

## 形状をトレースする
形状を定義する前にこちらの方が理解しやすいため、まずはトレースする方法を解説します。

シェーダー内で定義したシーンをトレースするには**レイマーチング**というテクニックを利用します。

単純なスクリーンスペースのエフェクトや、ParallaxOcculusionMappingを実装したことがある人は、オーソドックスな**固定長のレイマーチング**を思い浮かべるかもしれません。

**シェーダー芸ではスフィアトレーシング**という手法でレイを進めていきます。
![raymarch_anim3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/189d8aad-d6ce-6f81-e987-c83bd7d9ac35.gif)

### スフィアトレーシングを実装する
では実際に実装しながら理解していきましょう。

この記事では実装の際にブラウザでGLSLが動作する**GLSL Sandbox**というサイトを利用します。

こちらのリンク先から新規シェーダーを作成してみてください。
http://glslsandbox.com/e

#### 注意点
サイトを開くと初期コードはこのようなモノになっています。

```glsl
#ifdef GL_ES
precision mediump float;
#endif

#extension GL_OES_standard_derivatives : enable

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;

void main( void ) {

	vec2 position = ( gl_FragCoord.xy / resolution.xy ) + mouse / 4.0;

	float color = 0.0;
	color += sin( position.x * cos( time / 15.0 ) * 80.0 ) + cos( position.y * cos( time / 15.0 ) * 10.0 );
	color += sin( position.y * sin( time / 10.0 ) * 40.0 ) + cos( position.x * sin( time / 25.0 ) * 40.0 );
	color += sin( position.x * sin( time / 5.0 ) * 10.0 ) + sin( position.y * sin( time / 35.0 ) * 80.0 );
	color *= sin( time / 10.0 ) * 0.5;

	gl_FragColor = vec4( vec3( color, color * 0.5, sin( color + time / 3.0 ) * 0.75 ), 1.0 );

}
```

GLSL Sandboxでは以下の部分はエディタの実装上必要なおまじないコードです。

```glsl
#ifdef GL_ES
precision mediump float;
#endif

#extension GL_OES_standard_derivatives : enable

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
```

レイマーチングの仕組みを理解するうえでこれらは必要ないので、そのまま放置しておいてください。

ここからの解説では**main関数にレイマーチングのアルゴリズムをゴリゴリ書いていく**ことになります。

#### まずはUVを表示する
**gl_FragCoord（スクリーン座標）**を**resolution（画面解像度）**で割って**0 ~ 1のUV座標**にします。

まずはこれをスクリーンにそのまま表示してみましょう。

**シェーダーライブコーディングはこの状態から始まります**。

```glsl
void main( void ) {
	vec2 uv = gl_FragCoord.xy / resolution.xy;
	gl_FragColor = vec4(uv, 0.0, 1.0);
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/8868cc2b-ec1d-97fe-57f0-7d7df1e7de57.png)

#### スクリーン空間を変更する
スフィアトレーシングでは画面に対してレイを飛ばしますが、**このままではレイを定義するときに不便**なので、**-1 ~ 1の空間に変更**します。

```glsl
void main( void ) {
	vec2 p = ( gl_FragCoord.xy * 2. - resolution.xy ) / min(resolution.x, resolution.y);
	gl_FragColor = vec4(p, 0.0, 1.0);
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/2f4632a8-da51-0aa1-8102-da6311fc563e.png)

#### カメラを定義
続いて、**シーンを写すカメラの位置(cameraPos)を定義**します。画像でいうところの緑の点です。

そして、先ほど作成した**-1~1のスクリーンを仮想的に空間に配置**して、**カメラ位置から各ピクセルに向けたレイ(rayDirection)を定義**します。画像でいうところの赤い線です。

これで描画に必要な視錐台を作ることができました。

```glsl
void main( void ) {
	vec2 p = ( gl_FragCoord.xy * 2. - resolution.xy ) / min(resolution.x, resolution.y);

	vec3 cameraPos = vec3(0., 0., -5.);
	float screenZ = 2.5;
	vec3 rayDirection = normalize(vec3(p, screenZ));

	gl_FragColor = vec4(p, 0.0, 1.0);
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/66e2af24-80e2-faeb-c01d-0e3127727b60.png)

#### 視野角について
レイの計算時に**screenZ**という変数がありましたが、これは**スクリーン平面をどれだけカメラ位置から遠ざけるかの変数**です。(スクリーンをレンズに見立てれば焦点距離(Focal Length)ということもできます。)

この値を**大きくすると平面が離れて視野角が狭く**、**小さくすると近づいて視野角が広く**なります。

レイマーチングではこの変数でFOVを調整するのが主流です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/b6b17447-3cdb-6b56-a011-9a7453713a8b.png)

#### レイ（光線）をマーチ（行進）する
視錐台を作れたら、肝であるスフィアトレーシングを実際に行っていきます。

**カメラ位置（cameraPos）**から空間に配置したスクリーンの**ピクセル方向（rayDirection）**に**形状に衝突するまでどんどんレイを進めていきます**。

進める距離には**Distance Function**と呼ばれる、**オブジェクト（今回は単純な球）への最短距離を出力とした関数**を利用します。レイマーチングではこの関数を使って形状を定義します。

**この最短距離を半径とした球の範囲内にはオブジェクトが存在しないことが保証される**ので、その距離だけ**レイを安全に進めることができます**。

このようにレイを行進する度に球がたくさん出てくるのでスフィアトレーシングと呼ばれています。

```glsl
float distanceFunction(vec3 pos) {
	float d = length(pos) - 0.5;
	return d;
}

void main( void ) {
	vec2 p = ( gl_FragCoord.xy * 2. - resolution.xy ) / min(resolution.x, resolution.y);
	
	vec3 cameraPos = vec3(0., 0., -5.);
	float screenZ = 2.5;
	vec3 rayDirection = normalize(vec3(p, screenZ));
	
	float depth = 0.0;
	for (int i = 0; i < 99; i++) {
	    vec3 rayPos = cameraPos + rayDirection * depth;
	    float dist = distanceFunction(rayPos);
	    depth += dist;
	}

	gl_FragColor = vec4(p, 0.0, 1.0);
}
```
![raymarch_anim3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/189d8aad-d6ce-6f81-e987-c83bd7d9ac35.gif)

#### 衝突したら色を決定する
**距離関数で出力される距離が限りなく0に近づいたとき、レイがオブジェクトに衝突していると判定できる**ので、色を付けてマーチングループを早期終了します。

このタイミングでライティングなどのシェード処理を記述できますが、今回は単純に単色を設定します。

```glsl
float distanceFunction(vec3 pos) {
	float d = length(pos) - 0.5;
	return d;
}

void main( void ) {
	vec2 p = ( gl_FragCoord.xy * 2. - resolution.xy ) / min(resolution.x, resolution.y);
	
	vec3 cameraPos = vec3(0., 0., -5.);
	float screenZ = 2.5;
	vec3 rayDirection = normalize(vec3(p, screenZ));
	
	float depth = 0.0;
	
	vec3 col = vec3(0.0);
	
	for (int i = 0; i < 99; i++) {
		vec3 rayPos = cameraPos + rayDirection * depth;
		float dist = distanceFunction(rayPos);
		
		if (dist < 0.0001) {
			col = vec3(1.);
			break;
		}
		
		depth += dist;
	}
	
	gl_FragColor = vec4(col, 1.0);
}
```

#### ここまでを全てのピクセルで実行する
今まで図では例のため、とあるピクセルに注目してスフィアトレーシングを行いましたが、**これをすべてのピクセルに対して行います**。

しかしこれについては深く考える必要はありません、**既に画面を覆う板ポリのフラグメントシェーダーを使っているので自動的にこれが行われます**。
![raymarch_anim2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/e0c46ce8-11d7-94f9-017f-acbbcc8a0a25.gif)

スフィアトレーシングの仕組みはこれだけです、とても単純な仕組みであることが分かっていただけたと思います。

**見た目は地味ですが、これはレイマーチングで描画されています！最初の一歩を踏み出せました！！**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/2e489fb4-5e97-7230-147e-61e37967098e.png)

## 形状を定義する
続いて形状の解説です、先ほどのスフィアトレーシングの解説で**Distance Function（距離関数）**というものが出てきました。

スフィアトレーシングではシーンを構成するオブジェクトはこの関数を使って定義します。

たいそうな名前がついていますが、以下二つのシンプルな特性を満たしていればスフィアトレーシングで可視化することができます。

- **入力座標pからオブジェクトへの最短距離を出力する**
- **外部なら正の距離、内部なら負の距離を出力する**

こちらの縞々模様の画像ですが、これは**入力座標pをスクリーンのピクセル座標とした時の距離の等高線**のようなものです。青が内部、オレンジが外部となっていて、本記事では距離関数の解説にはこの等高線を使います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/0ef6dca1-961a-4a46-8bc8-90c473e9a078.png)

### 簡単な距離関数を紹介
#### 球
```glsl
float sdSphere(vec3 p, float r) {
	float d = length(p) - r;
	return d;
}
```

一つ目の球はとてもシンプルです。

原点からの**距離(length(p))**から**半径(r)**を引くだけで球までの最短距離となります。

図を見てもらえば一目瞭然だと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/d32b48c1-0c86-bcc6-6874-e77d22a94a34.png)

#### 平面
```glsl
float sdPlane(vec3 p) {
	float d = p.y;
	return d;
}
```

続いて**本記事で一押しする平面の距離関数**です。

そして**最もシンプルに定義できる距離関数**でもあります。

三次元の入力座標の高さyをそのまま距離とすれば、xz平面の最短距離となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/4563f0f6-cb4e-bc9a-2140-77fc24e81085.png)

ライブコーディングをしばらく練習していると、**平面の汎用性の高さ**にきっと気づくと思います。

少なくとも**歴一か月の私はライブコーディング中にこの平面をめちゃくちゃ使います**。どういうことかは次の話で分かると思います。

### 箱の距離関数を作ってみよう！
雑に二つ紹介しましたが、**ライブコーディングでは自分で距離関数を構築するという場面が結構出てきます**。

また初心者にとってレイマーチングでは**距離関数のハードルがとても高い**のではないかと思っています。実際に私も距離関数難しそう..となっていました。

その助けとなるために、ここからは私が**ライブコーディングでよく使う箱の距離関数を一緒に作ってみます**。

#### 実は距離関数は最短距離じゃなくてもいい！？
最短距離というと難しい距離関数ですが、**Distance Estimator（距離推定器）**とも呼ばれていて、実は**必ずしも最短距離である必要はありません**。

これと先ほど紹介した平面の距離関数を頭に入れておけば**簡単に箱を作ることができます**。では実際にやってみます。

#### ベースとなる平面を用意
例によって**原点がスクリーンの中心にある-1～1の空間**で話を進めます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/46ec9b4e-dab9-9495-c987-781983d755d5.png)

まず**箱のベースとなる平面を二つ用意**します。

一つ目は**y座標を利用した平面**。

```glsl
float sdBox(vec3 p, float s) {
	return p.y;
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/815538d7-62ca-8dc2-9c71-74792826bdd9.png)

二つ目は**x座標を利用した平面です**。

```glsl
float sdBox(vec3 p, float s) {
	return p.x;
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/3042b060-4071-b58b-4f60-2e4f9ffee59b.png)

#### max関数による論理積演算
距離関数は、**min, max関数を利用して簡単にブーリアン演算を行うことができます**。

今回は**maxを利用して先ほどの二つの平面の積集合**を取ります。もっと簡単に言うと**二つの平面を比較して両方とも平面の内部である場所を残す**という操作です。

（**この例では、実際は3次元の箱の距離関数を構築するため、3つ目の軸であるp.zの平面も含まれています**）

```glsl
float sdBox(vec3 p, float s) {
	return max(max(p.x, p.y), p.z);
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/396a7e83-822f-9507-ee76-a9b09b140562.png)

距離関数のブーリアン演算については @gam0022 さんの [資料](https://www.slideshare.net/shohosoda9/threejs-58238484) が詳しいのでこちらをご参照ください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/bf1306d2-1544-0d44-c2bc-76dc407810bc.png)

#### 正の領域を複製するというアイデア
今のままでは崖のような形状でとても箱とは言えないので、おもむろに**座標が正の方向にｓだけ平行移動**してみます。

```glsl
float sdBox(vec3 p, float s) {
	p = p - s;
	return max(max(p.x, p.y), p.z);
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/e750cba6-19a1-b85c-5f53-b025f79529de.png)

すると、この**右上の領域を、その他の領域に鏡のように対称に配置すれば箱になるのではないか**というのが見えてきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/5269e256-3b64-4c4e-1134-709de6a739a3.png)

これは**Fold（フォールド）**というテクニックで実現できます。

#### 座標のFold（折り畳み）による鏡像
Foldをするには**abs**という**絶対値を返す関数**を座標に対して使っていきます。

理解しやすいように、**今回はx座標のみをFold**してみます。

```glsl
vec2 foldX(vec3 p) {
	p.x = abs(p.x);
	return p;
}
```

こちらの例では**中央の0.0より左側のマイナスの領域には何も表示されていません**。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/3ccd73e1-bf95-048d-f7e3-900bb8aaabc8.png)

absによるFoldを行うことで、以下のように**0.0を中心に座標の関係が対象になり鏡像が現れます**。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/4220e24f-a2a0-7f16-df65-016d9c031899.png)

これは**入力点に対して形状の内外判定を評価する**という距離関数の特性だからこそできるテクニックです！

既存のいくつかの距離関数は、**このテクニックを利用して処理コストを削減**しています。

#### 距離関数にFoldを適用して箱が完成！
今紹介したテクニックを先ほどの作りかけの距離関数に適用して見ます

これはまだマイナスの領域が残っている状態です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/6c976613-47c8-bb43-6bb1-b7cb73b39f2d.png)

absを利用したフォールドを行うことで**マイナスだった領域に右上の部分が折りたたまれて鏡像が現れました**。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/443b1421-e456-942f-91a5-9f21db381f97.png)

**これだけで箱の距離関数が完成してしまいます！**

```glsl
float sdBox(vec3 p, float s) {
	p = abs(p) - s;
	return max(max(p.x, p.y), p.z);
}
```

**absによって全ての座標が軸に対して対象**になり、鏡像が表れているのが図を見たら分かるはずです。

#### 正確な距離関数と比較
今回作った距離関数は赤く囲ったあたりが正確ではありませんが、このように**必ずしも最短距離でない関数でもスフィアトレーシングでは可視化することができます**。

私の場合ライブコーディング中はできるだけ頭を使いたくないというのと、正確なものに比べてタイプ数が少なくなることが多いので、今回のような**単純な平面の組み合わせで距離関数を構築する**ということをよくやります。

このように**平面を覚えておくだけで結構いろんなパターンの形状**を構築できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/61154566-f5d9-421f-0d13-d282696ba241.png)

### 次は？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/3718a186-f3a9-c0c4-2db9-23a9999fd575.png)

ここまでの内容で皆さんは画像のようなシーンを距離関数で定義して、スフィアトレーシングで可視化できるようになりました。

しかしシェーダーライブコーディングは**事前準備やコピペができません**、**ここまでの内容をググらずコピペせずに書けるようになりましょう！**

そして**スピードが命**です、何も見ずに書けるようになったら**3分で書けるようになってください！！！**

そこまでできれば皆さんはシェーダーライブコーディングのほぼプロです。あとは**細かいテクニックを覚えていくだけでできることがどんどん増えていきます**。

好みの題材を見つけて今できる範囲で実際にコーディングしてみてもよいですし、次のテクニックが知りたいとなったら、**シェーダーウィザードの方々の力を借りましょう**。大体この辺りにいます。

- [Shadertoy](https://www.shadertoy.com/)
- [GLSLSandbox](http://glslsandbox.com/)
- [glslfan](https://glslfan.com/)

また以前書いたこちらのリンク集も、シェーダーライブコーディングで使えるテクニックが多数あります。

是非こちらも参考にしてみてください

https://qiita.com/kaneta1992/items/7fe9b47cc6c0836222af

# ここまでのまとめ
ここまででシェーダーライブコーディングの楽しさと遊び方をお伝えしました。

一人にでも多く「**意外といけそう！とかこれは一生遊べるのでは？**」と思っていただけたならうれしいです。

何度も言いますが、**平面を覚えるだけで距離関数攻略だけでなくレイマーチングでの表現も広がってくるというのを覚えて頂けるとこれから入門するにあたって助けになると思います**。

というわけで**魔法使いになりたい人**は、今すぐに**シェーダーライブコーディング**を始めてみてください！

# 当日行ったライブコーディングの解説
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/d39782dc-e834-a0d9-2791-44c10bbfa196.png)
ここからは、**イベント当日の懇親会で実際に私が20分でライブコーディングを行って完成したシェーダーの解説**をしてみます。

今回作ったものは、この**記事内で説明したスフィアトレーシングと距離関数をそっくりそのまま**使って、**プラスアルファでいくつかのテクニックを利用**して作りました。

この記事の内容だけでもこんなものが作れるんだという目安にして頂きたいです。

## map関数について
私はレイマーチングでシーンを定義するとき、全体のオブジェクトをmapという関数にまとめて記述します。

今回のmap関数を理解するにあたって**距離関数にまつわる3つのテクニック**を理解しておく必要があります。
### Modによる複製
このテクニックは `p.z = mod(p.z, 10.0)` のように余剰を利用して距離関数のオブジェクトを複製するテクニックです。

これはとても有名なテクニックで日本でもたくさん情報があるので説明をそちらにお任せしようと思います。

こちらのページがとても詳しく解説してくださっています。
https://wgld.org/d/glsl/g012.html

### Polar Modによる複製
当日は不慮の事故でお見せすることができなかったんですが、**極座標で空間を分割してオブジェクトを複製**するというテクニックです。

Shadertoyでは至る所でこのテクニックが使われていますが、日本の解説を見たことがなかったので頑張って解説してみます。

関連するコードはこちらです。

```glsl
mat2 rot(float a) {
	float c = cos(a), s = sin(a);
	return mat2(c,s,-s,c);
}

const float pi = acos(-1.0);
const float pi2 = pi*2.;

vec2 pmod(vec2 p, float r) {
	float a =  atan(p.x, p.y) + pi/r;
	float n = pi2 / r;
	a = floor(a/n)*n;
	return p*rot(-a);
}
```

rotというのは二次元の回転行列を作る関数です、piは円周率を覚えていないのでacos(-1.0)で横着しています。

本体はpmodという関数で、**第一引数に座標**、**第二引数に空間を分割する数**を取ります。

まず一行目を見てみましょう

```glsl
float a =  atan(p.x, p.y) + pi/r;
```

ここでは極座標系で分割するために、**atan関数を用いて入力座標の角度**を計算しています。

この時利便性のために**分割角度の半分であるpi/rというオフセット**を加えます。

どういうことか説明するために、加えなかった時と加えた時の分割後の状態を比較してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/794068d5-42d6-1ebc-f62f-d4949c406c54.png)

上の図の真っ黒なところが複製の基準となる、**空間IDが0**の領域です。この領域に存在するオブジェクトがその他の領域にも現れます。

見比べてもらうと一目瞭然だと思いますが、オフセットを加えていない場合、**空間の切れ目が画面中央**に存在していますよね？

するとどういうことが起きるかというと、試しに円を八分割で複製して見比べてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/b9859a0b-e3f9-a2ea-1827-a1f1a39b56d5.png)

このように単純なy座標の移動のみだと、空間の切れ目にオブジェクトが移動してしまうので、見切れた形で複製されてしまいます。なので**単純なy座標の移動のみを考えるだけで複製できるようにオフセットを追加する必要**があります。

続いてこちらの部分

```glsl
float n = pi2 / r;
a = floor(a/n)*n;
```

floorという関数が登場しましたが、これは床関数といって、**小数点以下を切り捨てて整数部のみを残す**という関数です。

pi2/rの部分は**分割空間当たりの角度**を求めています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/67104951-7c69-9eef-3a5c-bad480283c0f.png)

先ほどatanで求めた、**入力座標の角度を分割空間当たりの角度で割ったもの**を、さらに**floorで小数点を切り捨てる**と、**空間毎のID**を求めることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/732921f1-89e9-c6ba-0867-4716c01433b9.png)

この**ID**に**空間当たりの角度**を掛けてやると**ID0の空間と入力座標が属している空間の差分の角度**がわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/121c797d-a882-59ad-4ae5-3f81b0109b33.png)

最後に、座標に対して、**適した角度の回転**をすることで、**IDが0のエリア内のオブジェクトがその他のエリアにも現れて複製が完了**します。

```glsl
return p*rot(-a);
```

### IFSによる複雑な形状
**Iterated function systemを略してIFS**といいます。

ループのイテレーションごとに、**Foldや平行移動、回転**などを座標に施して**複雑なフラクタル形状を簡単に作成するテクニック**です。

以下に**箱に対してイテレーション毎に1回のFoldと2回の回転を行う簡単なIFS**を適用した時の例を置いておきます。

```glsl
for(int i=0; i<ITER; i++) {
    p = abs(p) - 1.; // Fold
    p.xz *= rot(1.); // 回転
    p.xy *= rot(1.); // 回転
}
d = min(d, box(p));
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/1d217986-f8ba-0afb-58b7-0250a010aa5e.png)

## X線のような見た目について
### Phantom Mode
もしかしたら**X線のような見た目**になっていることに気づいた人がいるかもしれません。

この見た目は**Phantom Mode**と言うこれまた単純なテクニックで実現されています。

おそらくPhantom Modeの初出は[aiekick](https://twitter.com/aiekick)先生のこちらのシェーダーだと思います。
https://www.shadertoy.com/view/MtScWW
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/26f7b221-5511-94b5-f0b0-41e13cb49705.png)


簡単にやっていることを説明します。

このテクニックの**肝はマーチングループ**にあります。

```glsl
for(int i= 0;i < 99; i++) {
    pos = ro + ray * t;
    float d = map(pos);
    d = max(abs(d), 0.02 + (exp(5.*sin(time * 0.3)) / exp(5.)) * 0.6);
    ac += exp(-d * 3.);
    gl += step(0.7, ii) * 0.3;
    t += d * 0.5;
}
```

アニメーションのコードだったり無関係のところがあるので大事なところだけピックアップします。

このルックの肝はこの二行です。

```glsl
d = max(abs(d), 0.02);
ac += exp(-d * 3.);
```

一行目は**オブジェクトを透過させるための処理**です。

この記事で距離関数は内部は負の距離になり、また限りなく0に距離近づいたら色を決定して早期終了すると説明しました。

**実は今回のシェーダーではレイマーチング中の早期終了分岐が無くなっています**。

**最短距離の絶対値**を取り、**進む距離が絶対に0にならないように、今回だとmaxで0.02以下にならないようにする**ことで、**レイがオブジェクトを突き抜けてforループが終わるまで進み続けます**。

そして二行目は距離を適当な指数関数で減衰した値を密度としたボリューム累積を行っています。

↓はexp(-d*3.0)をプロットしたものです、距離関数で定義した輪郭付近(距離が0付近)が大きくなることがわかると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/41f44578-0142-0a59-48e4-7c4522a91fe3.png)


このボリュームを使って色付けをすると**X線で写されたように透過感**のあるルックになります。

今回のシェーダーのPhantom Modeに関連する箇所だけを抜き出した部分をこちらに置いておきます。

https://www.shadertoy.com/view/wl2GWG
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136662/cb85b8a6-8c3b-9d51-ac59-ed2a01189d3f.png)

## HSV関数について
暗記していたHSVからRGBに変換するコードは@keim_at_si先生のこちらの記事のものです

[[汎用関数]HSV2RGB 関数](https://qiita.com/keim_at_si/items/c2d1afd6443f3040e900)

丁寧な解説もありますし、コードがとにかく短いので、ある程度仕組みを理解しておけば丸暗記でなくても意外と実装できるようになりました！

## 魚眼レンズについて
```glsl
vec3 ray = normalize(fo*((1.0-dot(p, p))*0.3 + 1.)+le*p.x+up*p.y);
```

この部分で魚眼っぽい効果を付けています。

肝はこちらの部分です。

```glsl
((1.0-dot(p, p))*0.3 + 1.)
```

ここでスクリーン座標（~1~1）の距離の二乗でピクセル毎にfovを変更することで魚眼っぽい効果が生まれます。

@edo_m18先生のこちらの記事でさらに詳しく魚眼効果を解説してくださっています。

[[GLSL] Shadertoyのシェーダ芸人になるためのTips集](https://qiita.com/edo_m18/items/1532aceb9d470174caaf#%E8%A6%96%E7%82%B9%E3%82%92%E6%AD%AA%E3%82%81%E3%82%8B)

# まとめ
シェーダーライブコーディングがとても楽しいものだというのがこの記事を通してお伝えできていればうれしいです。

後半では、当日行ったライブコーディングで使用された重要なテクニックをいくつか紹介しました。

このように、この記事の内容をしっかり理解できていれば、**少しのテクニックを追加するだけで魔法のように全然異なる見た目を作ることができます！**

作る方も楽しいんですが、見る方もかなり楽しいので、**この記事を読んで興味を持ってくれた方のライブコーディングが見れる時を楽しみにしています！！**