第1回.(初級)カスタムライティング関数でRampシェーダーを作る

はじめに

 今回はライティング関数をカスタマイズして、陰影をクッキリさせるシェーダを作ります。このように陰影の滑らかさを調整できるシェーダをRamp(傾斜)シェーダと呼ぶみたいです。
 環境はUnity5.6.0f3です。

本記事で解説される新要素

  • カスタムライティング関数

何もしないカスタムライティング

 Unity - マニュアル: サーフェイスシェーダーライティングの例を参考に、カスタムライティングを行うシェーダを作成します。

Shader "Custom/Ramp" {
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_Glossiness ("Smoothness", Range(0,1)) = 0.5
		_Metallic ("Metallic", Range(0,1)) = 0.0
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Toon fullforwardshadows

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		half _Glossiness;
		half _Metallic;
		fixed4 _Color;

		// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
		// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
		// #pragma instancing_options assumeuniformscaling
		UNITY_INSTANCING_CBUFFER_START(Props)
		// put more per-instance properties here
		UNITY_INSTANCING_CBUFFER_END


		#include "UnityPBSLighting.cginc"
		inline half4 LightingToon(SurfaceOutputStandard s, half3 viewDir, UnityGI gi)
		{
			return LightingStandard(s, viewDir, gi);
		}
		inline void LightingToon_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi)
		{
			LightingStandard_GI(s, data, gi);
		}


		void surf (Input IN, inout SurfaceOutputStandard o) {
			// Albedo comes from a texture tinted by color
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			// Metallic and smoothness come from slider variables
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

自動生成されたシェーダから変更した点は

#pragma surface surf Toon fullforwardshadows

StandardをToonに変更しています。これはStandardライティング関数をToonライティング関数に変えます、という意味になります。

更に以下が追加されています。

#include "UnityPBSLighting.cginc"
inline half4 LightingToon(SurfaceOutputStandard s, half3 viewDir, UnityGI gi)
{
	return LightingStandard(s, viewDir, gi);
}	
inline void LightingToon_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi)
{
	LightingStandard_GI(s, data, gi);
}

 
 実際のライティング関数がLightingToonとLightingToon_GIになります。LightingToon_GIはLightProbeやEnlightenからGIを求める関数のようですが、今回は改造しません。
 今回は主にLightingToonを改造していきます。
 このシェーダはStandardライティング関数と処理内容が同じなので、見た目も通常と全く変わりません。

f:id:IARIKE:20170402160044p:plain

Step関数でクッキリさせる

 さて、LightingToon関数を改造して、陰影をクッキリさせます。

inline half4 LightingToon(SurfaceOutputStandard s, half3 viewDir, UnityGI gi)
{
	float diffuse = saturate(dot(s.Normal, gi.light.dir));
	return  step(0.3, diffuse);
}

 diffuseの行でピクセル法線とライト方向の内積(dot)を計算することで、拡散反射項を求めています。
(さらにsaturateで0-1の範囲にクランプし、マイナスの値にならないようにしています)

 step関数は、第2引数が第1引数以上のとき1を返し、それ以外のときは0を返す関数です。つまりdiffuseが0.3以上なら白、0.3未満なら黒になるということです。

f:id:IARIKE:20170402160103p:plain

色とGIを入れる

 現在マテリアルの色を全く考慮していないので、Albedoをdiffuseに乗算して色を出します。
また暗部が暗すぎるので、GIからの値も加算してあげます。

inline half4 LightingToon(SurfaceOutputStandard s, half3 viewDir, UnityGI gi)
{
	float diffuse = step(0.3,dot(s.Normal, gi.light.dir));
	return float4(s.Albedo * diffuse + s.Albedo * gi.indirect.diffuse, 0);
}

 また、step関数を使うことでsaturateが不要だということに気づいたので、外してあります。

f:id:IARIKE:20170402160124p:plain

Rampテクスチャを使う

 現在はstep関数で陰影を区切っていますが、これだと2段階の陰影しか作れませんし、段階ごとに色を変えるようなこともできません。
 そこで、diffuseの値をUV座標のx値として用いるRampテクスチャを導入して、任意に段階を作れるように改造します。
 またGIの明るさを調整できるように、"_GIPower"を追加します。

Properties {
	_Color ("Color", Color) = (1,1,1,1)
	_MainTex ("Albedo (RGB)", 2D) = "white" {}
	_Glossiness ("Smoothness", Range(0,1)) = 0.5
	_Metallic ("Metallic", Range(0,1)) = 0.0
	_RampTex("Ramp (RGB)", 2D) = "white" {}
	_GIPower("GI Power",Range(0,1)) = 0.1
}

 まずプロパティに"_RampTex"を追加します。デフォルトは白テクスチャなので、なにも設定しない場合は常に100%の明るさになります。"_GIPower"も追加しておきます。

	sampler2D _MainTex;
	sampler2D _RampTex;
	float _GIPower;

 サンプラー変数、"_GIPower"もちゃんと宣言しておきます。

inline half4 LightingToon(SurfaceOutputStandard s, half3 viewDir, UnityGI gi)
{
	float diffuse = saturate(dot(s.Normal, gi.light.dir));
	float3 ramp = tex2D(_RampTex, float2(1-diffuse, 0.5)).rgb;
	return float4(s.Albedo * ramp + s.Albedo * gi.indirect.diffuse * _GIPower, 0);
}

_RampTexからフェッチする際に、(1-diffuse)をUV座標のXとしています。Yは固定の0.5ですが、ここをカスタマイズすればより変化を増やすことができます。
 Xが(1-diffuse)なのは、テクスチャの左側が明、右側が暗になるようにしたかったからです。

f:id:IARIKE:20170402160134p:plain

またRampテクスチャは下記のもので、インポート設定でRepeatからClampに変更してあります。
f:id:IARIKE:20170402160239p:plain

完成したソースコードはこちらになります

Shader "Custom/Ramp" {
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_Glossiness ("Smoothness", Range(0,1)) = 0.5
		_Metallic ("Metallic", Range(0,1)) = 0.0
		_RampTex("Ramp (RGB)", 2D) = "white" {}
		_GIPower("GI Power",Range(0,1)) = 0.1
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Toon fullforwardshadows

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;
		sampler2D _RampTex;
		float _GIPower;

		struct Input {
			float2 uv_MainTex;
		};

		half _Glossiness;
		half _Metallic;
		fixed4 _Color;

		// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
		// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
		// #pragma instancing_options assumeuniformscaling
		UNITY_INSTANCING_CBUFFER_START(Props)
			// put more per-instance properties here
		UNITY_INSTANCING_CBUFFER_END


		#include "UnityPBSLighting.cginc"
		inline half4 LightingToon(SurfaceOutputStandard s, half3 viewDir, UnityGI gi)
		{
			float diffuse = saturate(dot(s.Normal, gi.light.dir));
			float3 ramp = tex2D(_RampTex, float2(1-diffuse, 0.5)).rgb;

			return float4(s.Albedo * ramp + s.Albedo * gi.indirect.diffuse * _GIPower, 0);
		}

		inline void LightingToon_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi)
		{
			LightingStandard_GI(s, data, gi);
		}


		void surf (Input IN, inout SurfaceOutputStandard o) {
			// Albedo comes from a texture tinted by color
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			// Metallic and smoothness come from slider variables
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

まとめ

 今回はカスタムライティング関数を作って、シェーディング結果を段階的にするRampシェーダーを作成しました。
 5.6になってINSTANCING用の自動生成コードが追加されていたり、カスタムライティングもGI関数とセットで作らないとエラーになるなど、少し複雑になっています。