【Godot】シェーダーの基本的な使い方

# 概要
この記事では、Godot Engine でのシェーダーの基本的な使い方について説明します。

# プロジェクトを作成
プロジェクトを作成して、2Dノードを作成し "Main" に名前を変更します。

# スプライトを登録
"icon.png" をドラッグ＆ドロップしてスプライトを作成します。
![Godot_Engine_-_TestShader____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/281deaac-490c-d1e4-840f-3cff29beb510.png)

スプライト（ノード名は iconになっています）を選択して、インスペクタから `CanvasItem > Material > [空]` をクリックして、「新規 ShaderMaterial」を選びます。
![Godot_Engine_-_TestShader____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/bcf9f91d-fb60-50f1-e5a1-9a4f749a295e.png)
次に、表示された Material をクリックして、`Shader > [空]` をクリックして、「新規Shader」を選びます。
![Godot_Engine_-_TestShader_-_Main_tscn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/ff7d4962-06e4-b6e1-f40a-e605b106236b.png)

最後に表示された「Shader」の文字をクリックすると、シェーダーエディタが開きます。
![Godot_Engine_-_TestShader_-_Main_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/8d9e149f-45ba-6cac-cdf2-def867633e98.png)

# シェーダーコードの入力
## 何もしないフラグメントシェーダーのコード
最低限のフラグメントシェーダーのコードは以下のとおりです。

```glsl
// CanvasItemのシェーダーであることを宣言
shader_type canvas_item;

// フラグメントシェーダー
void fragment() {
	// テクスチャのUVに対応する色をピクセルに反映する
	COLOR = texture(TEXTURE, UV);
}
```

ここから簡単なシェーダーコードを書いていきます。

## 単色にするシェーダーコード
まずは単色に置き換えるシェーダーです。
![Godot_Engine_-_TestShader_-_Main_tscn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/f95613aa-5c25-84af-3d7f-12634328310d.png)
このようにアルファ値以外を特定の色に置き換えます。

```glsl
// CanvasItemのシェーダーであることを宣言
shader_type canvas_item;

// フラグメントシェーダー
void fragment() {
	// 色を取得
	vec4 color = texture(TEXTURE, UV);
	// アルファ値以外を置き換える
	color.rgb = vec3(1, 1, 1);
	// 反映
	COLOR = color;
}
```

色情報は `vec4` で RGB成分は `.rgb` で取得できるので、その部分を `vec3` で置き換えることで色を特定の色のみにすることができます。

## 2値化する
特定のしきい値を上回っているかそうでないかで色を白と黒にします。
![Godot_Engine_-_TestShader_-_Main_tscn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/e7ce1735-0ab4-7b2a-5c5c-b79f8b8f0649.png)

```glsl

// CanvasItemのシェーダーであることを宣言
shader_type canvas_item;

// フラグメントシェーダー
void fragment() {
	// 色を取得
	vec4 color = texture(TEXTURE, UV);
	
	// RGBの合計を求める
	float sum = color.r + color.g + color.b;
	if(sum > 1.0) {
		// しきい値を超えていたら色を白にする
		color.rgb = vec3(1, 1, 1);
	}
	else {
		// そうでない場合は黒にする
		color.rgb = vec3(0, 0, 0);
	}
	
	// 反映
	COLOR = color;
}
```

RGBの合計を求めて、一定の値（ここでは 1.0）を超えていれば白色、それ以下の場合は黒色としています。

変化がわかりにくいので素材を "icon.png" から以下の素材に変更します……。
![escape.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/6bc86d1e-7db7-9ece-433b-098f8049e3b6.png)

この画像を SpriteのTextureに割り当てると以下のようになります。
![Godot_Engine_-_TestShader_-_Main_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/c242a02d-0e78-1a64-62fc-0a62453966d4.png)

## エディタから値を変更できるようにする
このシェーダーに外部からパラメータを渡せるようにします。

```glsl

// CanvasItemのシェーダーであることを宣言
shader_type canvas_item;

// しきい値
uniform float threshold;

// フラグメントシェーダー
void fragment() {
	// 色を取得
	vec4 color = texture(TEXTURE, UV);
	
	// RGBの合計を求める
	float sum = color.r + color.g + color.b;
	if(sum > threshold) {
		// しきい値を超えていたら色を白にする
		color.rgb = vec3(1, 1, 1);
	}
	else {
		// そうでない場合は黒にする
		color.rgb = vec3(0, 0, 0);
	}
	
	// 反映
	COLOR = color;
}
```

`uniform float threshold;` という値を宣言し、しきい値判定にはその値を使用するようにします。

次に、`icon`ノードにスクリプトをアタッチして以下のように記述します。

```gd:icon.gd
# エディタでパラメータを反映できるようにする
tool

extends Sprite

# 値の範囲は 0.0〜3.0 とする
export(float, 0, 3) var threshold = 1.0

func _process(delta):
	# しきい値をシェーダーに渡す
	material.set_shader_param("threshold", threshold)

```

`material.set_shader_param([パラメータ名], [値])`でシェーダーにパラメータを渡すことができます。

またエディタ上で値を変更して反映できるように `tool` 宣言をしています。
おそらく `シーンファイルを開き直さないと「tool」宣言は反映されない` ので、シーンを保存して開き直します。
するとエディタ上で変更した値がシェーダーにリアルタイムで反映されるようになります。

![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/abdcec6b-ea66-2fac-4f71-5a892866d85d.gif)

# UVスクロールするシェーダー
最後にUVスクロールを行うシェーダーです。
シェーダーに以下の記述をします。

