海洋シェーダ(泡)

海洋シェーダ(泡)

 前回の記事(海洋シェーダ(反射と屈折))では、海面に反射して映るオブジェクトや海中の屈折して見えるオブジェクトを追加しました。今回は、海面に生じる泡の追加を行いました。

波の高さにより泡を生成

 波が特定の高さになったときに泡を発生させます。波ごとに高さの判定を行っていますが、その際、波の振幅を計算から除外しています。これにより、振幅が低い波でも泡が発生するように処理をしています。また、泡のテクスチャは波ごとに割り当て、波の移動方向へテクスチャを動かしています。さらに、泡に合わせて求めた法線と最終的に求まった泡を用いて、FoamColorにより泡にハーフランバート反射を追加しています。

float Foam(float3 v, float time, float3 wave_normal, out float3 foam_normal, float wave_height, int wave_number){
	float foam = 0.0;
	foam_normal = 0.0;
	for(int n = 0; n < wave_number; n++){
		foam += HightFoam(amp[n], freq[n], steep[n], speed[n], noise_size[n], foam_speed[n], dir[n], v.xz, time, n, foam_normal, wave_height);
	}
	foam = saturate(foam);

	float foam_normal_strength = _FoamNormalStrength;
	float3 tangent_space_geo_normal = float3(0.0, 0.0, 1.0);
	foam_normal = lerp(tangent_space_geo_normal, foam_normal, foam * foam_normal_strength);
	foam_normal = normalize(foam_normal);

	return foam;
}
float HightFoam(float2 amp, float freq, float steep, float speed, float noise, float f_speed, float2 dir, float2 v, float time, int seed, inout float3 normal, float wave_height)
{
	float2 d = normalize(dir.xy);
	float q = steep;
	seed *= 3;

	float2 v_n = v + noise2(v * noise + time, seed) * _NoiseStrength;
	float f = dot(d, v_n) * freq + time * speed;
	float crest = sin(f);

	float h_0 = 0.5;
	float h_max = _FoamHeightOffset;
	float foam_factor = saturate((crest - h_0) / (h_max - h_0));
	float foam = wave_height * foam_factor * _HeightFoamStrength;

	float3 geo_normal = float3(0.0, 0.0, 1.0);
	float3 foam_normal = UnpackNormal(tex2D(_FoamNormalTex, (v + d * time * speed) * _FoamSize));
	foam_normal = lerp(geo_normal, foam_normal, foam_factor);
	normal = BlendNormal_rnm(geo_normal, normal, foam_normal);
	normal = normalize(normal);

	float foam_tex = tex2D(_FoamTex, (v + d * time * speed) * _FoamSize);

	return saturate(foam * foam_tex);
}
float FoamColor(float3 world_pos, float3 normal, float4 proj_coord, float foam){
	float3 lightDir = normalize(UnityWorldSpaceLightDir(world_pos));
	float3 viewDir = normalize(UnityWorldSpaceViewDir(world_pos));
	
	float diff = saturate(dot(normal, lightDir)) * 0.5 + 0.5;
	diff *= diff;
	diff = diff * _LightColor0;
	
	return foam * diff;
}

泡の生成箇所の計算

 以下のコードにより波の高さを計算しています。波の振幅を含めないことで、振幅が低い波でも泡が発生するように処理をしています。

float2 v_n = v + noise2(v * noise + time, seed) * _NoiseStrength;
float f = dot(d, v_n) * freq + time * speed;
float crest = sin(f);

得られた高さより、泡を発生させる場所を求めます。

float h_0 = 0.5;
float h_max = _FoamHeightOffset;
float foam_factor = saturate((crest - h_0) / (h_max - h_0));

最後に、実際の波の高さ(wave_height)を乗算することで、波の高くなっている部分のみに泡が発生するように処理をしています。また、_HeightFoamStrengthによって泡の強さを調整しています。

float foam = wave_height * foam_factor * _HeightFoamStrength;

法線の計算

 ノーマルマップから泡のテクスチャに合わせて法線を取得します。

float3 geo_normal = float3(0.0, 0.0, 1.0);
float3 foam_normal = UnpackNormal(tex2D(_FoamNormalTex, (v + d * time * speed) * _FoamSize));

下記のコードより、上方を示す法線(geo_normal)と泡の法線(foam_normal)を泡の強度(foam_factor)によって線形補間をすることで、泡の強度に応じた法線を求めています。

foam_normal = lerp(geo_normal, foam_normal, foam_factor);

波ごとに法線を求めBlendNormal_rnmによって法線を順に合成することで最終的な泡の法線を求めています。

normal = BlendNormal_rnm(geo_normal, normal, foam_normal);
normal = normalize(normal);
float3 BlendNormal_rnm(float3 g_n, float3 n1, float3 n2)
{
	float4 q = float4(cross(g_n, n1), dot(g_n, n1) + 1.0);
	q /= sqrt(2.0 * (dot(g_n, n1) + 1.0));
	float3 r = n2 * (q.w - dot(q.xyz, q.xzy)) + 2 * q.xyz * dot(q.xyz, n2) + 2 * q.w * cross(q.xyz, n2);
	return r;
}

さらに、上方を示す法線(tangent_space_geo_normal )と合成した泡の法線を最終的に求まった泡の強度(foam)で線形補間することで、泡の法線を求めています。

