[Unity]CustomShaderGUIによるBlend Mode指定

本文
この記事は、[Unity Advent Calendar 2017](https://qiita.com/advent-calendar/2017/unity) の10日目の記事になります。
前日の記事は @lycoris102 さんの [[Unity] Editor拡張でInspectorの"戻る"を実現する](https://qiita.com/lycoris102/items/b2ffc86f94e904fd50fd) です。gifを用いた非常に丁寧な投稿でした。


本内容ですが、以前に[ShaderのCullingやZTest等のMaterialPorpertyをEnumにて指定](https://qiita.com/fujioko/items/f5e5d3651391f9b16327)できるようにしましたが、
コメントにてMaterialのEditor拡張を行う[CustomShaderGUI](https://docs.unity3d.com/ja/current/Manual/SL-CustomShaderGUI.html)を教えて頂きました。

大分期間が空いてしまいましたが、今回それを用いてBlend Modeのプリセットを指定できるよう拡張しました。
サンプルとしてSpriteRenderer用のシンプルなシェーダーを作ります。

プリセットのSprite-Defaultシェーダーは加算ブレンドがない為、
Particle/Additive等を代用したり自前で用意したりする必要がありますが
もうこの際シンプルなシェーダーはコレ一つで足りるようにします。


```ShaderBasic.shader
// 標準スプライトシェーダー
Shader "Custom/SpriteBasic" {
	Properties {
		_MainTex ("Base (RGB), Alpha (A)", 2D) = "white" {}
		[Enum(UnityEngine.Rendering.CullMode)]
		_Cull("Cull", Float) = 0		// Off
		[Enum(UnityEngine.Rendering.CompareFunction)]
		_ZTest("Z Test", Float) = 4		// LEqual

		[Enum(UnityEngine.Rendering.BlendMode)]
		_SrcBlend("Src Factor", Float) = 5	// SrcAlpha
		[Enum(UnityEngine.Rendering.BlendMode)]
		_DstBlend("Dst Factor", Float) = 10	// OneMinusSrcAlpha

		[HideInInspector] _Mode("__mode", Float) = 0.0	// ブレンドモードキャッシュ
	}
	
	SubShader {
		Tags {
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
			"RenderType" = "Transparent"
			"PreviewType" = "Plane"
			"CanUseSpriteAtlas"="True"
		}
		
		Cull [_Cull] ZTest [_ZTest] ZWrite Off
		Blend[_SrcBlend][_DstBlend]

		Pass {
			CGPROGRAM
			struct appdata_t {
				float4 vertex : POSITION;
				half2 texcoord : TEXCOORD0;
				fixed4 color : COLOR;
			};

			struct v2f {
				float4 pos : SV_POSITION;
				half2 uv : TEXCOORD0;
				fixed4 color : COLOR;
			};

			sampler2D _MainTex;

			v2f vert(appdata_t v) {
				v2f o;
				o.pos = mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)));
				o.uv = v.texcoord;
				o.color = v.color;

#ifdef PIXELSNAP_ON
				o.pos = UnityPixelSnap(o.pos);
#endif
				return o;
			}

			fixed4 frag(v2f i) : SV_Target {
				return tex2D(_MainTex, i.uv) * i.color;
			}

			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest
			ENDCG
		}
	}

	// ShaderGUI指定
	CustomEditor "SpriteBasicShaderGUI"
}
```
```SpriteBasicShaderGUI.cs
using UnityEngine;
using UnityEditor;
using System;


/// <summary>
/// 標準スプライトシェーダー
/// </summary>
public sealed class SpriteBasicShaderGUI : ShaderGUI {
	/// <summary>
	/// ブレンド方法
	/// </summary>
	public enum BlendMode {
		Opaque,					// 不透明
		Transparent,			// 半透明
		Additive,				// 加算
		AdditiveTransparent,	// 加算半透明
	}

	private MaterialProperty blendProp, cullProp, ztestProp, snapProp;

	/// <summary>
	/// Inspector表示
	/// </summary>
	public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props) {
		this.blendProp = ShaderGUI.FindProperty("_BlendMode", props);
		this.cullProp = ShaderGUI.FindProperty("_Cull", props);
		this.ztestProp = ShaderGUI.FindProperty("_ZTest", props);
		this.snapProp = ShaderGUI.FindProperty("_PixelSnap", props);

		materialEditor.ShaderProperty(this.cullProp, "Culling");
		materialEditor.ShaderProperty(this.ztestProp, "Z Test");

		BlendMode mode = (BlendMode)this.blendProp.floatValue;
		EditorGUI.BeginChangeCheck();
		mode = (BlendMode)EditorGUILayout.Popup("Blend Mode", (int)mode, Enum.GetNames(typeof(BlendMode)));
		if (EditorGUI.EndChangeCheck()) {
			this.blendProp.floatValue = (float)mode;
			foreach (UnityEngine.Object obj in this.blendProp.targets)
				this.SetupBlendMode(obj as Material, mode);
		}
		
		materialEditor.ShaderProperty(this.snapProp, "Pixel Snap");
		materialEditor.RenderQueueField();
		//materialEditor.EnableInstancingField();	// GPU Instancing未対応
	}

	/// <summary>
	/// Shader切り替えコールバック
	/// </summary>
	public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader) {
		base.AssignNewShaderToMaterial(material, oldShader, newShader);

		
		// MaterialのShader切り替え時にBlend指定が変更されてしまうので再設定
		this.SetupBlendMode(material, (BlendMode)material.GetFloat("_BlendMode"));
	}

	/// <summary>
	/// ブレンド種類設定
	/// </summary>
	private void SetupBlendMode(Material material, BlendMode blendMode) {
		switch (blendMode) {
			case BlendMode.Opaque:
				material.SetFloat("_SrcBlend", (float)UnityEngine.Rendering.BlendMode.One);
				material.SetFloat("_DstBlend", (float)UnityEngine.Rendering.BlendMode.Zero);
				break;
			case BlendMode.Transparent:
				material.SetFloat("_SrcBlend", (float)UnityEngine.Rendering.BlendMode.SrcAlpha);
				material.SetFloat("_DstBlend", (float)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
				break;
			case BlendMode.Additive:
				material.SetFloat("_SrcBlend", (float)UnityEngine.Rendering.BlendMode.One);
				material.SetFloat("_DstBlend", (float)UnityEngine.Rendering.BlendMode.One);
				break;
			case BlendMode.AdditiveTransparent:
				material.SetFloat("_SrcBlend", (float)UnityEngine.Rendering.BlendMode.SrcAlpha);
				material.SetFloat("_DstBlend", (float)UnityEngine.Rendering.BlendMode.One);
				break;
		}
	}
}
```

これで下記画像のように半透明・加算のマテリアル等を用意すればこのShaderひとつで対応できます。
![SpriteMaterial.PNG](https://qiita-image-store.s3.amazonaws.com/0/105136/1b74a162-beaf-d8fb-82ce-c768db711413.png)

今回Sprite用のマテリアルなのでMainTexの表記はInspector上に表示しないようにしています。
Tintカラーもやや蛇足な処理なので今回は無視しています。

GPU Instancingを対応したい場合はビルトインシェーダーのUnitySprite.cgincを参考にすればよいでしょう。
※ビルトインシェーダーはMITライセンスが明記されていますのでその旨ご注意ください。

---

次の記事は @Kan_Kikuchi さんによる [日本語から変数や関数名を生成するエディタ拡張](http://kan-kikuchi.hatenablog.com/entry/NameGeneration)になります！