【Unity】Skyboxシェーダーの書き方

UnityのSkyboxはシェーダーを書くことで自作できるのですが、あまり情報がなかったのでまとめてみました。

まず、次のようなy軸方向で色がグラデーションするシンプルな例を見てみましょう。

![vertical.gif](https://qiita-image-store.s3.amazonaws.com/0/61569/2a37014d-cc6f-be93-91aa-099781813a85.gif)

このSkyboxを実現しているシェーダーは以下のようになります。
Skyboxシェーダーの特徴は頂点データとして渡される`TEXCOORD0`です。ここには視線方向の三次元の値が[-1, 1]の範囲で入っています。例えば、デフォルトのカメラは+z軸方向を向いているので、画面の真ん中での`texcoord`の値は[0, 0, 1]になります。
また、背景として描画するので、`RenderType`と`Queue`は`Background`にして、`ZWrite`は`Off`にしておきます。`"PreviewType"="SkyBox"`は必須ではないですが、設定するとマテリアルのプレビューがSkybox用のものになります。

```Vertical.shader
Shader "CustomSkybox/Vertical"
{
	SubShader
	{
		Tags
		{
			"RenderType"="Background"
			"Queue"="Background"
			"PreviewType"="SkyBox"
		}

		Pass
		{
			ZWrite Off
			Cull Off

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			struct appdata
			{
				float4 vertex : POSITION;
				float3 texcoord : TEXCOORD0;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float3 texcoord : TEXCOORD0;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.texcoord = v.texcoord;
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				return fixed4(lerp(fixed3(1, 0, 0), fixed3(0, 0, 1), i.texcoord.y * 0.5 + 0.5), 1.0);
			}
			ENDCG
		}
	}
}
```

このシェーダーでマテリアルを作成して、`Window > Lighting > Settings > Scene > Environment > Skybox Material`にセットすることで、Skyboxとして適用することができます。


frag関数を以下のようにすると+x軸方向が赤、+y軸方向が緑、+z軸方向が青となるようなSkyboxとなります。

```RGB.shader
fixed4 frag (v2f i) : SV_Target
{
	return fixed4(i.texcoord * 0.5 + 0.5, 1.0);
}
```

![rgb.gif](https://qiita-image-store.s3.amazonaws.com/0/61569/904a2f7c-288a-0fd6-b51e-0b1b5670f6d6.gif)


最後にせっかくなので太陽を書いてみました。

![sun.gif](https://qiita-image-store.s3.amazonaws.com/0/61569/ea9b33bf-1a6b-e5f0-62e8-ab912f0d5842.gif)

このシェーダーでは太陽の方向`_SunDir`と視線方向`texcoord`の内積をとることで、どれぐらい太陽の方向を向いているかを計算しています。この値を適当な値で累乗することで太陽の強さを設定しています。

```Sun.shader
Shader "CustomSkybox/Sun"
{
	Properties {
		_BGColor ("Background Color", Color) = (0.7, 0.7, 1, 1)
		_SunColor ("Color", Color) = (1, 0.8, 0.5, 1)
		_SunDir ("Sun Direction", Vector) = (0, 0.5, 1, 0)
		_SunStrength("Sun Strengh", Range(0, 30)) = 12
	}
	SubShader
	{
		Tags
		{
			"RenderType"="Background"
			"Queue"="Background"
			"PreviewType"="SkyBox"
		}

		Pass
		{
			ZWrite Off
			Cull Off

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			fixed3 _BGColor;
			fixed3 _SunColor;
			float3 _SunDir;
			float _SunStrength;

			struct appdata
			{
				float4 vertex : POSITION;
				float3 texcoord : TEXCOORD0;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float3 texcoord : TEXCOORD0;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.texcoord = v.texcoord;
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				float3 dir = normalize(_SunDir);
				float angle = dot(dir, i.texcoord);
				fixed3 c = _BGColor + _SunColor * pow(max(0.0, angle), _SunStrength);
				return fixed4(c, 1.0);
			}
			ENDCG
		}
	}
}
```

## 参考

* [GitHub \- keijiro/UnitySkyboxShaders: Custom skybox shaders](https://github.com/keijiro/UnitySkyboxShaders)
* [Cg Programming/Unity/Skyboxes \- Wikibooks, open books for an open world](https://en.wikibooks.org/wiki/Cg_Programming/Unity/Skyboxes)
* [Simple procedural skybox with a moon – Cyrill Nadezhdin – Medium](https://medium.com/@cyrilltoboe/simplest-gradient-skybox-with-moon-8581610c86c0)