float foam_normal_strength = _FoamNormalStrength;
float3 tangent_space_geo_normal = float3(0.0, 0.0, 1.0);
foam_normal = lerp(tangent_space_geo_normal, foam_normal, foam * foam_normal_strength);
foam_normal = normalize(foam_normal);

ライティング

 求めた泡にハーフランバート反射を追加することで陰影をつけています。

float FoamColor(float3 world_pos, float3 normal, float4 proj_coord, float foam){
	float3 lightDir = normalize(UnityWorldSpaceLightDir(world_pos));
	float3 viewDir = normalize(UnityWorldSpaceViewDir(world_pos));
	
	float diff = saturate(dot(normal, lightDir)) * 0.5 + 0.5;
	diff *= diff;
	diff = diff * _LightColor0;
	
	return foam * diff;
}

実行結果

 波の高さによって求めた泡を追加すると以下のような結果が得られます。

波の法線により泡を生成

 波の高さを用いて泡を生成すると、波の高い部分のみに泡が発生します。波の低い部分にも泡を発生させるために、波の法線を用いて泡の生成を行います。 上方を示すベクトルと波の法線の内積を求め、その値から波が急になっている部分を求め、この結果にテクスチャを乗算することで泡を生成します。

float Foam(float3 v, float time, float3 wave_normal, out float3 foam_normal, float wave_height, int wave_number){
	//foam(height base)
	・・・
	//foam(normal base)
	float3 world_space_geo_normal = float3(0.0, 1.0, 0.0);
	float n_dot_up = dot(wave_normal, world_space_geo_normal) * 0.5 + 0.5;
	float foam_factor = (1.0 - smoothstep(0.9, 1.0, n_dot_up))  * _NormalFoamStrength;
	float3 foam_tex = tex2D(_FoamTex, v.xz * _FoamSize);
	foam += foam_factor * foam_tex;
	foam = saturate(foam);

	//calculate foam normal
	float3 foam_normal_s = UnpackNormal(tex2D(_FoamNormalTex, v.xz * _FoamSize));
	foam_normal_s = lerp(tangent_space_geo_normal, foam_normal_s, foam_factor * foam_normal_strength);
	foam_normal_s = normalize(foam_normal_s);
	foam_normal = BlendNormal_rnm(tangent_space_geo_normal, foam_normal, foam_normal_s);

	return foam;
}

泡の生成箇所の計算

 波上方を示すベクトル(world_space_geo_normal)と波の法線(wave_normal)の内積を求めます。

float3 world_space_geo_normal = float3(0.0, 1.0, 0.0);
float n_dot_up = dot(wave_normal, world_space_geo_normal) * 0.5 + 0.5;

この結果を用いて

float foam_factor = (1.0 - smoothstep(0.9, 1.0, n_dot_up))  * _NormalFoamStrength;

と処理することで波が急になっている部分を求めています。この結果にテクスチャを乗算することで泡を生成しています。さらに、波の高さにより求めた泡を加算することで最終的な泡を求めています。

float3 foam_tex = tex2D(_FoamTex, v.xz * _FoamSize);
foam += foam_factor * foam_tex;
foam = saturate(foam);

法線の計算

 ノーマルマップより法線を取り出し、上方を示す法線(tangent_space_geo_normal)と泡の法線(foam_normal_s )を求めた泡の強度(foam_factor)で線形補間することで泡の法線を求めています。さらに、この法線と波の高さによって求めた泡の法線を合成することで最終的な泡の法線を求めています。

float3 foam_normal_s = UnpackNormal(tex2D(_FoamNormalTex, v.xz * _FoamSize));
foam_normal_s = lerp(tangent_space_geo_normal, foam_normal_s, foam_factor * foam_normal_strength);
foam_normal_s = normalize(foam_normal_s);
foam_normal = BlendNormal_rnm(tangent_space_geo_normal, foam_normal, foam_normal_s);

実行結果

波の法線より生成した泡は以下のようになります。

波の高さにより生成した泡と波の法線より生成した泡を組み合わせると以下の結果が得られます。

オブジェクトの周りに発生する泡

 海面に接するオブジェクトの周りに発生する泡を追加しました。

float DepthFoam(float4 grab_pos, float4 scr_pos, float3 geo_pos, out float3 foam_normal){
	float4 dist_uv = grab_pos;
	float surf_depth = UNITY_Z_0_FAR_FROM_CLIPSPACE(scr_pos.z);
	float ref_fix = LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(dist_uv))));
	float depth_diff = (ref_fix - surf_depth);
	
	float2 fuv = geo_pos.xz * _DepthFoamSize;
	float scroll = _Time.x * _FlowSpeed;
	float c_a = tex2D(_DepthFoamTex, fuv - float2(scroll, cos(fuv.x))).r;
	float c_b = tex2D(_DepthFoamTex, fuv * 0.5 + float2(sin(fuv.y), scroll)).b;
	float mask = (c_a + c_b) * 0.95;
	mask = saturate(mask * mask);
	
	float fa = 0;
	if(depth_diff < _EdgeWidth * _EdgeFalloff){
		fa = depth_diff / (_EdgeWidth * _EdgeFalloff);
		mask *= fa;
	}
	float falloff = 1.0 - saturate(depth_diff / _EdgeWidth);

	float depth_foam = saturate(falloff - mask);
	depth_foam *= tex2D(_DetailDepthFoamTex, geo_pos.xz * _DetailDepthFoamSize);

	float3 tangent_space_geo_normal = float3(0.0, 0.0, 1.0);
	foam_normal = UnpackNormal(tex2D(_DetailDepthFoamNormalTex, geo_pos.xz * _DetailDepthFoamSize));
	foam_normal = lerp(tangent_space_geo_normal, foam_normal, depth_foam * _FoamNormalStrength);
	foam_normal = normalize(foam_normal);

	return depth_foam;
}

