Unity Shader波纹效果

我们今天来模拟一下波纹效果,当一颗石头投入水面时,在水中会形成向外扩散的一圈波纹,本质上就是一个向四周扩散的波。根据我们日常生活的经验可以知道,当一个物体投入水中时,中心的振幅时比较大的,而随着波向边缘运动,振幅越来越小,而波的频率在中心总体时很小的,而在边缘时波频率很大。

那么我们可以先试着用正弦波来模拟。根据我们上述的波的性质,可以简单的将公式写为

 

 

 这样,一个以(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));

最终结果:

其实和水纹一点都不像,哈哈哈,主要是正弦波函数是随便一拍脑门拟合的,没去细究真实世界的波函数究竟是什么,但是这里提供这样一个思路,换一个波函数也可以解决~~

终于写完了,睡午觉~~~

posted @ 2020-12-19 13:10  syb7384  阅读(1046)  评论(0编辑  收藏  举报