海洋シェーダ(海の色)
前回の記事(海洋シェーダ(波の形))では、ゲルストナー波をノイズで変形させることで 海洋における波の形を作成しました。この波に、フレネル反射等を追加することによって海の色を表現します。
基本の色
海の基本となる色(_SeaBaseColor)にランバート反射で凹凸を付けています。また、波の高さ(wave_height)によって色を変えるために、_SeaShallowColor * wave_height * 0.5 + 0.2) * _ColorHightOffsetを加算しています。
1 2 3 4 |
float3 lightDir = normalize(UnityWorldSpaceLightDir(world_pos)); float diff = saturate(dot(normal, lightDir)) * _LightColor0; float sea_height = world_pos.y - geo_pos.y; sea_color = _SeaBaseColor * diff * _BaseColorStrength + _SeaShallowColor * (wave_height * 0.5 + 0.2) * _ColorHightOffset; |
これを実行すると以下のような結果が得られます。各プロパティの値は次の通りです。
_SeaBaseColor(27, 57, 77)、 _SeaShallowColor (75, 89, 35)、_BaseColorStrength(1.5)、_ColorHightOffset(0.15)
フレネル反射
フレネル反射を追加しました。これには海が反射する空の色が必要となるので、以下のコードで反射ベクトルから空の色を計算しています。
1 2 3 4 5 6 7 8 9 |
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 viewDir = normalize(UnityWorldSpaceViewDir(world_pos)); float3 reflectDir = reflect(-viewDir, normal); float3 sea_reflect_color = GetSkyColor(reflectDir, _SkyColor); |
_SkyColor(0, 104, 255)としたとき、空の色は以下のようになります。
この結果をフレネル反射に利用します。
1 2 3 4 5 6 7 8 |
//fresnel float r = 0.02; float facing = saturate(1.0 - dot(normal, viewDir)); float fresnel = r + (1.0 - r) * pow(facing, 5.0); ・・・ float3 sea_base_color = _SeaBaseColor * diff * _BaseColorStrength; float3 water_color = lerp(sea_base_color, sea_reflect_color, fresnel); float3 sea_color = water_color + _SeaShallowColor * (wave_height * 0.5 + 0.2) * _ColorHightOffset; |
以下の結果が得られます。_BaseColorStrengthを1.5から1.2へ変更しています。
基本色の変更
_SeaBaseColorを変更しても、あまり良い結果が得られませんでした。そこで、基本色の計算式に_SeaShallowColorを加えた以下の計算方法に変更してみました。
1 2 3 |
・・・ float3 sea_base_color = _SeaBaseColor * diff * _BaseColorStrength + lerp(_SeaBaseColor, _SeaShallowColor * _ShallowColorStrength, diff); ・・・ |
実行すると以下のようになります。 _BaseColorStrengthを0.9、_ShallowColorStrengthを0.35としています。
反射光
海には反射光(スペキュラー)が生じます。そこで、フォン鏡面反射を追加してみました。
1 2 3 4 |
・・・ float3 halfDir = normalize(lightDir + viewDir); float spec = pow(max(0.0, dot(normal, halfDir)), _Shininess * 128.0) * _LightColor0; ・・・ |
結果は以下のようになります。
他の方法を探したところRendering Water as a Post-process Effectに別の方法が掲載されていたので試してみました。
1 2 3 4 5 |
・・・ 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; ・・・ |
結果は以下のようになります。
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 135 |
Shader "Ocean/OceanColor" { 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)) = 0.5 _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.27 } 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 world_pos : TEXCOORD1; float4 proj_coord : TEXCOORD5; }; sampler2D _MainTex; float4 _MainTex_ST; 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); 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; float3 result = OceanColor(world_pos, wave_height, normal, i.proj_coord); 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 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 |
#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}; float4 _SeaBaseColor; float4 _SeaShallowColor; float _SeaColorStrength; float _BaseColorStrength; float _ShallowColorStrength; float _ColorHightOffset; float _WaveSpeed; float4 _LightColor0; float _Shininess; 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; } 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 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 = GetSkyColor(reflectDir, _SkyColor); 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; } |
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; } |
参考サイト
-
前の記事
海洋シェーダ(波の形) 2020.01.12
-
次の記事
海洋シェーダ(反射と屈折) 2020.02.02
コメントを書く