海洋シェーダ(波の形)
以前、ゲルストナー波(Gerstner Wave)についての記事を掲載しました。このゲルストナー波を用いて海の波をシェーダで作成しました。
目次
ゲルストナー波(8個)
shader
単純にゲルストナー波を単純に8個組み合わせるシェーダです。法線は近接する頂点を利用して計算しています。また、ランバート反射によって波の凹凸が分かるように処理をしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
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
ゲルストナー波を単純に組み合わせるだけでなく、直行するゲルストナー波を発生させ、組み合わせてみました。
1 2 3 4 5 |
・・・ 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
・・・ 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個のゲルストナー波を発生させています。
1 2 3 4 5 6 7 8 9 |
・・・ 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
ノイズによってゲルストナー波を計算する座標を移動させることで、細かい凹凸を追加しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
#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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
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
コメントを書く