效果:
如果是极具真实感的水波模拟需要包含以下等式中的全部或者一部分:
水效=波的模拟+折射+散射+反射+菲涅尔效应+高光
但是在真实感要求不高,可以用在Lowpoly或者其他动画风格的场景中的时候,水波的模拟就比较简单了
最简单的可以采用水面扰动来做,基本原理为:
采用一张水面波纹的贴图(UV移动动画)和一张噪声纹理(UV扰动扭曲),达到水波的初步模拟效果,关键代码如下(参考博客:点这里):
//顶点着色器
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.uv += float2(_XSpeed, _YSpeed)*_Time.y;
return o;
}
//片元着色器
fixed4 frag(v2f i) : SV_Target
{
// sample the texture
fixed4 bias = tex2D(_Noise, i.uv + _Time.xy*_distortFactorTime);
half4 color = tex2D(_MainTex, i.uv + bias.xy*_distortFactor);
return color * _Color;;
}
但需要注意的是,这样得到的水波:
1:没有起伏
2:与物体交界处过渡不自然
在此基础上,我们要对shader进行如下修改:
1:针对没有起伏的问题:使用Gerstner波来模拟水波
2:与物体交界处过渡不自然的问题:使用深度比较来制造渐变效果
使用Gerstner波来模拟水波
Gerstner波的公式:
A指波的振幅(波峰到水平面的高度)
ω指波的频率,一般波长T=2π/ω(波峰与波峰的距离)
φt指波的相位(波在X轴上移动的距离)
S指速度,可以写成φ=Sω=S2π/T
D指波在xz平面上的运动方向
Q指波的陡度,Q越大,波越陡
注意:Q值为(0,1),超过1则会造成环,等于0则是最平缓的波
环如下图所示:
关键代码如下(参考博客:点这里):
float4 _A;
float4 _S;
float4 _Dx;
float4 _Dz;
float4 _L;
float3 CalculateWavesDisplacement(float3 vert)
{
float PI = 3.141592f;
float3 pos = float3(0,0,0);
float4 w = 2*PI/_L;
float4 psi = _S*2*PI/_L;
float4 phase = w*_Dx*vert.x+w*_Dz*vert.z+psi*_Time.x;
float4 sinp=float4(0,0,0,0), cosp=float4(0,0,0,0);
sincos(phase, sinp, cosp)
pos.x = dot(_Q*_A*_Dx, cosp);
pos.z = dot(_Q*_A*_Dz, cosp);
pos.y = dot(_A, sinp);
return pos;
}
v2f vert (appdata v)
{
v2f o;
float3 worldPos = mul(_Object2World, v.vertex);
float3 disPos = CalculateWavesDisplacement(worldPos);
v.vertex.xyz = mul(_World2Object, float4(worldPos+disPos, 1));
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
使用深度比较来制造交界处的渐变效果:
原理:比较水的Z深度与物体的深度(前提是摄像机开启了深度模式并在shader中声明_CameraDepthTexture
)来计算深度差,物体与水面交界处的深度差为0,物体越往下深度差越大。然后对深度进行插值,来对应不同的颜色。
关键代码如下(参考unity内置粒子shader中使用深度差实现软粒子的例子):
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.screenPos = ComputeScreenPos(o.vertex);
COMPUTE_EYEDEPTH(o.eyeZ);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//fixed4 col = _Color.rgba;
//计算深度差
float screenZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)));
float halfWidth = _IntersectionWidth / 2;
float diff = saturate(abs(i.eyeZ - screenZ) / halfWidth);
fixed4 crossColor = lerp(_IntersectionColor, color, diff);//插值
fixed4 finalColor;
if (diff == 1)//避免颜色混合
finalColor = color;
else
finalColor = crossColor;
return finalColor;
}
最终我们就可以得到如上图所示的效果(虽然不好看,但是可以慢慢调试~)
最终源码:稍后上传