Flowmap in OpenGL | OpenGL中Flowmap水面

笔试的时候有这么一道题,讲模拟水面,用FlowMap。当时没有太懂,就随便回答的,正好这次图形学大作业有机会来实现一下。

FlowMap是用来解决水面流动方向单一问题的方法,SIGGRAPH2010上有篇文章讲的V社做Portal2时使用Flowmap的方法,见此。V社官网上也有一篇文章讲水面内容相似。他们做好场景以后用Houdini做了一个Flowmap,也是蛮厉害的。还有一篇博客,不过讲的不是很清楚,没说FlowMapOffset0和FlowMapOffset1怎么算。

注:以下代码是GLSL version 3.3

最朴素的scrolling diffuse map,就是根据时间改变uv坐标了。

当然这有些愚蠢,更真实一点的是scrolling normal map

这样就有一个bug,水流只能沿着特定的uv方向流动,没法改变流动方向。
解决的小trick叫FlowMap,就是一张贴图,用两个通道(比如RG,或者黑白)定义某处水流方向。

…略去大部分code了,未说明的变量定义同上文

但这样是有bug的,这个法线贴图在时间久了以后就会跪掉,出现奇怪的法线。 解决方法是让时间循环,比如这样:

这样法线贴图就不会出问题了,但又有了bug,水流的周期性太强,也就是一个周期后会跳跃变回周期开始的样子,水面会震颤一下。
FlowMap这里的trick是用两个normal map,都按着同样方向移动。因为两个normal map也都是有周期性问题的,所以我们混合一下它们就好了。
第二个normal map的周期比第一个晚半个周期,这样错动半个周期混合。

那个SIGGRAPH的文章给了这两张图讲的还是比较清楚的,前面提到的另一篇博客没有提两个法线贴图周期的相位差,让我悟了好久。

vlachos-siggraph10-waterflow
vlachos-siggraph10-waterflow2

当然做到这里水面流动的效果就有了,还需要做一下反射。skybox的反射好办,直接在shader里,用reflect算出光线方向采样一下skybox就好了。

对物体的反射稍微麻烦一点。有一套Youtube教程讲这个思路,简单说就是用RenderToTexture把反射和折射的部分渲染出来,然后放到shader里扰动制作一个假的反射效果。有关RTT有教程。另外还有一个讲水面的博客比较精髓,比如如何做clip plane,如何做反射。得到RenderTexture以后我直接参考了GPU Gem的Generic Refraction Simulation这一章写Shader。

首先带着截平面渲染,原理是用Ax + By + Cz + D = 0定义一个平面,然后直接 (x,y,z) · (A, B, C) + D > 0就能判断某点在这个平面上还是下。代码这个样子:

渲之前把clip_plane的参数传进来就行了,传(0,0,0,0)就是没有截平面。

之后渲染倒影也好办,直接换一个ModelMatrix,Y方向Scale -1就可以

然后渲染纹理,定义一个Framebuffer和两个texture绑定上

然后在渲染循环里

我这没有用stentil buffer,而是直接渲染了RGBA四个通道。。。。A通道就是stentil了,不知道性能有没有影响。

这俩texture传到水的shader里,采样要注意一下,要声明一下gl_FragCoord来计算屏幕空间坐标。然后要算一个texelSize来计算贴图坐标。然后这里rendertexture的扰动就直接用上面flowmap的法线xz分量了,所以比较粗略和假,然而效果凑合。

发表评论

电子邮件地址不会被公开。 必填项已用*标注