第4回.(中級)頂点カラーで線の太さを制御する

はじめに

 今回は頂点カラー(R)を使ってエッジの太さを調整してみます。ついでにエッジの色と、距離に応じた太さ調整も入れてみましょう。

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

  • VertexModifier関数で頂点カラーを使用する
  • オブジェクト原点とカメラとの距離を求める

ソースコード解説

 前回からの変更点を解説していきます。

Shader "Custom/Toon7" {
	Properties{
		_Color("Color", Color) = (1,1,1,1)
		_MainTex("Albedo (RGB)", 2D) = "white" {}
		_OffsetTex("Offset (RGB)", 2D) = "black" {}
		_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
		
		_EdgeColor("Edge Color", Color) = (0,0,0,0)
		_EdgeWidth("Edge Width",Range(0,0.03)) = 0.001
	}

 名前はToon7にしました。エッジの色指定用として_EdgeColorを追加してあります。デフォルト0,0,0,0で黒にしています。


// ここから輪郭線パス
Cull front
CGPROGRAM
#include "UnityCG.cginc"
#pragma surface surf Standard fullforwardshadows vertex:vertexmod
#pragma target 3.0

float4 _EdgeColor;
float _EdgeWidth;

 表面のパスは飛ばして、エッジのパスに入ります。_EdgeColorを忘れずに追加しておきます。


void vertexmod(inout appdata_full v)
{
	float dist = length(mul(unity_WorldToObject,float4(_WorldSpaceCameraPos,1)).xyz);
	v.vertex += float4(v.normal * _EdgeWidth * v.color.r * dist, 0);
}

 まず、appdata_baseには頂点カラーが含まれていないので、appdata_fullに変更しています。これら構造体は"UnityCG.cginc"の中に宣言されています。
 次にdistでは”オブジェクト原点からカメラまでの距離”を求めています。_WorldSpaceCameraPosにはカメラのワールド座標が入っているので、ワールド空間→オブジェクト空間変換行列(unity_WorldToObject)を乗算して、”オブジェクト空間上のカメラ位置”を求めます。さらにそのベクトルをlength関数に渡して、原点からの距離を求めています。
 最後に頂点カラーのRチャンネルであるv.color.rと、距離distを乗算して輪郭線の太さを調整しています。距離が遠くなるほど線も太くなるので、常に太さが一定に保たれるようになります。


void surf(Input IN, inout SurfaceOutputStandard o) 
{
	o.Albedo = 0;
	o.Emission = _EdgeColor;
	o.Alpha = 0;
}
	ENDCG

 エッジの色をEmission(発光)に入れることで、周囲の状況に関わらず確実にその色が出るようにします。
(周囲のライティング状況を反映させたい場合はEmissionではなくAlbedoに入れます。またStandardライティング関数を用いているため、ReflectionProbeによる多少のスペキュラが入ってしまいます)

ソース全文

Shader "Custom/Toon7" {
	Properties{
		_Color("Color", Color) = (1,1,1,1)
		_MainTex("Albedo (RGB)", 2D) = "white" {}
		_OffsetTex("Offset (RGB)", 2D) = "black" {}
		_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
		
		_EdgeColor("Edge Color", Color) = (0,0,0,0)
		_EdgeWidth("Edge Width",Range(0,0.03)) = 0.001
	}
		SubShader
		{
			Tags { "RenderType" = "Opaque" }
			LOD 200

			Cull off
			CGPROGRAM
			#pragma surface surf Toon fullforwardshadows
			#pragma target 3.0

			sampler2D _MainTex;
			sampler2D _RampTex;
			sampler2D _OffsetTex;
			float _GIPower;

			struct Input {
				float2 uv_MainTex;
			};

			half _Glossiness;
			half _Metallic;
			fixed4 _Color;
			
			struct SurfaceOutputStandardUV
			{
				fixed3 Albedo;      // base (diffuse or specular) color
				fixed3 Normal;      // tangent space normal, if written
				half3 Emission;
				half Metallic;      // 0=non-metal, 1=metal
				half Smoothness;    // 0=rough, 1=smooth
				half Occlusion;     // occlusion (default 1)
				fixed Alpha;        // alpha for transparencies
				fixed2 uv;
			};

			#include "UnityPBSLighting.cginc"
			inline half4 LightingToon(SurfaceOutputStandardUV s, half3 viewDir, UnityGI gi)
			{
				float diffuse = saturate(dot(s.Normal, gi.light.dir));
				
				float3 offset = tex2D(_OffsetTex,s.uv).rgb;
				// 陰オフセット
				diffuse = saturate(diffuse - offset.r);
				// 明オフセット
				diffuse = saturate(diffuse + offset.g);

				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(SurfaceOutputStandardUV s, UnityGIInput data, inout UnityGI gi)
			{
				SurfaceOutputStandard s2;
				s2.Albedo = s.Albedo;
				s2.Normal = s.Normal;
				s2.Emission = s.Emission;
				s2.Metallic = s.Metallic;
				s2.Smoothness = s.Smoothness;
				s2.Occlusion = s.Occlusion;
				s2.Alpha = s.Alpha;

				LightingStandard_GI(s2, data, gi);
			}
			void surf(Input IN, inout SurfaceOutputStandardUV 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;
				o.uv = IN.uv_MainTex;
			}
			ENDCG

			// ここから輪郭線パス
			Cull front
			CGPROGRAM
			#include "UnityCG.cginc"
			#pragma surface surf Standard fullforwardshadows vertex:vertexmod
			#pragma target 3.0

			float4 _EdgeColor;
			float _EdgeWidth;

			struct Input 
			{
				float2 uv_MainTex;
			};

			void vertexmod(inout appdata_full v)
			{
				float dist = length(mul(unity_WorldToObject,float4(_WorldSpaceCameraPos,1)).xyz);
				v.vertex += float4(v.normal * _EdgeWidth * v.color.r * dist, 0);
			}

			void surf(Input IN, inout SurfaceOutputStandard o) 
			{
				o.Albedo = 0;
				o.Emission = _EdgeColor;
				o.Alpha = 0;
			}
			ENDCG
		}
		FallBack "Diffuse"
}

結果

エッジ調整あり エッジ調整なし
f:id:IARIKE:20170417234840p:plain f:id:IARIKE:20170417234830p:plain

 目や顎の上半分?の線が細くなって、無くなっているのがわかるかと思います。またエッジに赤みを入れて、肌色に馴染ませています。

 頂点カラーは以下のような状態になっています(Blender画面)
 f:id:IARIKE:20170417235304p:plain

おわりに

 今回はエッジの調整を行いました。次回からは法線編集やステンシルバッファの活用など、より応用的な内容に入っていきます。