頂点シェーダによる変形により波打つ水面のような形を作ってみます。
波の形を作る
まず波の形を作ります。
x座標とz座標をsin関数で変換した値をy座標のオフセットとすることでそれらしくなります。
Shader "VertexWave" { SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; // x座標とz座標に応じて-1~1の連続した値を計算する float offsetY = sin(v.vertex.x) + sin(v.vertex.z); // その値をy座標に足す v.vertex.y += offsetY; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = 1; return col; } ENDCG } } }
波の細かさと大きさを調整する
次に波の細かさと大きさを調整するためのプロパティを作ります。
Shader "VertexWave" { Properties { _Frequency("Frequency ", Range(0, 3)) = 1 _Amplitude("Amplitude", Range(0, 1)) = 0.5 } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float _Frequency; float _Amplitude; v2f vert (appdata v) { v2f o; // _Frequencyが大きいほど細かい波になる(ポリゴン数が足りてないとおかしくなるけど) float offsetY = sin(v.vertex.x * _Frequency) + sin(v.vertex.z * _Frequency); // 振幅の値を乗算する offsetY *= _Amplitude; v.vertex.y += offsetY; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = 1; return col; } ENDCG } } }
説明はコメントの通りです。
結果は下図のようになります。
アニメーションさせる
次にこれを時間に応じてアニメーションさせます。
Shader "VertexWave" { Properties { _Speed("Speed ",Range(0, 100)) = 1 _Frequency("Frequency ", Range(0, 3)) = 1 _Amplitude("Amplitude", Range(0, 1)) = 0.5 } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float _Speed; float _Frequency; float _Amplitude; v2f vert (appdata v) { v2f o; // 時間によって波が移動するように float time = _Time * _Speed; float offsetY = sin(time + v.vertex.x * _Frequency) + sin(time + v.vertex.z * _Frequency); offsetY *= _Amplitude; v.vertex.y += offsetY; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = 1; return col; } ENDCG } } }
時間に応じて波が移動していきます。
結果は次のようになります。
法線を補正する
ここまでの実装では法線は初期状態のまま(Y軸正方向を向いたまま)です。
頂点の盛り上がりに応じて法線もそれらしく補正してみます。
Shader "VertexWave" { Properties { _Speed("Speed ",Range(0, 100)) = 1 _Frequency("Frequency ", Range(0, 3)) = 1 _Amplitude("Amplitude", Range(0, 1)) = 0.5 } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float4 normal : NORMAL; }; struct v2f { float4 vertex : SV_POSITION; }; float _Speed; float _Frequency; float _Amplitude; v2f vert (appdata v) { v2f o; float time = _Time * _Speed; float2 factors = time + v.vertex.xz * _Frequency; float offsetY = sin(factors.x) * _Amplitude + sin(factors.y) * _Amplitude; v.vertex.y += offsetY; o.vertex = UnityObjectToClipPos(v.vertex); // 法線を補正 float2 normalOffsets = -cos(factors) * _Amplitude; v.normal.xyz = normalize(half3(normalOffsets.x, 1, normalOffsets.y)); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = 1; return col; } ENDCG } } }
最適化
ここで、ここまでの処理を最適化します。
Shader "VertexWave" { Properties { _Speed("Speed ",Range(0, 100)) = 1 _Frequency("Frequency ", Range(0, 3)) = 1 _Amplitude("Amplitude", Range(0, 1)) = 0.5 } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float4 normal : NORMAL; }; struct v2f { float4 vertex : SV_POSITION; }; float _Speed; float _Frequency; float _Amplitude; v2f vert (appdata v) { v2f o; float2 factors = _Time.x * _Speed + v.vertex.xz * _Frequency; float2 offsetYFactors = sin(factors) * _Amplitude; v.vertex.y += offsetYFactors.x + offsetYFactors.y; o.vertex = UnityObjectToClipPos(v.vertex); // 法線を補正 float2 normalOffsets = -cos(factors) * _Amplitude; v.normal.xyz = normalize(half3(normalOffsets.x, 1, normalOffsets.y)); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = 1; return col; } ENDCG } } }
上と下に色を付ける
最後に高さに応じて色を付けてみます。
Shader "VertexWave" { Properties { _TopColor("Top Color", Color) = (1, 1, 1, 1) _BottomColor("Bottom Color", Color) = (1, 1, 1, 1) _Speed("Speed ",Range(0, 100)) = 1 _Frequency("Frequency ", Range(0, 3)) = 1 _Amplitude("Amplitude", Range(0, 1)) = 0.5 } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; float offsetRate : TEXCOORD0; }; float4 _TopColor; float4 _BottomColor; float _Speed; float _Frequency; float _Amplitude; v2f vert (appdata v) { v2f o; float time = _Time * _Speed; float offsetY = sin(time + v.vertex.x * _Frequency) + sin(time + v.vertex.z * _Frequency); o.offsetRate = offsetY * 0.5 + 0.5; offsetY *= _Amplitude; v.vertex.y += offsetY; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = lerp(_BottomColor, _TopColor, i.offsetRate); return col; } ENDCG } } }
結果は下図のようになります。
うまく設定すれば白波のような表現ができるかもしれません。