泡の生成箇所の計算

 以下のコードでオブジェクトと海面の深度差を計算しています。

float4 dist_uv = grab_pos;
float surf_depth = UNITY_Z_0_FAR_FROM_CLIPSPACE(scr_pos.z);
float ref_fix = LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(dist_uv))));
float depth_diff = (ref_fix - surf_depth);

この深度差を用いて泡の生成箇所を計算しています ( fire-face:Water breakdown ) 。この結果に、泡のテクスチャを乗算することでオブジェクト周りに泡を生成しています。

float2 fuv = geo_pos.xz * _DepthFoamSize;
float scroll = _Time.x * _FlowSpeed;
float c_a = tex2D(_DepthFoamTex, fuv - float2(scroll, cos(fuv.x))).r;
float c_b = tex2D(_DepthFoamTex, fuv * 0.5 + float2(sin(fuv.y), scroll)).b;
float mask = (c_a + c_b) * 0.95;
mask = saturate(mask * mask);
	
float fa = 0;
if(depth_diff < _EdgeWidth * _EdgeFalloff){
	fa = depth_diff / (_EdgeWidth * _EdgeFalloff);
	mask *= fa;
}
float falloff = 1.0 - saturate(depth_diff / _EdgeWidth);

float depth_foam = saturate(falloff - mask);
depth_foam *= tex2D(_DetailDepthFoamTex, geo_pos.xz * _DetailDepthFoamSize);

法線の計算

 今までの方法と同様に、上方を示す法線(tangent_space_geo_normal)と泡のノーマルマップより取り出した法線(foam_normal)を泡の強度(depth_foam * _FoamNormalStrength)で線形補完することで泡の法線を求めています。

float3 tangent_space_geo_normal = float3(0.0, 0.0, 1.0);
foam_normal = UnpackNormal(tex2D(_DetailDepthFoamNormalTex, geo_pos.xz * _DetailDepthFoamSize));
foam_normal = lerp(tangent_space_geo_normal, foam_normal, depth_foam * _FoamNormalStrength);
foam_normal = normalize(foam_normal);

実行結果

 実行すると、以下のようにオブジェクト周りに泡を発生させることができます。

シェーダ

 作成したシェーダは以下の通りです。

OceanFoam.shader

