2D中水纹的实现

发表于2018-04-19
评论0 2.1k浏览
最近需要做一个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

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引