Cube Ocean海洋水立方实现方式

制作一个水立方。用代码生成mesh。可自定义水体的配色,尺寸,浪高,流速,方向,等等。水面物体跟随波浪浮动。
首先看看最终的demo:

  • 按住鼠标右键可以旋转camera
  • 右边的菜单可以自定义海水立方的参数
代码生成海水的mesh:

生成海面和四边的剖面

  • 为了让海面起伏时,海面和侧截面的mesh无缝衔接,我直接把它做成一整块mesh。
  • 海面和侧截面的材质不同,所以要给mesh分2个材质。Unity里通过设置subtriangles属性来拆分次级网格。Subtriangles的数量对应材质球的数量和ID。例如把海面的subtriangles的ID设成0,把4个侧面的设成1,然后mesh renderer里填2个对应的材质球,就OK了。

海面动态起伏效果实现

  • 做法1:用shader实现,在vert阶段控制顶点偏移。优点是用gpu计算省性能
  • 做法2:代码直接控制mesh的vertices坐标实现。虽然有点耗性能,但是我们后面需要计算出海面上物体的高度坐标,所以我就先这个做法。但缺点是很吃CPU,我把尺寸限定在50×50以内,再大了就跑不动了。
  • 除了海面高低起伏,还增加了一些自定义的内容:海浪高度,平移速度,海浪移动方向。

海面上的对象跟随海浪起伏

  • 海面上的物体的高度坐标根据其所相对应位置的顶点高度计算得到。我用个简单的做法:通过坐标位置算出相对的顶点位置。
  • 海面物体还要随着波浪摇摆晃动。通过上述顶点高度再算出晃动幅度,并带有一定的随机参数。
  • 这里注意:我原本打算用顶点的法线方向来算的,但是mesh的法线信息光是读取都超级吃CPU,根本不能拿来用。
海面材质制作
第一个pass制作正面
  • 首先用个面向camera的cubemap来做出基础的立体感。
  • 再用fresnel做出逆光rim。这样配合波浪起伏,就已经有水体的感觉了。
  • 用被遮挡物体的深度值做出水下物体渐渐模糊的效果。
  • 用上述的深度值再做出边缘泡沫的效果。再通过混合2张uv滚动贴图做出泡沫随机飘散效果。
  • 通过顶点坐标的Y轴高度,做出海浪高的时候,浪尖产生泡沫的效果。泡沫飞散刚好用上一步里两张uv滚动贴图混合的值。
第二个pass制作海面反面
  • 向上仰视的时候会看到漂浮物一半在水中一半在水面上。所以要做出水面上景物扭曲的效果。做法是单独用个pass来渲染海面的反面,将grabpass贴图做个随机扭曲效果。
  • 通过posWorld与camera坐标的距离差值,可以算出海面相对于camera的距离深度。用这个做出海面扭曲影子越远颜色越淡的效果。
  • 最终海面的颜色输出成白底,是为了后面配合海水侧面材质制作。后面直接用侧面的颜色乘以Grab pass获得的这个白底颜色。
海水侧边剖面材质制作

  • 首先加一个斜上方受光的cubemap增加水立方的立体感,并且叠上蓝色。
  • 要体现出海水越远越看不清的效果。通过被遮挡对象的深度值,做出水中物体越深越看不清的效果。我让物体越靠近水面颜色越深,越远越接近水体颜色,比较符合水中光影效果。
  • 为了做出水中物体模糊的效果,再把grabpass贴图高斯模糊一下。不过我用的高斯模糊算法用了2次循环计算,webgl不支持,所以Demo里没有显示出来。

  • 通过顶点的世界坐标y轴,让水立方越往下方颜色越暗。
  • 这里有个麻烦的问题:由于海面材质是当做透明物体渲染的,没有写入深度,grabpass无法获得海面自身的深度遮挡信息,所以grabpass贴图中显示的是完整的漂浮物,没有被裁切掉水面的部分,而且我们是隔着侧面海水看海面反面的,即要使用被遮挡对象的深度做出海中物体渐变的效果,还要考虑到渲染排序,这里的计算有点费脑子。我最终通过stencil把“水面材质的反面”和“侧面材质的反面”分别记录在2个stencil ref ID里,然后在侧面的材质里用2个pass分别绘制出两部分最终的颜色。

 

水中物体材质制作
  • 首先注意水中物体不能是unlit材质,因为只有受光物体才能产生深度值用来做水下渐渐模糊的效果。所以要在shader里使用“LIGHT_ATTENUATION”,同时必须要有一盏平行光主光源。
  • 受光材质不一定要使用diffuse效果。我们可以输出自己想要的颜色。例如以自定义的emission颜色输出为主。
自定义海水颜色
以上材质基本就做完了,可以改改材质颜色做出几种不同风格的水立方。