読者です 読者をやめる 読者になる 読者になる

Onoty3D

Unityに関するメモとか

Shaderでゾワゾワ表現

Unity Shader プロ生ちゃん

ふと、アニメなんかでみるゾワゾワッとする表現がShaderで作ってみたくなったので試してみました。
まだ課題が残っていますがとりあえず一旦ここで一息。

結果から出すとこんな感じになりました。
f:id:onoty3d:20161109185553g:plain

UnityPackageはこちら
Unity5.5で作成したので、5.4以前で上手く動かないかもしれません。
ToonLit Shaderがベースなので、各バージョンのToonLit Shaderにパラメータと数式を移植すれば動くと思います。

またテッセレーションが有効な環境(DX11以上 / OpenGL Core)でないと動きません。

使い方

Shaderのパラメータはこんな感じ。
パラメータ名は雰囲気で付けました。
f:id:onoty3d:20161109190148p:plain
YBorder:ゾワゾワっとさせる中心となるY座標です。Materialをセットするメッシュでのローカル座標になります。

※ここを動かすとゾワゾワ位置が移動します。
上記動画はスクリプトでこのYBorderを変化させています。なのでShaderだけでゾワゾワが下から上に走るわけではありません。
あしからず。

YRange:YBorder±YRangeの範囲をゾワゾワっとさせます。
Volume:ゾワゾワの大きさです。
Pow:後述のGapを顕著にします
Gap:ゾワゾワの高低差を大きくします
Fineness:ゾワゾワの細かさです。
Edge:ゾワゾワを出す頂点とカメラの向きの関係です。0に近いほどカメラに対して横を向いている頂点(基本的にモデルの端っこ)からゾワゾワがでます。
Tessellationテッセレーションのパラメータ

とりあえず、適当に色々触ると色々動くので試してみてください。
f:id:onoty3d:20161109191625g:plain

うまくやればFur Shaderっぽくもなります。
f:id:onoty3d:20161109194744p:plain

メモ

はじめは単純に頂点を動かすだけだったんですが、ボコボコとした三角形が出来るだけでした。
f:id:onoty3d:20161109190612g:plain
この時点でもっとハイポリゴンなメッシュじゃないと無理かな、と思ってやめようと思ったのですが、
Twitterテッセレーション使いなされとアドバイスを貰ったのでテッセレーション処理も入れてみるとうまく行きました。
とりあえず固定量のテッセレーション

課題

球体など完全に凸なメッシュの場合はEdgeを0近くにすれば端からしかゾワゾワは出ませんが、凹凸のあるメッシュの場合は意図せぬ部分からもゾワゾワが出ます。
例えばプロ生ちゃんの髪だと、前髪からも出ます。
f:id:onoty3d:20161109190934p:plain
ステンシルバッファを使ってマスク処理するとか、Z座標の位置も判定に入れるとか、そのへんで調整すればいいかもしれません。

ソース

Shader "Onoty3D/Toon/Lit ZowaZowa" {
	Properties{
		_Color("Main Color", Color) = (0.5,0.5,0.5,1)
		_MainTex("Base (RGB)", 2D) = "white" {}
		_Ramp("Toon Ramp (RGB)", 2D) = "gray" {}
		[Enum(OFF,0,FRONT,1,BACK,2)] _CullMode("Cull Mode", int) = 2 //OFF/FRONT/BACK
		
		_YBorder("YBorder", float) = 0
		_YRange("YRange", float) = 0.005
		_Volume("Volume", float) = 0.001
		_Pow("Pow", Range(0, 3)) = 2
		_Gap("Gap", Range(1, 3)) = 2
		_Fineness("Fineness", float) = 12
		_Edge("Edge", Range(0, 1)) = 0.1
		_Tess("Tessellation", Range(1, 32)) = 4
	}

		SubShader{
		Tags{ "RenderType" = "Opaque" }
		LOD 200
		Cull[_CullMode]

		CGPROGRAM
#pragma surface surf ToonRamp vertex:vert tessellate:tessFixed
#pragma target 5.0
			sampler2D _Ramp;

#pragma lighting ToonRamp exclude_path:prepass
			inline half4 LightingToonRamp(SurfaceOutput s, half3 lightDir, half atten)
			{
#ifndef USING_DIRECTIONAL_LIGHT
				lightDir = normalize(lightDir);
#endif

				half d = dot(s.Normal, lightDir) * 0.5 + 0.5;
				half3 ramp = tex2D(_Ramp, float2(d,d)).rgb;

				half4 c;
				c.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
				c.a = 0;
				return c;
			}

			float _Tess;
			float4 tessFixed()
			{
				return _Tess;
			}

			struct Input {
				float2 uv_MainTex : TEXCOORD0;
			};

			float _YBorder;
			float _YRange;
			float _Volume;
			float _Gap;
			float _Pow;
			float _Fineness;
			float _Edge;

			void vert(inout appdata_full v) {
				float4x4 modelMatrix = unity_ObjectToWorld;
				float4x4 modelMatrixInverse = unity_WorldToObject;

				float3 normalDirection = normalize(mul(v.normal, modelMatrixInverse)).xyz;
				float3 viewDirection = normalize(_WorldSpaceCameraPos - mul(modelMatrix, v.vertex).xyz);

				float dotValue = saturate(dot(normalDirection, viewDirection));
				float fineness = _Fineness * 100;

				if (dotValue < _Edge) {
					if (abs(v.vertex.y - _YBorder) < _YRange) {
						//ゾワゾワを表現する部分はとりあえずいろいろ試して個人的にいい感じになった数式で特に思想があるわけではありません。
						//float3 value = v.normal * _Volume * pow(_Gap * (sin(v.vertex.y * _Fineness) + sin(v.vertex.x * _Fineness) + sin(v.vertex.z * _Fineness)), _Pow);
						float3 value = v.normal * _Volume * pow(_Gap * (sin(v.normal.y * fineness) + sin(v.normal.x * fineness) + sin(v.normal.z * fineness)), _Pow);
						v.vertex.x += value.x;
						v.vertex.y += value.y;
						v.vertex.z += value.z;
					}
				}
			}

			sampler2D _MainTex;
			float4 _Color;

			void surf(Input IN, inout SurfaceOutput o) {
				half4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
				o.Albedo = c.rgb;
				o.Alpha = c.a;
			}
		ENDCG

	}

	Fallback "Diffuse"
}