海洋シェーダ(波の形)
以前、ゲルストナー波(Gerstner Wave)についての記事を掲載しました。このゲルストナー波を用いて海の波をシェーダで作成しました。
目次
ゲルストナー波(8個)
shader
単純にゲルストナー波を単純に8個組み合わせるシェーダです。法線は近接する頂点を利用して計算しています。また、ランバート反射によって波の凹凸が分かるように処理をしています。
Shader "Ocean/Gerstner_Geo_8_Waves"
{
Properties
{
_MainTex ("Texture", 2D) = "gray" {}
_WaveSpeed("Wave Speed", Float) = 0.0
[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)
_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)
_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)
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Assets/Shaders/Ocean.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
static const int wave_number = 8;
v2f vert (appdata v)
{
v2f o;
float4 vt = v.vertex;
float4 worldPos = mul(unity_ObjectToWorld, vt);
o.worldPos = worldPos.xyz;
float time = _Time.x * _WaveSpeed;
float3 p = 0.0;
for(int i = 0; i < wave_number; i++){
p += GerstnerWave(amp[i], freq[i], steep[i], speed[i], dir[i], worldPos.xz, time, i);
}
worldPos.xyz += p;
vt = mul(unity_WorldToObject, worldPos);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.vertex = UnityObjectToClipPos(vt);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//CalcNormal
float3 worldPos = i.worldPos;
float3 geo_pos = worldPos;
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 = worldPos.xyz + float3(0.05, 0.0, 0.0);
float3 v_tan = worldPos.xyz + float3(0.0, 0.0, 0.05);
for(int m = 0; m < wave_number; m++){
p += GerstnerWave(amp[m], freq[m], steep[m], speed[m], dir[m], worldPos.xz, time, m);
pb += GerstnerWave(amp[m], freq[m], steep[m], speed[m], dir[m], v_bi.xz, time, m);
pt += GerstnerWave(amp[m], freq[m], steep[m], speed[m], dir[m], v_tan.xz, time, m);
}
worldPos += p;
float3 dist = worldPos - geo_pos;
float3 normal = normalize(cross(pt - p, pb - p));
//lighting
float3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float3 halfDir = normalize(lightDir + viewDir);
float diff = saturate(dot(normal, lightDir)) * _LightColor0;
float3 result = diff;
fixed4 col = fixed4(result, 1.0);
return col;
}
ENDCG
}
}
}
Ocean.cginc
float4 _Amplitude;
float4 _Frequency;
float4 _Steepness;
float4 _Speed;
float4 _DirectionA;
float4 _DirectionB;
float4 _FoamSpeed;
float4 _Amplitude2;
float4 _Frequency2;
float4 _Steepness2;
float4 _Speed2;
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};
float _WaveSpeed;
float4 _LightColor0;
float3 GerstnerWave(float2 amp, float freq, float steep, float speed, float2 dir, float2 v, float time, int seed)
{
float3 p;
float2 d = normalize(dir.xy);
float q = steep;
seed *= 3;
float f = dot(d, v) * freq + time * speed;
p.xz = q * amp * d.xy * cos(f);
p.y = amp * sin(f);
return p;
}
実行結果
実行結果は以下の通りです。同じような模様が繰り返し現れており、あまり海らしくありません。
直行するゲルストナー波を加える
全てのゲルストナー波に直行する波を加える
shader
ゲルストナー波を単純に組み合わせるだけでなく、直行するゲルストナー波を発生させ、組み合わせてみました。
・・・
for(int i = 0; i < wave_number; i++){
p += GerstnerWave_Cross(amp[i], freq[i], steep[i], speed[i], dir[i], worldPos.xz, time, i);
}
・・・
Ocean.cginc
・・・
float3 GerstnerWave_Cross(float2 amp, float freq, float steep, float speed, float2 dir, float2 v, float time, int seed)
{
float3 p;
float2 d = normalize(dir.xy);
float q = steep;
seed *= 3;
float3 p1;
float3 p2;
float2 d1 = normalize(dir.xy);
float2 d2 = float2(-d.y, d.x);
float2 f1 = dot(d1, v) * freq + time * speed;
float2 f2 = dot(d2, v) * 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, 0.5);
return p;
}
実行結果
実行結果は以下の通りです。先ほどより良くなりましたが、波を16個も合成するのは多すぎるので、以下のように、直行するゲルストナー波を半分に減らしてみました。
直行するゲルストナー波を減らす
shader
先ほどのシェーダでは全てのゲルストナー波に対して直行する波を発生させていましたが、振幅の大きい4個のゲールトナー波はそのまま、残りのゲルストナー波に直行するゲルストナー波を発生させ組み合わせました。つまり、合計で12個のゲルストナー波を発生させています。
・・・
int count = 4;
for(int i = 0; i < count; i++){
p += GerstnerWave(amp[i], freq[i], steep[i], speed[i], dir[i], worldPos.xz, time, i);
}
for(int j = wave_number - count; j < wave_number; j++){
p += GerstnerWave_Cross(amp[j], freq[j], steep[j], speed[j], dir[j], worldPos.xz, time, j);
}
・・・
実行結果
実行結果は以下の通りです。 波の数は16個から12個へ減りましたが、見た目はほとんど変わっていません。
ノイズによる歪みの追加
shader
ノイズによってゲルストナー波を計算する座標を移動させることで、細かい凹凸を追加しました。
Shader "Ocean/Gerstner_Geo_Noise_4_Waves_4_Cross_Waves"
{
Properties
{
_MainTex ("Texture", 2D) = "gray" {}
_WaveSpeed("Wave Speed", Float) = 0.0
[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
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Assets/Shaders/Ocean.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
static const int wave_number = 8;
v2f vert (appdata v)
{
v2f o;
float4 vt = v.vertex;
float4 worldPos = mul(unity_ObjectToWorld, vt);
o.worldPos = worldPos.xyz;
float time = _Time.x * _WaveSpeed;
float3 p = 0.0;
int count = 4;
for(int i = 0; i < count; i++){
p += GerstnerWave(amp[i], freq[i], steep[i], speed[i], noise_size[i], dir[i], worldPos.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], worldPos.xz, time, j);
}
//*/
worldPos.xyz += p;
vt = mul(unity_WorldToObject, worldPos);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.vertex = UnityObjectToClipPos(vt);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//CalcNormal
float3 worldPos = i.worldPos;
float3 geo_pos = worldPos;
float time = _Time.x * _WaveSpeed;
//time = 1.55;
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 = worldPos.xyz + float3(0.05, 0.0, 0.0);
float3 v_tan = worldPos.xyz + float3(0.0, 0.0, 0.05);
int count = 4;
for(int m = 0; m < count; m++){
p += GerstnerWave(amp[m], freq[m], steep[m], speed[m], noise_size[m], dir[m], worldPos.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], worldPos.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);
}
//*/
worldPos += p;
float3 dist = worldPos - geo_pos;
float3 normal = normalize(cross(pt - p, pb - p));
//lighting
float3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float3 halfDir = normalize(lightDir + viewDir);
float diff = saturate(dot(normal, lightDir)) * _LightColor0;
float3 result = diff;
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 _Amplitude2;
float4 _Frequency2;
float4 _Steepness2;
float4 _Speed2;
float4 _Noise2;
float4 _DirectionC;
float4 _DirectionD;
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};
float _WaveSpeed;
float4 _LightColor0;
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);
//p = lerp(p1, p2, 0.5);
return p;
}
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;
}
実行結果
実行結果は以下の通りです。 ノイズによって細かい凹凸が追加され、より海らしくなりました。
参考サイト
GPU Gems:Chapter 1. Effective Water Simulation from Physical Models
-
前の記事
キャラクターの移動(Input.GetAxisRaw) 2019.12.01
-
次の記事
海洋シェーダ(海の色) 2020.01.19




コメントを書く