我们今天来模拟一下波纹效果,当一颗石头投入水面时,在水中会形成向外扩散的一圈波纹,本质上就是一个向四周扩散的波。根据我们日常生活的经验可以知道,当一个物体投入水中时,中心的振幅时比较大的,而随着波向边缘运动,振幅越来越小,而波的频率在中心总体时很小的,而在边缘时波频率很大。
那么我们可以先试着用正弦波来模拟。根据我们上述的波的性质,可以简单的将公式写为
这样,一个以(0,0,0)为中心点的正弦波就构造出来了,前面振幅之所以要在分母上加1是为了防止分母为0.
然后上代码:
Shader "Unlit/VertWave" { Properties { _MainTex ("Texture", 2D) = "white" {} _Color("Color",Color) = (1,1,1,1) _WaveIntensity("Intensity",float) = 0.2 _Speed("Speed",float) = 1 _Frequency("Frequency",Range(0.1,1)) = 1 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" #include "UnityLightingCommon.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal:NORMAL; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; float3 normalWS : TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; float _WaveIntensity; float _Speed; float _Frequency; half4 _Color; v2f vert (appdata v) { v2f o; float4 positionWS = mul(UNITY_MATRIX_M,v.vertex); float distanceSqr = dot(positionWS.xz,positionWS.xz); positionWS.y = _WaveIntensity*rcp(distanceSqr+1) * sin(_Frequency*distanceSqr -_Time.y*_Speed); o.vertex = UnityWorldToClipPos(positionWS); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.normalWS = UnityObjectToWorldNormal(v.normal); UNITY_TRANSFER_FOG(o,o.vertex); return o; } half4 frag (v2f i) : SV_Target { half4 col = tex2D(_MainTex, i.uv)*_Color; float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); float3 normalWS = normalize(i.normalWS); col.rgb *= _LightColor0.rgb*saturate(dot(normalWS,lightDir)); UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } } }
结果如下:
我们会看到因为只移动了顶点(不要直接用plane那个unity自带的网格,顶点数太少,出来的效果很丑,吼吼吼~),没有调整法线导致的光照一致,如果不是用了一张mainTex可能都看不出来里面有波在运动。所以我们接下来就修正法线。
法线怎么求呢?我们可以通过求每个点x向的切线和z向的切线,然后通过叉乘构造出法线。那么问题就回到了如何求切线。我们知道切线的斜率其实就是一个函数在某一点的导数,在3维空间中的偏导数。那么我们就开始求偏导数:
代码:
float distanceSqr = dot(positionWS.xz,positionWS.xz); positionWS.y = _WaveIntensity*rcp(distanceSqr+1) * sin(_Frequency*distanceSqr -_Time.y*_Speed); float3 xTangent = float3(1,0,0); xTangent.y = (2*positionWS.x*(distanceSqr+1)*cos(distanceSqr)-sin(distanceSqr))/((distanceSqr+1)*(distanceSqr+1)); float3 zTangent = float3(0,0,1); zTangent.y = (2*positionWS.z*(distanceSqr+1)*cos(distanceSqr)-sin(distanceSqr))/((distanceSqr+1)*(distanceSqr+1)); o.normalWS = normalize(cross(zTangent,xTangent));
效果如下:
gif图前半部分是法线错误的效果,后半部分是计算法线后的效果。
但是这个算出来的结果真的正确吗?别忘了我们还在shader里面公开了一些参数,这些参数的变化都会影响法线,也就是我们要把这些变动因素也要计入。
公式修改为:
代码修改为:
float3 xTangent = float3(1,0,0); xTangent.y = (2*_Frequency*positionWS.x*_WaveIntensity*(distanceSqr+1)*cos(distanceSqr)-sin(distanceSqr))/((distanceSqr+1)*(distanceSqr+1)); float3 zTangent = float3(0,0,1); zTangent.y = (2*_Frequency*positionWS.z*_WaveIntensity*(distanceSqr+1)*cos(distanceSqr)-sin(distanceSqr))/((distanceSqr+1)*(distanceSqr+1));
最终结果:
其实和水纹一点都不像,哈哈哈,主要是正弦波函数是随便一拍脑门拟合的,没去细究真实世界的波函数究竟是什么,但是这里提供这样一个思路,换一个波函数也可以解决~~
终于写完了,睡午觉~~~