Shader "Ocean/OceanFoam"
{
    Properties
    {
		_MainTex ("Texture", 2D) = "gray" {}

		_WaveSpeed("Value", Float) = 1.0

		[Header(OceanColor)]
		_SeaBaseColor ("SeaBaseColor", Color) = (0, 0, 0, 1)
		_SeaShallowColor ("SeaShallowColor", Color) = (0, 0, 0, 1)
		_BaseColorStrength ("Base Color Strength", Range(0, 2.0)) = 1.32
		_ShallowColorStrength ("Shallow Color Strength", Range(0, 1.0)) = 0.36
		_ColorHightOffset ("Color Hight Offset", Range(0, 1.0)) = 0.15

		[Header(SkyColor)]
		_SkyColor ("SkyColor", Color) = (0, 0, 0, 1)

		[Header(GerstnerWave)]
		_Amplitude ("Amplitude", Vector) = (0.78, 0.81, 0.6, 0.27)
		_Frequency ("Frequency", Vector) = (0.16, 0.18, 0.21, 0.27)
		_Steepness ("Steepness", Vector) = (1.70, 1.60, 1.20, 1.80)
		_Speed ("Speed", Vector) = (24, 40, 48, 60)
		_Noise ("Noise", Vector) = (0.39, 0.31, 0.27, 0.57) 
		_DirectionA ("Wave A(X,Y) and B(Z,W)", Vector) = (0.35, 0.31, 0.08, 0.60)
		_DirectionB ("C(X,Y) and D(Z,W)", Vector) = (-0.95, -0.74, 0.7, -0.5)

		_Amplitude2 ("Amplitude", Vector) = (0.17, 0.12, 0.21, 0.06)
		_Frequency2 ("Frequency", Vector) = (0.7, 0.84, 0.54, 0.80)
		_Steepness2 ("Steepness", Vector) = (1.56, 2.18, 2.80, 1.90)
		_Speed2 ("Speed", Vector) = (32, 40, 48, 60)
		_Noise2 ("Noise", Vector) = (0.33, 0.81, 0.39, 0.45) 
		_DirectionC ("Wave A(X,Y) and B(Z,W)", Vector) = (0.7, 0.6, 0.10, 0.38)
		_DirectionD ("C(X,Y) and D(Z,W)", Vector) = (0.43, 0.07, 0.42, 0.61)

		_NoiseSizeLerp("Noise Size", Range(0, 0.5)) = 0.5
		_NoiseStrength("Noise Strength", Range(0, 5)) = 1.26

		_Shininess ("Shininess", Range(0 ,5)) = 0.15

		[Header(Refraction)]
		_Distortion ("Distortion", Range(-0.5, 0.5)) = 0.5
		_RefractionDepth ("Refraction Depth", Range(0.01, 0.2)) = 0.065
		_SilhouetteDepth ("Silhouette Depth", Range(0.0, 0.1)) = 0.0386

		[Header(Reflection)]
		[Toggle]
		_PlanarReflection ("PlanarReflection", Int) = 0
		_ReflectionTex ("ReflectionTexture", 2D) = "white" {}
		_BumpScale ("BunpScale", Range(0.0, 20.0)) = 10.0

		[Header(Foam)]
		[Toggle]
		_Foam ("Enable", Int) = 1
        _FoamTex ("Foam Texture", 2D) = "white" {}
		_FoamNormalTex ("Foam Normal Texture", 2D) = "black" {}
        _FoamSize ("Foam Size", Range(0.001, 0.3)) = 0.2
		_HeightFoamStrength ("Foam Srength(Height Base)", Range(0.0, 2)) = 0.2
		_FoamHeightOffset ("Foam Height Offset", Range(0.0, 10.0)) = 1.35
		_NormalFoamStrength ("Foam Strength(Normal Base)", Range(0.0, 1.5)) = 0.5
		_FoamNormalStrength ("Foam Normal Strength", Range(0.0, 10.0)) = 1.5

		[Header(DepthFoam)]
		[Toggle]
		_DepthFoam ("Enable", Int) = 1
		_DepthFoamTex ("Depth Foam Texture", 2D) = "white" {}
		_DepthFoamSize ("Depth Foam Size", Range(0.01, 0.2)) = 0.12
		_DetailDepthFoamTex ("Detail Depth Foam Texture", 2D) = "white" {}
		_DetailDepthFoamNormalTex ("Detail Depth Foam Normal Texture", 2D) = "black" {}
		_DetailDepthFoamSize ("Detail Depth Foam Size", Range(0.01, 0.2)) = 0.095
		_FlowSpeed ("Foam Flow Speed", Range(0.001, 1.0)) = 0.5
		_EdgeWidth ("WaterEdgeWidth", Range(0, 10)) = 7.0
		_EdgeFalloff ("EdgeFalloff", Range(0, 1.0)) = 0.386
    }
    SubShader
    {
		Tags { "RenderType"="Transparent" "Queue"="Transparent"}

		GrabPass{ "_GrabTex" }

        Pass
        {
 			Tags { "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
			
			#pragma shader_feature _PLANARREFLECTION_ON
			#pragma shader_feature _FOAM_ON
			#pragma shader_feature _DEPTHFOAM_ON

            #include "UnityCG.cginc"
			#include "Assets/Shaders/Ocean.cginc"

            struct appdata
            {
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
            };

            struct v2f
            {
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
				float3 world_pos : TEXCOORD1;
				float4 proj_coord : TEXCOORD2;
				float4 scrPos : TEXCOORD3;
				float4 grabPos : TEXCOORD4;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

			float _WaveTime;

			static const int wave_number = 8;
			static const int count = 4;

            v2f vert (appdata v)
            {
				v2f o;
				float4 vt = v.vertex;
				float4 world_pos = mul(unity_ObjectToWorld, vt);
				o.world_pos = world_pos.xyz;

				float time = _Time.x * _WaveSpeed;

				float3 p = 0.0;
				for(int i = 0; i < count; i++){
					p += GerstnerWave(amp[i], freq[i], steep[i], speed[i], noise_size[i], dir[i], world_pos.xz, time, i);
				}
				for(int j = wave_number - count; j < wave_number; j++){
					p += GerstnerWave_Cross(amp[j], freq[j], steep[j], speed[j], noise_size[j], dir[j], world_pos.xz, time, j);
				}
				world_pos.xyz += p;

				vt = mul(unity_WorldToObject, world_pos);

				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.vertex = UnityObjectToClipPos(vt);
				o.proj_coord = ComputeScreenPos(o.vertex);
				o.grabPos = ComputeGrabScreenPos(o.vertex);
				o.scrPos = ComputeScreenPos(o.vertex);
				o.normal = v.normal;
				o.tangent = v.tangent;

				return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
				//CalcNormal
				float3 world_pos = i.world_pos;
				float3 geo_pos = world_pos;

				float time = _Time.x * _WaveSpeed;

				float3 p = 0.0;
				float3 pb = float3(0.05, 0.0, 0.0);
				float3 pt =float3(0.0, 0.0, 0.05);
				float3 v_bi = world_pos.xyz + float3(0.05, 0.0, 0.0);
				float3 v_tan = world_pos.xyz + float3(0.0, 0.0, 0.05);
				for(int m = 0; m < count; m++){
					p += GerstnerWave(amp[m], freq[m], steep[m], speed[m], noise_size[m], dir[m], world_pos.xz, time, m);
					pb += GerstnerWave(amp[m], freq[m], steep[m], speed[m], noise_size[m], dir[m], v_bi.xz, time, m);
					pt += GerstnerWave(amp[m], freq[m], steep[m], speed[m], noise_size[m], dir[m], v_tan.xz, time, m);
				}
				for(int n = wave_number - count; n < wave_number; n++){
					p += GerstnerWave_Cross(amp[n], freq[n], steep[n], speed[n], noise_size[n], dir[n], world_pos.xz, time, n);
					pb += GerstnerWave_Cross(amp[n], freq[n], steep[n], speed[n], noise_size[n], dir[n], v_bi.xz, time, n);
					pt += GerstnerWave_Cross(amp[n], freq[n], steep[n], speed[n], noise_size[n], dir[n], v_tan.xz, time, n);
				}
				world_pos += p;
				float3 normal = normalize(cross(pt - p, pb - p));
				float wave_height = world_pos.y - geo_pos.y;

				float4 grab_pos = i.grabPos;
				float4 scr_pos = i.scrPos;
				float4 proj_coord = i.proj_coord;

				/* foam */
				float foam = 0.0;
				float3 foam_normal = float3(0.0, 1.0, 0.0);
				float3 geo_normal = i.normal;
				float4 tangent = i.tangent;
				#ifdef _FOAM_ON
					foam = Foam(geo_pos, time, normal, foam_normal, wave_height, wave_number);
					foam_normal = TangentToWorldNormal(foam_normal, geo_normal, tangent);
					foam = FoamColor(world_pos, foam_normal, proj_coord, foam);
				#endif

				/* depth foam */
				float depth_foam = 0.0;
				float3 depth_foam_normal;
				#ifdef _DEPTHFOAM_ON
					depth_foam = DepthFoam(grab_pos, scr_pos, geo_pos, depth_foam_normal);
					depth_foam_normal = TangentToWorldNormal(depth_foam_normal, geo_normal, tangent);
					depth_foam = FoamColor(world_pos, depth_foam_normal, proj_coord, depth_foam);
					foam += depth_foam;
				#endif

				float3 result = OceanColor(world_pos, wave_height, normal, proj_coord);
				result = RefractionColor(result, grab_pos, scr_pos, normal);
				result += foam;

                fixed4 col = fixed4(result, 1.0);
                return col;
            }
            ENDCG
        }
    }
}

Ocean.cginc

#include "Noise.cginc"
float _NoiseStrength;
float _NoiseSizeLerp;

float4 _Amplitude;
float4 _Frequency;
float4 _Steepness;
float4 _Speed;
float4 _Noise;
float4 _DirectionA;
float4 _DirectionB;
float4 _FoamSpeed;

float4 _Amplitude2;
float4 _Frequency2;
float4 _Steepness2;
float4 _Speed2;
float4 _Noise2;
float4 _DirectionC;
float4 _DirectionD;
float4 _FoamSpeed2;

static const float amp[8] = {_Amplitude.x, _Amplitude.y, _Amplitude.z, _Amplitude.w, _Amplitude2.x, _Amplitude2.y, _Amplitude2.z, _Amplitude2.w};
static const float freq[8] = {_Frequency.x, _Frequency.y, _Frequency.z, _Frequency.w, _Frequency2.x, _Frequency2.y, _Frequency2.z, _Frequency2.w};
static const float steep[8] = {_Steepness.x, _Steepness.y, _Steepness.z, _Steepness.w, _Steepness2.x, _Steepness2.y, _Steepness2.z, _Steepness2.w};
static const float speed[8] = {_Speed.x, _Speed.y, _Speed.z, _Speed.w, _Speed2.x, _Speed2.y, _Speed2.z, _Speed2.w};
static const float2 dir[8] = {_DirectionA.xy, _DirectionA.zw, _DirectionB.xy, _DirectionB.zw, _DirectionC.xy, _DirectionC.zw, _DirectionD.xy, _DirectionD.zw};
static const float noise_size[8] = {_Noise.x, _Noise.y, _Noise.z, _Noise.w, _Noise2.x, _Noise2.y, _Noise2.z, _Noise2.w};
static const float foam_speed[8] = {_FoamSpeed.x, _FoamSpeed.y, _FoamSpeed.z, _FoamSpeed.w, _FoamSpeed2.x, _FoamSpeed2.y, _FoamSpeed2.z, _FoamSpeed2.w};

float4 _SeaBaseColor;
float4 _SeaShallowColor;
float _SeaColorStrength;

float _BaseColorStrength;
float _ShallowColorStrength;
float _ColorHightOffset;

float _WaveSpeed;

float4 _LightColor0;
float _Shininess;

float _Distortion;
float _RefractionDepth;
float _SilhouetteDepth;

sampler2D _GrabTex;
float4 _GrabTex_ST;
float4 _GrabTex_TexelSize;

sampler2D _CameraDepthTexture;
float4 _CameraDepthTexture_ST;
float4 _CameraDepthTexture_TexelSize;

sampler2D _FoamTex;
float4 _FoamTex_ST;
float _FoamSize;
float2 _FoamDir;
float _HeightFoamStrength;
float _NormalFoamStrength;
float _FoamHeightOffset;

sampler2D _FoamNormalTex;
float4 _FoamNormalTex_ST;
float _FoamNormalStrength;

sampler2D _DepthFoamTex;
float4 _DepthFoamTex_ST;
float _DepthFoamSize;
sampler2D _DetailDepthFoamTex;
float4 _DetailDepthFoamTex_ST;
sampler2D _DetailDepthFoamNormalTex;
float4 _DetailDepthFoamNormalTex_ST;
float _DetailDepthFoamSize;
float _FlowSpeed;
float _EdgeWidth;
float _EdgeFalloff;

float3 GerstnerWave(float2 amp, float freq, float steep, float speed, float noise, float2 dir, float2 v, float time, int seed)
{
	float3 p;
	float2 d = normalize(dir.xy);
	float q = steep;

	seed *= 3;
	v +=  noise2(v * noise + time, seed) * _NoiseStrength;
	float f = dot(d, v) * freq + time * speed;
	p.xz = q * amp * d.xy * cos(f);
	p.y = amp * sin(f);

	return p;
}

float3 GerstnerWave_Cross(float2 amp, float freq, float steep, float speed, float noise, float2 dir, float2 v, float time, int seed)
{
	float3 p;
	float2 d = normalize(dir.xy);
	float q = steep;

	float noise_strength = _NoiseStrength;
	seed *= 3;

	float3 p1;
	float3 p2;
	float2 d1 = normalize(dir.xy);
	float2 d2 = float2(-d.y, d.x);

	float2 v1 = v + noise2(v * noise + time * d * 10.0, seed) * noise_strength;
	float2 v2 = v + noise2(v * noise + time * d * 10.0, seed + 12) * noise_strength;
	float2 f1 = dot(d1, v1) * freq + time * speed;
	float2 f2 = dot(d2, v2) * freq + time * speed;
	p1.xz = q * amp * d1.xy * cos(f1);
	p1.y = amp * sin(f1);
	p2.xz = q * amp * d2.xy * cos(f2);
	p2.y = amp * sin(f2);

	p = lerp(p1, p2, noise2(v * _NoiseSizeLerp + time, seed) * 0.5 + 0.5);
	return p;
}

int _PlanarReflection;
sampler2D _ReflectionTex;
float4 _ReflectionTex_ST;
float _BumpScale;
float4 _SkyColor;

float3 GetSkyColor(float3 dir, float3 c){
	dir.y = max(0.0, dir.y);
	float et = 1.0 - dir.y;
	return (1.0 - c) * et + c;
}
			
float3 Reflection(float4 proj_coord, float3 normal, float3 reflect_dir)
{
	float3 sea_reflect_color;
	proj_coord.xyz += normalize(float3(normal.xy * _BumpScale, normal.z));

	#ifdef _PLANARREFLECTION_ON
		sea_reflect_color = tex2Dproj(_ReflectionTex, UNITY_PROJ_COORD(proj_coord));
		sea_reflect_color = GetSkyColor(reflect_dir, sea_reflect_color);
	#else
		sea_reflect_color = GetSkyColor(reflect_dir, _SkyColor);
	#endif

	return sea_reflect_color;
}

float2 AlignWithGrabTexel (float2 uv)
{
	return (floor(uv * _CameraDepthTexture_TexelSize.zw) + 0.5) * abs(_CameraDepthTexture_TexelSize.xy);
}

float3 RefractionColor(float3 base_color, float4 grab_pos, float4 scr_pos, float3 normal)
{
	float4 depth_uv = grab_pos;
	float2 uv_offset = normal.xy * _Distortion;

	depth_uv.xy = grab_pos.xy + uv_offset;
	float surf_depth = UNITY_Z_0_FAR_FROM_CLIPSPACE(scr_pos.z);
	float ref_fix = LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(depth_uv))));
	float depth_diff = saturate(ref_fix - surf_depth);

	float2 uv = grab_pos.xy + uv_offset * depth_diff;
	float2 grab_uv = AlignWithGrabTexel(uv / grab_pos.w);
		
	float3 refraction_color = tex2D(_GrabTex, grab_uv);

	depth_uv.xy = uv;
	float ref_fix_2 = LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(depth_uv))));
	float diff = ref_fix_2 - surf_depth;

	float ref_depth = _RefractionDepth;
	float grad_1 = saturate(diff * (ref_depth + _SilhouetteDepth));
	float grad_2 = saturate(diff * ref_depth);
	float3 col = lerp(refraction_color, _SeaBaseColor, grad_1);
	
	return lerp(col, base_color, grad_2);
}