```glsl
// CanvasItemのシェーダーであることを宣言
shader_type canvas_item;

// UV値のオフセット値
uniform vec2 ofs_uv;

// フラグメントシェーダー
void fragment() {
	// 色を取得
	vec4 color = texture(TEXTURE, UV+ofs_uv);
	
	// 反映
	COLOR = color;
}
```

スクリプト側は以下のように記述します。

```gd:icon.gd
# エディタでパラメータを反映できるようにする
tool

extends Sprite

# 値の範囲は 0.0〜1.0 とする
export(float, 0, 1) var ofs_u = 0
export(float, 0, 1) var ofs_v = 0

func _ready():
	# テクスチャリピートを有効にする
	texture.flags = Texture.FLAG_REPEAT

func _process(delta):
	# UVオフセット値をシェーダーに渡す
	material.set_shader_param("ofs_uv", Vector2(ofs_u, ofs_v))
```

`_ready()` でテクスチャにリピート設定をしました。
この設定はシーンを再読み込みする必要があるので、シーンを保存して閉じ、読み込みし直します。

すると、エディタからUVスクロールを行うことができるようになります。
![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/6c352f81-6214-277c-e6eb-b515f20430b3.gif)

# 追記(2021.2.15)：ラスタースクロール
ラスタースクロールのシェーダーを書いてみたので備忘録として残しておきます。
![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/1900a0bc-9cf3-9e32-2678-c03a47d77f40.gif)

まずはスクリプト側

```gd
tool
extends Sprite

export(float, 0, 30) var time := 0.0

func _process(delta):
	# シェーダにパラメータを設定
	material.set_shader_param("time", time)
```

シェーダー側のコードです。

```glsl
shader_type canvas_item;

uniform float time;

// ラスタースクロールする
void fragment() {
	float u = UV.x + 0.5 * sin(UV.y*time);
	vec2 uv = vec2(u, UV.y);
	COLOR = texture(TEXTURE, uv);
}
```

# 追記(2021.2.17)：フォグシェーダー
フォグシェーダーの作り方を紹介している動画を見つけたので試しに作ってみました。

* [Godot Shaders: How to Make Animated 2D Fog(Procedural)](https://www.youtube.com/watch?v=QEaTsz_0o44)

![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/61e3840c-fba9-324e-b3d8-5e624b75f831.gif)

処理は今ひとつ理解できていませんが、以下のシェーダーでプロシージャルなフォグが実装できます。

```glsl
shader_type canvas_item;

// フォグの色
//uniform vec3 color = vec3(0.33, 0.15, 0.82); // 紫
uniform vec3 color = vec3(0.33, 0.48, 0.95); // 青

// ズームインの回数
uniform int OCTAVES = 4;

// フォグの移動速度
uniform float speed = 0.5;

// アルファの倍率
uniform float alpha_rate = 0.5;

// 疑似乱数
float rand(vec2 coord) {
	// UVとの内積を求める
	float fdot = dot(coord, vec2(56, 78));
	
	float fsin = sin(fdot) * 1000.0;
	
	return fract(fsin * 1000.0);
}

// パーリンノイズ
float noise(vec2 coord) {
	// 端数切り捨て
	vec2 i = floor(coord);
	
	// 小数部を取り出す
	vec2 f = fract(coord);
	
	float a = rand(i);                  // 左上
	float b = rand(i + vec2(1.0, 0.0)); // 右上
	float c = rand(i + vec2(0.0, 1.0)); // 左下
	float d = rand(i + vec2(1.0, 1.0)); // 右下
	
	// CubeOut.
	vec2 cubic = f * f * (3.0 - 2.0 * f);
	
	// 小数部の4点から補間する
	return mix(a, b, cubic.x) + (c - a) * cubic.y * (1.0 - cubic.x) + (d - b) * cubic.x * cubic.y;
}

// フラクタル
float fbm(vec2 coord) {
	float value = 0.0;
	float scale = 0.5;
	
	// ズームイン
	for(int i = 0; i < OCTAVES; i++) {
		value += noise(coord) * scale;
		coord *= 2.0;
		scale *= 0.5;
	}
	
	return value;
}

void fragment() {
	vec2 coord = UV * 20.0;
	
	// 移動処理
	vec2 motion = vec2( fbm(coord + vec2(TIME * -speed, TIME * speed)) );
	
	// 最終的な値
	float final = fbm(coord + motion);
	
	COLOR = vec4(color, final * alpha_rate);
}
```

# 追記(2021.2.20)：太らせるシェーダー
公式のサンプルを見ていたら面白いシェーダーを見つけたので紹介です。

* [godotengine/godot-demo-projects Demonstration and Template Projects](https://github.com/godotengine/godot-demo-projects)

![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/5f0a3ea3-3725-6b57-49d5-a7dabd44e978.gif)

```glsl
shader_type canvas_item;

render_mode blend_mix;
uniform float fattyness : hint_range(0, 10) ;

void fragment() {
	vec2 ruv = UV - vec2(0.5, 0.5);
	vec2 dir = normalize(ruv);
	float len = length(ruv);

	len = pow(len * 2.0, fattyness) * 0.5;
	ruv = len * dir;

	vec4 col = texture(TEXTURE, ruv + vec2(0.5, 0.5));

	COLOR = col;
}
```

# 参考

* [SHADERS: INTRO :: Godot Recipes](http://kidscancode.org/godot_recipes/shaders/intro/)
* [シェーディグ言語 -- Godot Endingのドキュメント](https://docs.godotengine.org/ja/stable/tutorials/shading/shading_reference/shading_language.html)
* [godotengine/godot-demo-projects Demonstration and Template Projects](https://github.com/godotengine/godot-demo-projects)
* [GDQuest/godot-shaders](https://github.com/GDQuest/godot-shaders/)