2D中水纹的实现
发表于2018-04-19
最近需要做一个Unity的项目,2D的,需要实现水的效果,这在3D中资料很多,随便找个Shader就能用了,但是对于2D资料却非常少,表示非常伤心。找到个Unity实现2D水纹的package,可见How to do 2d water ripple?
链接里的工程用到了一个前辈用java实现的水纹效果的思想,具体理论来自2D Water
当然,个人对于是否在项目中使用这种算法是有疑虑的,对于并不需要模拟非常真实的水面的需求来说,这种大量的计算很消耗性能,如果用CPU计算,低端安卓手机的帧率连10帧/s都达不到,GPU的话还没测试,但比较传统的实现方式,这种通过大量计算来实现效果的方案应该还是比较耗费性能的。
这个算法是如何实现的?(来自翻译部分)
首先,我们需要2列整形数组(不是字节)(感觉翻译成2维数组更好一点,理解算法应该用2维数组),用来记录水的状态。一个记录当前状态,一个记录上一帧的状态。为什么要用到两个队列呢?因为我们需要知道谁是如何从上一帧变化到这一帧的。
//阻尼:0和1之间的数 damping = some non-integer between 0 and 1 begin loop //对不是边上的每个元素进行操作 for every non-edge element: loop Buffer2(x, y) = (Buffer1(x-1,y) + Buffer1(x+1,y) + Buffer1(x,y+1) + Buffer1(x,y-1)) / 2 - Buffer2(x,y) Buffer2(x,y) = Buffer2(x,y) * damping end loop Display Buffer2 Swap the buffers end loop
Velocity(x, y) = -Buffer2(x, y)
想象一个播在一个一维表面传播,传播到左边。竖直方向的小箭头表明水位随时间变化的速度。淡的波显示了之前帧的位置,我们怎么才能海阔的每个波(竖直箭头)高度的正确变化呢?
你可以注意到两帧前的波的高度和箭头的大小成正比。因此只要有前两帧的记录,就很容易计算出波的每一部分的高度变化。我们来看下代码,当循环开始的时候,缓冲器1包含前一帧水的状态(wave1),缓冲器2则包含更前一帧的状态,因此缓冲器2有波的垂直速度信息。
波的传播也很重要,所以每一帧都得对缓冲进行处理以使其平滑。
Smoothed(x,y) = (Buffer1(x-1, y) + Buffer1(x+1, y) + Buffer1(x, y-1) + Buffer1(x, y+1)) / 4
现在,用这两个缓冲来计算新产生的水的高度。乘以2减少了速度的效果。
NewHeight(x,y) = Smoothed(x,y)*2 + Velocity(x,y)
最后,水纹是会损耗能量的,所以需要增加阻尼:
NewHeight(x,y) = NewHeight(x,y) * damping
注意:当n是2的幂的时候
优化后得到:
NewHeight(x,y) = NewHeight(x,y) - (NewHeight(x,y)/n)
所以,当需要整合的时候。应该用一些比较快的代码,这在C的内循环中是很重要的:
void ProcessWater(short *source, short *dest) { int i; for (i=320; i<64000-320; i++) { dest[i] = ( ((source[i-1]+ source[i+1]+ source[i-320]+ source[i+320]) >>1) ) -dest[i]; dest[i] -= (dest[i] >> 5); } }
渲染水
缓冲器包含每个像素的水的高度的信息,有很多方法可以用来选人一个高度域,在这个例子中,我们使用简单高效的方法:利用阴影和反射。你需要一张水下的纹理,所以你能看到反射。
for every pixel (x,y) in the buffer Xoffset = buffer(x-1, y) - buffer(x+1, y) Yoffset = buffer(x, y-1) - buffer(x, y+1) Shading = Xoffset t = texture(x+Xoffset, y+Yoffset) p = t + Shading plot pixel at (x,y) with colour p end loop
**附:**Unity Assets Store中有个利用贴图实现水波效果的插件不错,手机上也能完美运行Easy Water
来自:https://blog.csdn.net/rickshaozhiheng/article/details/50890257