Shaderで波の表現
前回の続きです。
【Unity(Shader)】Shader初心者が送る水面表現①(屈折)
前回は屈折表現を勉強したので、今回は波のゆらゆらする感じを再現します。
正直なところ、まだ理解できておらずいろいろ試して仮説立てて書いているので、
あまり参考にしない方がいいです。
むしろ、解釈が違ってたりしてたら教えてくださ~いって感じのノリです。
デモ
屈折表現と良い感じにシナジーを発揮して、波のゆらゆら感が際立ちました。
わかりやすいようにかなり早めに動かしてます。
Shader
今回も先駆者様のコードを参考に、どういう仕組みになっているのか理解していきます。
【参考リンク】:【Unity】【シェーダ】シェーダで頂点を変形させて波打つ水面を作る
Shader"Wave"{Properties{_DistortionTex("Distortion Texture(RG)",2D)="grey"{}_Color("WaterColor",Color)=(0,0,0,0)_DistortionPower("Distortion Power",Range(0,1))=0_ScrollSpeed("Scroll Speed",Range(0,0.5))=0.01_Frequency("Frequency ",Range(0,3))=1_Amplitude("Amplitude",Range(0,1))=0.5_WaveSpeed("WaveSpeed",Range(0,20))=10}SubShader{Tags{"Queue"="Transparent""RenderType"="Transparent"}CullBackZWriteOnZTestLEqualColorMaskRGBGrabPass{"_GrabPassTexture"}Pass{CGPROGRAM#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
structappdata{half4vertex:POSITION;half4texcoord:TEXCOORD0;half4normal:NORMAL;};structv2f{half4vertex:SV_POSITION;half2uv:TEXCOORD0;half4grabPos:TEXCOORD1;};sampler2D_DistortionTex;half4_DistortionTex_ST;sampler2D_GrabPassTexture;half4_Color;half_DistortionPower;half_ScrollSpeed;float_Frequency;float_Amplitude;float_WaveSpeed;v2fvert(appdatav){v2fo=(v2f)0;float2factors=_Time.x*_WaveSpeed+v.vertex.xz*_Frequency;float2offsetYFactors=(sin(factors)*_Amplitude);v.vertex.y+=offsetYFactors.x+offsetYFactors.y;o.vertex=UnityObjectToClipPos(v.vertex);o.uv=TRANSFORM_TEX(v.texcoord,_DistortionTex);o.grabPos=ComputeGrabScreenPos(o.vertex);returno;}fixed4frag(v2fi):SV_Target{// w除算half2uv=half2(i.grabPos.x/i.grabPos.w,i.grabPos.y/i.grabPos.w);// Distortionの値に応じてサンプリングするUVをずらすhalf2distortion=UnpackNormal(tex2D(_DistortionTex,i.uv+_Time*_ScrollSpeed)).rg;distortion*=_DistortionPower;uv+=distortion;half4refraction=tex2D(_GrabPassTexture,uv);refraction*=_Color;returnrefraction;}ENDCG}}}
頂点を上下に動かす
波をうねうねさせるには頂点を動かす必要があります。
GIFのような状態はそれぞれの頂点が上下(Y軸方向に)に動くことで再現できます。
ただ、各頂点が上下に動くだけではGIFのようにはなりません。
このように、ただの板が上下するだけです。
そこで、それぞれの頂点を異なるタイミングで上下させます。
【参考リンク】:【Unityシェーダ入門】シェーダで旗や水面をなびかせる
下記箇所でv.vertex.xz
を乗算しているのはそのためです。
この計算によりfactors
にはそれぞれの頂点のxz座標をベースにした計算結果が入ります。
それぞれの頂点のxz座標を演算の要素に含むことで、
各頂点の座標の差異を利用して異なる値を作りだすことができました。
float2factors=_Time.x*_WaveSpeed+v.vertex.xz*_Frequency;float2offsetYFactors=sin(factors)*_Amplitude;
利用する座標を限定してみる
もっとわかりやすくするためにx座標、z座標のみを演算の要素として盛り込んだパターンを見てみます。
それぞれ、利用した座標軸方向のみで異なる動きをしているのがわかるかと思います。
x座標のみ利用
float2factors=_Time.x*_WaveSpeed+v.vertex.x*_Frequency;float2offsetYFactors=sin(factors)*_Amplitude;
z座標のみ利用
float2factors=_Time.x*_WaveSpeed+v.vertex.z*_Frequency;float2offsetYFactors=sin(factors)*_Amplitude;
float2をfloatにぶち込んだ時
強い人にフィードバック頂いたので加筆します。
下記コードでふと疑問に思いました。v.vertex.y += offsetYFactors.x + offsetYFactors.y;
v.vertex.y += offsetYFactors;
となぜしていないのか?という疑問です。
offsetYFactors
はfloat2で値を2つ持ってるから、
加算代入したらその値の両方を加算代入できるはず!
と思いましたが、よくよく考えたらそんなことできるはずありませんでした。
実際に試してみると、x座標のみ利用した場合と同じ挙動になりました。
これは、内部でfloat2→floatの変換を勝手に行ってくれているそうで、
下記と同様の結果になるそうです。v.vertex.y += offsetYFactors.x;
まとめ
最初、shaderを見たとき、下記箇所の意味がわからず途方に暮れていました。v.vertex.y += offsetYFactors.x + offsetYFactors.y;
"あくまでも各頂点は上下に繰り返し移動しているだけで、offsetYFactors.x + offsetYFactors.y
の箇所はタイミングをずらすためのコードである"
と理解してスッキリしました。
次回は反射の表現も付け加えてみたいです。(重そう)