本篇文章是一个基于URP的可交互水面系统,实现了实时渲染水面扰动,使用了三缓冲RT交换用于实现水面波纹扩散效果。
上一篇文章实现了在无扰动理想情况下的程序化生成海面顶点数据,然而这种水面是在理想情况下,现实中水面不会平静也不会只有波浪,还有对系统施加外力情况下的水面扰动。比如,石子被投入水中、小船在水面航行等。这种情况下就不仅仅需要程序化计算顶点位置,还需要计算扰动并叠加。
代码分享:https://pan.baidu.com/s/1-VApXIrHxRjCthW2OYUMmA?pwd=9527
https://pan.baidu.com/s/1-VApXIrHxRjCthW2OYUMmA?pwd=9527代码学习、参考:神奇的小阿飞
https://space.bilibili.com/31839293/?spm_id_from=333.788.upinfo.detail.clickhttps://www.bilibili.com/video/BV1Xh411D7z7/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=e72728138018176c10ef41188bea0c35
https://www.bilibili.com/video/BV1Xh411D7z7/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=e72728138018176c10ef41188bea0c35
效果预览

代码分析
本项目包含四个文件:
- DrawShader.shader: 用于绘制鼠标输入点的水波扰动逻辑,以及扰动叠加逻辑
- RippleShader.shader: 用于处理水波扩散逻辑
- WaterShader.shader: 用于计算水质感
- Ripple.cs: 用于处理输入相关逻辑以及shader脚本调用逻辑
DrawShader.shader
着色器功能:在一张已有纹理上绘制一个可扩散的圆形涟漪效果。在_SourceTex上叠加一个"环形渐变"的亮度ripple,位置和半径由_Pos控制。
结构体:appdata------顶点着色器输入结构,v2f------顶点输出结构。
核心公式(一个平滑的环形函数):
float ripple = pow(saturate(0.05 - abs(length(uv - _Pos.xy) - _Pos.z)), 2.0);
叠加到原图:
return SAMPLE_TEXTURE2D(_SourceTex, sampler_SourceTex, uv).r + ripple * 0.8;
SAMPLE_TEXTURE2D(_SourceTex, sampler_SourceTex, uv).r采样源图并返回r通道值。
RippleShader.shader
着色器功能:二维波动方程(水波/高度场)离散求解器。
使用经典Ping-Pong Simulation双缓冲模拟,_CurrentTex:当前帧,_PreviousTex:上一帧,输出:下一帧
cs
half4 frag(v2f i) : SV_Target
{
float2 e = _CurrentTex_TexelSize.xy;
float2 uv = i.uv;
// 这里的 up/down/left/right 是相对于当前像素的四个邻居,而 center 是当前像素在上一帧的值
float up = SAMPLE_TEXTURE2D(_CurrentTex, sampler_CurrentTex, uv + float2(0, e.y)).r;
float down = SAMPLE_TEXTURE2D(_CurrentTex, sampler_CurrentTex, uv - float2(0, e.y)).r;
float left = SAMPLE_TEXTURE2D(_CurrentTex, sampler_CurrentTex, uv - float2(e.x, 0)).r;
float right = SAMPLE_TEXTURE2D(_CurrentTex, sampler_CurrentTex, uv + float2(e.x, 0)).r;
float center = SAMPLE_TEXTURE2D(_PreviousTex, sampler_PreviousTex, uv).r;
float c = (up + down + left + right) * 0.5f - center;
c *= 0.99f;
return c;
}
对当前像素的当前帧进行四领域采样,以及前一帧的中心像素采样,核心更新公式:
float c = (up + down + left + right) * 0.5f - center;
因为在波的传输过程中是有能量损失的,所以计算出来的下一帧波的高度应该有一定的损失程度,这里按每次损失1%,即c *= 0.99。
WaterShader.shader
着色器功能:用高度图(_RippleTex)驱动几何+法线+反射/折射,渲染动态水面。它做了三件事情:
- 顶点位移(水面波形):水面真实的"起伏"
- 法线重建(光照基础):根据高度图采样计算法线
- 光照(真实感来源):反射 + 折射 + 菲涅尔 + 高光
数据流:RippleTex(高度图) -> 顶点位移 -> 法线计算 -> 光照计算 -> 最终水面
在顶点着色器阶段不能使用函数SAMPLE_TEXTURE2D函数读取贴图,因为在这种情况下需要计算贴图的minmap,但是顶点阶段不能用自动LOD,所以会报错。这时候可以用SAMPLE_TEXTURE2D_LOD指定其minmap等级然后获取贴图。
这个shader就是普通的画面表现shader。
Ripple.cs
在Unity中实现一个基于高度场的水波模拟+鼠标交互扰动,实际上是一个在纹理上做物理模拟的系统,而不是直接操作网格。
使用三张RT做模拟,PreviousRT、CurrentRT、TempRT
包含功能:
- 鼠标点击产生扰动
- 每帧做波动传播
- 把结果给水材质显示