float3 OceanColor(float3 world_pos, float wave_height, float3 normal, float4 proj_coord){
	//lighting
	float3 lightDir = normalize(UnityWorldSpaceLightDir(world_pos));
	float3 viewDir = normalize(UnityWorldSpaceViewDir(world_pos));
	float3 halfDir = normalize(lightDir + viewDir);
	
	//fresnel
	float r = 0.02;
	float facing = saturate(1.0 - dot(normal, viewDir));
	float fresnel = r + (1.0 - r) * pow(facing, 5.0);
	float3 reflectDir = reflect(-viewDir, normal);
	
	float diff = saturate(dot(normal, lightDir)) * _LightColor0;
	//float spec = pow(max(0.0, dot(normal, halfDir)), _Shininess * 128.0) * _LightColor0;	//Blinn-Phong
	
	//https://www.gamedev.net/articles/programming/graphics/rendering-water-as-a-post-process-effect-r2642/
	float dotSpec = saturate(dot(reflectDir, lightDir) * 0.5 + 0.5);
	float spec = (1.0 - fresnel) * saturate(lightDir.y) * pow(dotSpec, 512.0) * (_Shininess * 1.8 + 0.2);
	spec += spec * 25.0 * saturate(_Shininess - 0.05) * _LightColor0;
	
	//reflection
	float3 sea_reflect_color = Reflection(proj_coord, normal, reflectDir);

	//original
	float3 sea_base_color = _SeaBaseColor * diff * _BaseColorStrength + lerp(_SeaBaseColor, _SeaShallowColor * _ShallowColorStrength, diff);
	float3 water_color = lerp(sea_base_color, sea_reflect_color, fresnel);
	float3 sea_color = water_color + _SeaShallowColor * (wave_height * 0.5 + 0.2) * _ColorHightOffset;

	//return sea_color + spec;
	return sea_color;
}

