赞
踩
转载自:https://blog.csdn.net/changbaolong/article/details/13172079
http://blog.sina.com.cn/s/blog_6f638fb60100shw0.html
SPH的流体模拟是目前大多数游戏所采用的模拟流体方法,特点是简单,十分容易实现,相比与基于Grid的Eulerian方法更加简单和高速,本文主要介绍一下使用SPH的流体模拟中一些常用的技巧和数据结构。
目前流体模拟中常用的2类方法, 分别代表了从2种不同的方面来解释Navier-Stokes的流体方程:
1、Eulerian方法从空间固定点观察该点的值得变化。
2、Lagrangian方法则将液体看作是跟随着流动的Particle。
Eulerian方法比较复杂,常用作离线模拟,可以产生非常逼真的流体效果,具体可以参考SIGGRAPH 2007的《FLUID SIMULATION》,作者是Bridson 和 Muller。
现在游戏中常用的实时流体模拟通常使用Lagrangian的方法来模拟流体,也就是基于粒子的方法,而大多数采用的都是基于SPH(Smooth Particle hydrodynamic)的方法。主要参考文章为Muller的《Particle-Based Fluid Simulation for Interactive Applications》。
SPH方法非常简单和容易实现,从我自己做过的刚体和柔体模拟的经验来看,可以说基于SPH的流体比刚体和柔体的模拟简单和容易实现的多。SPH流体模拟主要包括3部分:
1、水分子模拟
2、水面信息抽取
3、水体渲染
其中第一部分,第三部分比较简单,但是好的快速的效果需要有良好、真实地水面信息抽取算法支持。Marching Cube算法可以通过加大网格精度来不断提高视觉效果,速度却得不到保障,所以需要一个更加出色的算法来解决,因此如何开发一个高效、真实的适用于流体的Surface Extraction算法会是个很大的挑战。
(一) SPH方法概括的说:
1:对每个粒子,通过它附近周围的所有和它的距离半径小于r的所有粒子来计算出该粒子点的密度r被称为Smooth Length,通俗的说就是每个粒子的最大影响半径。
2:对于每一个粒子,通过理想气体状态方程根据上一步计算出的密度从而算出它的压强。
3:对于每一个粒子,根据它附近周围距离半径小于r的所有粒子的压强(上一步已经计算出)来计算出该粒子由于附近压强差而导致受到的压力差。
4:对于每一个粒子根据它附近周围距离半径小于r的所有粒子的速度差来计算出该粒子由于附近速度差而导致受到的粘滞力。
注: (1)对于第一步求解密度来说,其中r代表粒子i和粒子j之间的距离,h代表前面说的Smooth Length
(2)对于第二步来说使用公式 其中 是粒子的密度 是流体的稳定密度比如水的话一般取1000kg/立方米。 (3) 第3,4步也可以依次类推。具体请参考Muller的文章《Particle-Based Fluid Simulation for Interactive Applications》。实现中的常用技巧
1、推导Smooth Kernel函数的梯度和拉普拉辛算子
注意,根据上面一篇文章的SPH方法,需要推导Smooth Kernel函数的梯度和拉普拉辛算子。但Muller在文章中只给出了3个Kernel(一个计算密度,一个计算压强差力,一个计算粘滞力)函数,并没有帮我们推导出后2个函数的梯度公式和拉普拉辛算子公式。一开始我还花时间自己推导了下,还反复检查生怕推导错了(以前大学学数学我的粗心导致1,2个符号错误是常有的事情)。后来发现《SPH survival kit》这片小文章里已经帮你都推导好了。。。汗。所以你只要照抄就可以了。
2、空间Grid的数据结构
为了快速定位一个粒子周围的例子,我使用了空间Grid的数据结构,通常的说就是将空间等分成尺寸为h的立方格,这样对于一个粒子来说只要查找周围3X3X3的立方格中所包含的粒子就可以了。
为了实现无限大范围的Grid系统,我使用了空间稀疏哈希的方法来存储Grid,这是因为空间大部分Grid都是空着的所以使用Hash表存储和快速定位那些包含粒子的Grid,具体可以参考《Optimized Spatial Hashing for Collision Detection of Deformable Objects》。
每对粒子之间的力只需要计算一次就可以了(这样提高2倍的速度),也就是说对于粒子A,B来说。A粒子由于受到B粒子作用的压强力分量,和B粒子由于受到 A粒子作用的压强力分量是相同的只差一个正负号而已,为了避免计算A和B所受的作用力时重复计算,我在每一桢开始模拟前,首先构造每个粒子的邻接粒子列表:同时要保证A和B的邻接关系要么只出现在A的邻接列表中,要么只出现在B的邻接列表中。具体做法很简单,首先清空Hash Grid中每个Grid中的粒子信息,然后对于每一个粒子首先查找周围27个邻接Grid中的粒子,将这些粒子中和自己的距离长度小于Kernel Smooth Length的那些例子加入自己的邻接列表,最后将自己加入到自己所在的Grid中,这样就能保证A和B的邻接关系出现并且只出现一次(将会出现在后面的那个粒子的邻接列表中,因为对于前一个粒子来说,在统计它的邻接粒子的时候,后一个粒子还没有被加入到Hash Grid中~~)。
实际上Muller给出的那些公式中有不少可以提取公因式的部分,你可以仔细去研究下会发现很多可以优化速度的地方。
(二)水面信息抽取
上面的步骤只是完成了一个粒子系统的模拟,这样你看到的还只是一堆水分子,在正式渲染的时候需要通过第一步计算出的密度场信息来构造出水表面。我这里也是采用Muller论文里的Marching Cube算法,这个算法简单容易实现,但是效率极低,我模拟1500个粒子在一台Core2 4400的Intel cpu上可以达到150fps,一旦经过Marching Cube算法后一下子掉到52fps.所以不大推荐这种方法,我个人看到过一片比较好的文章是《Interactive Water Streams with Sphere Scan Conversion》,感觉挺不错,作者号称速度比Marching Cube快了很多倍。不过我自己也没有时间去做。
Marching Cube算法的论文<<Marching Cubes:A High Resolutin 3D Surface Construction Algorithm>>,注意论文中提到的构造一个顶点信息构造表,这个表你可以自己写程序构造。。但是更加方便的技巧是你去下载一个叫做 “PolyVox”的开源体素构造库,它里面已经帮你把表格生成好了。
(三)水体渲染
基本上需要模拟的包括水对于背景的折射效果,水面的Specular ,以及菲涅尔效果。其中前2项是最重要的,折射我使用的方法是先渲染水体以外的所以其他场景物体,然后将该Back Buffer拷到一张渲染纹理上,然后在渲染水的时候在PS里面对这张纹理进行一些扭曲(根据法向量)。最后将像素点写回back buffer,同时根据计算出的菲涅尔系数将像素点的颜色值和反射颜色值进行融合。(这个折射方法是同事教的,正好他在做画面扭曲效果。。)Specular的话和一般的Specular计算方法相同。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。