OutLineShader

OutLineShaderについて書いていきたいと思います。


f:id:shinobigiken:20170713121113p:plain


ShaderでOutLineを作る方法はいくつかあります。


一つは
視線ベクトルと法線ベクトルの内積を取って結果が0.0(ベクトル同士が垂直)になる部分を黒くしてOutLineを表現する方法です。
下記URLにこの手法の詳しい解説がされています。

GLSLシェーダによるカートゥーンレンダリング

床井研究室 - トゥーンシェーディング


もう一つは
OutLineにしたいオブジェクトを二つ描画して、
片方のオブジェクトはその表面はカリングして法線方向に押し出す事で手法です。


今回は後者の「OutLineにしたいオブジェクトを二つ描画して、片方のオブジェクトはその表面はカリングして法線方向に押し出す手法」
について書いていきたいと思います。


ちなみにShaderForgeとAmplify Shader EditorのSampleにあったOutLineShaderは両方とも後者の手法で作成されてました。

UnityChanToonShaderもこの手法でOutLineを作っています。

Shader "Custom/Outline" {
	//Inspectorで弄る値
	Properties {
		_Color ("Color", Color) = (1., 1., 1. ,1.)
		_Outline ("_Outline", Range(0,0.1)) = 0
		_OutlineColor ("Color", Color) = (1, 1, 1, 1)
	}
		SubShader {
			Pass {
				//RenderTypeをOpaqueに指定
				Tags { "RenderType"="Opaque" }
				//PrimitiveのFrontをカリング
				Cull Front
				//ここからShaderが始まる合図
				CGPROGRAM
				//VertexShaderをvertで定義
				#pragma vertex vert
				//FragmentShaderはfragで定義
				#pragma fragment frag
				//Unityのモジュール読み込み
				#include "UnityCG.cginc"

				//VertexShaderからFragmentShaderに渡す値
				struct v2f {
				//各頂点の位置
				float4 pos : SV_POSITION;
				};
	
				//Propertiesで設定した変数の宣言
				float _Outline;
				float4 _OutlineColor;

				//VertexShader
				float4 vert(appdata_base v) : SV_POSITION {
					//構造体v2fにout
					v2f o;
					//各頂点を座標変換
					o.pos = UnityObjectToClipPos(v.vertex);
					//法線を座標変換して変数normalに入れる
					float3 normal = mul((float3x3) UNITY_MATRIX_MV, v.normal);
					//カメラから見て、法線の方向にxとyだけの値を増やしたいので
					//TransformViewToProjection関数で
					//法線のxyにプロジェクション行列(投影行列)をかける
					float2 offset = TransformViewToProjection(normal.xy);
					//各頂点の位置から、上記で計算したプロジェクション行列*法線の方向に変数_Outlineぶん大きくする
					o.pos.xy += offset * _Outline;
					//out各頂点の位置をout
					return o.pos;
				}
					
				//FragmentShader
				half4 frag(v2f i) : COLOR {
					//Propertiesで設定した変数_OutlineColorを入れる
					return _OutlineColor;
				}
				//一つのShaderの終わりの合図
				ENDCG
			}

			//本体のShader
			//ここからShaderが始まる合図
			CGPROGRAM
			//SurfaceShaderをsurfに定義Lambert(スペキュラがないモデル)に設定
			#pragma surface surf Lambert
			//Propertiesで設定した変数の宣言
			fixed4 _Color;
			//Shaderを割り当てたオブジェクトから引っ張ってくる値
			struct Input {
				fixed4 col : COLOR;
			};
			//SurfaceShader
			void surf(Input IN, inout SurfaceOutput o) {
				//Albedoの値を変数_Colorに設定
				o.Albedo = _Color;
			}
			//一つのShaderの終わりの合図
			ENDCG
		}
	//上記のShaderが全部駄目だったらDiffuseを適用
	FallBack "Diffuse"
}


ちなみに上記の方法だと、法線方向に膨らませているだけなので、Boxのような形状のオブジェクトに当てると下記のように角が切れてしまいます。


その場合オブジェクトのInspectorでSmoothAngleを90にすると
f:id:shinobigiken:20170713174725p:plain


角が直ります。
ただ法線の情報を変えてしまっているのでライティングまで変わってしまいます。


UnityChanToonShaderも今回説明した手法で作られているのでOutLineを大きくすると角が切れてしまいます。のでとりあえず角切れ問題は必要があれば調べて追記したいと思います。