float3 BlendNormal_rnm(float3 g_n, float3 n1, float3 n2)
{
	float4 q = float4(cross(g_n, n1), dot(g_n, n1) + 1.0);
	q /= sqrt(2.0 * (dot(g_n, n1) + 1.0));
	float3 r = n2 * (q.w - dot(q.xyz, q.xzy)) + 2 * q.xyz * dot(q.xyz, n2) + 2 * q.w * cross(q.xyz, n2);
	return r;
}

float4x4 TangentMatrix(float3 tan, float3 bin, float3 nor)
{
	return float4x4(
		float4(tan, 0),
		float4(bin, 0),
		float4(nor, 0),
		float4(0, 0, 0, 1)
	); 
}

float3 TangentToWorldNormal(float3 target_normal, float3 normal, float4 tangent){
	float3 binormal = cross(normal, tangent.xyz) * tangent.w;
	target_normal = mul(target_normal, TangentMatrix(tangent.xyz, binormal, normal));
	target_normal = UnityObjectToWorldNormal(target_normal);

	return target_normal;
}

float3 TangentToWorldNormal2(float3 target_normal, float3x3 tangent_matrix){
	target_normal = mul(target_normal, tangent_matrix);
	target_normal = UnityObjectToWorldNormal(target_normal);

	return target_normal;
}

float HightFoam(float2 amp, float freq, float steep, float speed, float noise, float f_speed, float2 dir, float2 v, float time, int seed, inout float3 normal, float wave_height)
{
	float2 d = normalize(dir.xy);
	float q = steep;
	seed *= 3;

	float2 v_n = v + noise2(v * noise + time, seed) * _NoiseStrength;

	float f = dot(d, v_n) * freq + time * speed;

	float foam_fade = cos(freq + f_speed * time) * 0.5 + 0.5;
	foam_fade = 1.0;
	
	float crest = sin(f);

	float h_0 = 0.5;
	float h_max = _FoamHeightOffset;
	float foam_factor = saturate((crest - h_0) / (h_max - h_0));
	float foam = wave_height * foam_factor * _HeightFoamStrength;

	float3 geo_normal = float3(0.0, 0.0, 1.0);
	float3 foam_normal = UnpackNormal(tex2D(_FoamNormalTex, (v + d * time * speed) * _FoamSize));
	foam_normal = lerp(geo_normal, foam_normal, foam_factor);
	normal = BlendNormal_rnm(geo_normal, normal, foam_normal);
	normal = normalize(normal);

	float foam_tex = tex2D(_FoamTex, (v + d * time * speed) * _FoamSize);

	return saturate(foam * foam_tex * foam_fade);
}


float Foam(float3 v, float time, float3 wave_normal, out float3 foam_normal, float wave_height, int wave_number){
	//foam(height base)
	float foam = 0.0;
	foam_normal = 0.0;
	for(int n = 0; n < wave_number; n++){
		foam += HightFoam(amp[n], freq[n], steep[n], speed[n], noise_size[n], foam_speed[n], dir[n], v.xz, time, n, foam_normal, wave_height);
	}
	foam = saturate(foam);

	float foam_normal_strength = _FoamNormalStrength;
	float3 tangent_space_geo_normal = float3(0.0, 0.0, 1.0);
	foam_normal = lerp(tangent_space_geo_normal, foam_normal, foam * foam_normal_strength);
	foam_normal = normalize(foam_normal);

	//foam(normal base)
	float3 world_space_geo_normal = float3(0.0, 1.0, 0.0);
	float n_dot_up = dot(wave_normal, world_space_geo_normal) * 0.5 + 0.5;
	float foam_factor = (1.0 - smoothstep(0.9, 1.0, n_dot_up))  * _NormalFoamStrength;
	float3 foam_tex = tex2D(_FoamTex, v.xz * _FoamSize);
	foam += foam_factor * foam_tex;
	foam = saturate(foam);

	//calculate foam normal
	float3 foam_normal_s = UnpackNormal(tex2D(_FoamNormalTex, v.xz * _FoamSize));
	foam_normal_s = lerp(tangent_space_geo_normal, foam_normal_s, foam_factor * foam_normal_strength);
	foam_normal_s = normalize(foam_normal_s);
	foam_normal = BlendNormal_rnm(tangent_space_geo_normal, foam_normal, foam_normal_s);

	return foam;
}

float DepthFoam(float4 grab_pos, float4 scr_pos, float3 geo_pos, out float3 foam_normal){
	float4 dist_uv = grab_pos;
	float surf_depth = UNITY_Z_0_FAR_FROM_CLIPSPACE(scr_pos.z);
	float ref_fix = LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(dist_uv))));
	float depth_diff = (ref_fix - surf_depth);
	
	float2 fuv = geo_pos.xz * _DepthFoamSize;
	float scroll = _Time.x * _FlowSpeed;
	float c_a = tex2D(_DepthFoamTex, fuv - float2(scroll, cos(fuv.x))).r;
	float c_b = tex2D(_DepthFoamTex, fuv * 0.5 + float2(sin(fuv.y), scroll)).b;
	float mask = (c_a + c_b) * 0.95;
	mask = saturate(mask * mask);
	
	float fa = 0;
	if(depth_diff < _EdgeWidth * _EdgeFalloff){
		fa = depth_diff / (_EdgeWidth * _EdgeFalloff);
		mask *= fa;
	}
	float falloff = 1.0 - saturate(depth_diff / _EdgeWidth);

	float depth_foam = saturate(falloff - mask);
	depth_foam *= tex2D(_DetailDepthFoamTex, geo_pos.xz * _DetailDepthFoamSize);

	float3 tangent_space_geo_normal = float3(0.0, 0.0, 1.0);
	foam_normal = UnpackNormal(tex2D(_DetailDepthFoamNormalTex, geo_pos.xz * _DetailDepthFoamSize));
	foam_normal = lerp(tangent_space_geo_normal, foam_normal, depth_foam * _FoamNormalStrength);
	foam_normal = normalize(foam_normal);

	return depth_foam;
}

float FoamColor(float3 world_pos, float3 normal, float4 proj_coord, float foam){
	//lighting
	float3 lightDir = normalize(UnityWorldSpaceLightDir(world_pos));
	float3 viewDir = normalize(UnityWorldSpaceViewDir(world_pos));
	
	float diff = saturate(dot(normal, lightDir)) * 0.5 + 0.5;
	diff *= diff;
	diff = diff * _LightColor0;
	
	return foam * diff;
}

Noise.cginc

float2 rand2d(float2 st, int seed)
{
	float2 s = float2(dot(st, float2(127.1, 311.7)) + seed, dot(st, float2(269.5, 183.3)) + seed);
	return -1.0 + 2.0 * frac(sin(s) * 43758.5453123);
}

float noise2(float2 st, int seed)
{
	float2 p = floor(st);
	float2 f = frac(st);

	float w00 = dot(rand2d(p, seed), f);
	float w10 = dot(rand2d(p + float2(1.0, 0.0), seed), f - float2(1.0, 0.0));
	float w01 = dot(rand2d(p + float2(0.0, 1.0), seed), f - float2(0.0, 1.0));
	float w11 = dot(rand2d(p + float2(1.0, 1.0), seed), f - float2(1.0, 1.0));
	
	float2 u = f * f * (3.0 - 2.0 * f);

	return lerp(lerp(w00, w10, u.x), lerp(w01, w11, u.x), u.y);
}

float3 rand3d(float3 p, int seed)
{
	float3 s = float3(dot(p, float3(127.1, 311.7, 74.7)) + seed,
					  dot(p, float3(269.5, 183.3, 246.1)) + seed,
					  dot(p, float3(113.5, 271.9, 124.6)) + seed);
	return -1.0 + 2.0 * frac(sin(s) * 43758.5453123);
}

float noise3(float3 st, int seed)
{
	float3 p = floor(st);
	float3 f = frac(st);

	float w000 = dot(rand3d(p, seed), f);
	float w100 = dot(rand3d(p + float3(1, 0, 0), seed), f - float3(1, 0, 0));
	float w010 = dot(rand3d(p + float3(0, 1, 0), seed), f - float3(0, 1, 0));
	float w110 = dot(rand3d(p + float3(1, 1, 0), seed), f - float3(1, 1, 0));
	float w001 = dot(rand3d(p + float3(0, 0, 1), seed), f - float3(0, 0, 1));
	float w101 = dot(rand3d(p + float3(1, 0, 1), seed), f - float3(1, 0, 1));
	float w011 = dot(rand3d(p + float3(0, 1, 1), seed), f - float3(0, 1, 1));
	float w111 = dot(rand3d(p + float3(1, 1, 1), seed), f - float3(1, 1, 1));
	
	float3 u = f * f * (3.0 - 2.0 * f);

	float r1 = lerp(lerp(w000, w100, u.x), lerp(w010, w110, u.x), u.y);
	float r2 = lerp(lerp(w001, w101, u.x), lerp(w011, w111, u.x), u.y);

	return lerp(r1, r2, u.z);
}

float fbm(float2 st, int seed){
	float val = 0.0;
	float a = 0.5;

	for(int i = 0; i < 6; i++){
		val += a * noise2(st, seed);
		st *= 2.0;
		a *= 0.5;
	}
	return val;
}

テクスチャ

 この記事で使用したテクスチャは以下の通りです。

・泡テクスチャ(_FoamTex、_FoamNormalTex、_DetailDepthFoamTex、_DetailDepthFoamNormalTex)

Unity Asset Store:Foam Textures

・深度差による泡生成用テクスチャ(_DepthFoamTex)

参考サイト

GPU Gems 2:Chapter 18. Using Vertex Texture Displacement for Realistic Water Rendering

fire-face:Water breakdown

Self Shadow :Blending in Detail