计算机图形学 流体粒子系统

一、简介

此次作业主要实现了一个利用SPH算法计算的流体,交互方式主要采用鼠标键盘,另利用安卓手机的加速度传感器使用socket通信来控制,场景采用openmesh导入一个房间模型。

二、操作说明

电脑端:F1切换粒子显示模式(用point绘制,带纹理绘制,模型绘制),F2、F3控制镜头远近,F4切换控制流体容器转动模式(鼠标拖拽,手机加速度传感器控制),F5切换是否让房间跟随容器转动,F11、F12控制光照强度,wasd控制场景移动,上下左右键控制流体容器的移动。

安卓端:将电脑与手机连在同一wifi下,电脑端cmd使用ipconfig查询ipv4地址,将地址输入手机,点击连接按钮。(连接需在模型加载完毕后,命令行显示port 9400 listening后连接)

三、初始思路

在刚看到题目要求的粒子系统时,由于没有大的限制,一时无从下手。后来在考虑一段时间后突然想到可以实现一个水壶倒水,水落入水盆的场景,这样可以使用手机的陀螺仪来控制水壶的倾角,会很有意思,尤其是如果把水盆的盆面正对屏幕,则可以实现adobe宣传片中手机模仿洒水,电脑屏幕上出现反应的效果。

因此在考虑过后,预计实现需要完成三块内容:

1、水的粒子系统实现方式。

2、安卓手机的交互。

3、水的渲染。

在实现的过程中发现,由于sph算法非常精细复杂,对于算力的要求很高,在我的电脑上,没有加载场景模型的情况下,512个水粒子可以无卡顿,而加载后已经出现卡顿,如果按照开始的设想去设计,最终效果很差,因此最后没有实现倒水,而是仅仅将流体放在一个立方体容器中,进行旋转移动控制

四、难点及攻克历程

1、SPH算法

①在确定一开始的设想后,我首先实现了一个简单的粒子系统,接着查询了相关论文和博客,包括动态的水面模拟[1],OpenGL中基于粒子系统的喷泉模拟实现[2]等,但这两者的主要内容都在于水面的模拟,而我想实现的是对于流体细致到每一个粒子的模拟,因此找到的几篇文章都难以帮助我实现预想的效果。(最后的事实证明SPH算法过于精细,对于计算能力的要求很高,没能实现预想效果,在提前答辩过程中助教指出sph算法并不是对每个粒子渲染,而是用每个粒子来计算一部分区域的流体的位置,对流体进行渲染。)

②接着去问了船建学院的同学,想知道如何计算倒水时的轨迹,一位同学给了我一个简单的方程:

但这仅仅是计算了一个水流的直径,模拟出来的结果应该是一个管道的形状,显然 不是我想要的。

另一个同学给我推荐了一个模拟软件,我想看需要调哪些参数已得知要模拟流体需要考虑哪些因素,但这却无法得知背后的计算模型。

③最后询问荀琳玲助教得知了SPH算法(荀琳玲助教在作业完成过程中给了我很多帮助),于是上网查询,在找了多个博客后锁定了这个博客:SPH算法简介[3]。

按照我的理解,SPH算法就是将水看成一个个粒子组成,粒子之间互相影响,计算每个水珠的各种属性,根据每个水珠在一个时刻的受力计算出它下一时刻的位置,而关键就在于计算它的受力。

那么,粒子之间互相影响导致的受力如何计算呢?这里就需要一个“光滑核”的概念,即粒子的属性会扩散到周围,并且随着距离的增加影响逐渐变小,这种随着距离而衰减的函数被称为“光滑核”函数,最大影响半径为“光滑核半径”。

设想流体中某点r(此处不一定有粒子),在光滑核半径h范围内有数个粒子,位置分别是,r0→,r1→,r2→,…rj→,则该处某项属性A的累加公式为:

我们假设流体中一个位置为ri→的点,此处的密度为ρ(ri)、压力为p(ri)、速度为u(ri),可以推导出此处的加速度a (ri)为

ri→处的密度计算公式最终为:

压力产生的加速度部分:

粘度产生的加速度部分:

以上两个部分的计算分别由sph_fluid_system类中的_computePressure方法和_computeForce计算。

那么现在的问题变成了,如何知道哪些粒子在当前粒子的光滑核半径之内呢?

这里采用的方法是sph_grid_container类和sph_neighbour_table类一起作用实现。具体的方式是,grid_container实现一个内部分为一个个小方格的容器,方格是按照顺序排好顺序的,在particlepool中取得粒子后根据粒子位置属性找出方格序号,进而找出方格中其它粒子,也就找到了邻居,加入到neighbour_table,之后按照公式计算即可。

而在查询SPH算法相关资料之前,我已经写了一个粒子系统,结构是particle类和一个particlepool类,后者负责粒子的管理。写的时候因为没有设想到之后会采用复杂的算法,因此直接将绘制写为了particle类自身的一个方法。之后发现这样的实现方式很不清晰,所以最后将绘制统一放到了源.cpp中,将glut与sph系统解耦,也就是说换一个glew或者glfw这个sph系统还能用。

源.cpp在每次display时sph_system调用tick方法,利用以上所述计算了粒子的加速度后计算出下一时刻粒子的位置,然后源.cpp获取particlepool中所有粒子的位置进行绘制。

2、模型的加载

一开始仅仅在加载粒子模型时需要使用,因此使用了在作业一中写的objloader,之后需要加载大的模型,发现自己写的不够用了。

首先想采用assimp库加载场景模型,但网上大多数教程都是glew,而我用的是glut,因此最后采用OpenMesh。此处遇到一个坑,OpenMesh里应该有ws2def.h,与win2sock.h有冲突,而后者是使用安卓进行连接时用到的库,在尝试使用命名空间分隔无果后,发现include顺序调换之后不再报错,在向几位学长请教之后发现命名空间解决的是命名上的冲突,其它的冲突还是得看报错进行具体解决。

3、容器旋转,流体的重力向量保持不变。

由于容器和流体的旋转是在渲染阶段利用glrotate做的旋转,也就是说在流体看来,重力方向还是原本那样,即在渲染的时候就跟着旋转了,而不是在世界坐标系中的(0,-9.8,0)。(重力向量是源.cpp传入fluid_system的参数)因此需要在每次rotate之前把新的重力向量传入fluid_system。新的重力向量是采用数学方法推出的。绕x轴转yRotate度,绕y轴转xRotate度,最后的位置是:

X = -9.8 * sin(xRotate / 180 * PI) * sin(yRotate / 180 * PI),

Y = -9.8 * cos(xRotate/180 * PI),

Z = 9.8 * sin(xRotate / 180 * PI) * cos(yRotate / 180 * PI)

此处遇到一个坑,math.h中采用弧度制,glrotate采用角度制,导致一段时间怎么调也不对,而我以为是公式错了,导致在此处耽误了很长时间,最后通过在display函数中加了一个将重力向量绘制出来查看才发现了这个问题并加以解决。

另外,此处的数学计算为之后做交互埋下了一个伏笔。

4、交互

首先实现鼠标键盘交互,像控制远近,控制上下左右移动都是为了调整模型到合适的位置方便,在实现过程中加入的。可以通过f5切换场景模型是否跟着容器变化角度。视角的变化是采用gltranslate去变换物体位置实现的。

接着是实现安卓手机与之的交互。

首先要解决的问题自然是连接。因为以前做过一个安卓app的项目,有过cs架构的经验,而那时服务器是放在阿里云上的,所以我一开始设想了这样的架构:安卓发消息给阿里云的服务器,电脑端发消息去阿里云服务器获取。后来才想到电脑端用的c++不一定不能写服务器呀,于是去学习了一下socket在安卓端的java和电脑端的c++的使用,建立了连接,此时也就遇到了在上述的与openmesh冲突的问题。

控制方面,开始时设想采用陀螺仪获取角加速度,让流体的容器跟着旋转,可是实现完成后发现这个实现无法让流体容器与手机的角度同步,很难控制。我想要的效果是,手机转到什么位置,容器就转到什么位置,但采用陀螺仪的话容器的旋转就变成增量式的了。

后来查找了安卓的多个传感器,发现加速度传感器的xyz数值就是重力在三个轴上的分量,也就是是说,只要解上面那个公式的方程即可得到xRotate、yRotate的数值,这样就可让容器的转动与手机同步,即

X = -9.8 * sin(xRotate / 180 * PI) * sin(yRotate / 180 * PI)

Y = -9.8 * cos(xRotate/180 * PI)

Z = 9.8 * sin(xRotate / 180 * PI) * cos(yRotate / 180 * PI).

最终实现成功了,不过此处的一个坑是asin、acos在遇到不合理的参数比如大于1时会报nan,出错,导致程序挂掉,因此需要检查一下再输入。

但最终实现效果也没有预期的好,因为涉及到了网络,无法做到实时流畅的响应,还是会有延迟,考虑更流畅的方式可能是利用计算机摄像头或者专门用于操控的设备来实现控制。

五、其它技术实现

1、场景:房间模型,可通过f4切换调整。开始时给了材质的定义,发现效果很差,最后将其去掉,调整了一下光照位置让它真实感强一些。

2、光照:通过设置一个light_intensity值作为light0的ambient,diffuse,specular的输入,通过f11、f12增大减小light_intensity值调整光照。因为要调整,因此将光照的初始化放在display函数中,且与房间模型的相对位置不随glrotate改变。

3、粒子系统物理仿真:sph算法

4、粒子系统模型切换:开始时采用自己写的objloader,后来既然用了openmesh就直接也用它读入了一个cube模型,因为计算量本来就很大,就没有读入更复杂的模型。

5、粒子系统光照、纹理映射:在texture.h中实现纹理的读入,然后在粒子位置四周画了一个立方体每一面都贴上相同的waterball.jpg这张纹理。另外,透明效果通过glBlendFunc混合颜色来实现。

6、交互控制:鼠标键盘、安卓设备。

六、总结

这次大作业虽然没有实现预期那么好的效果,但是在利用安卓手机进行交互这一点还是增添了一点趣味性,并且在实现的过程中让我意识到了adobe宣传片的效果的大致实现思路,同时也意识到,利用网络进行实时的交互这种方式不可靠,不够快也不够稳定安全,我觉得这也是需要特定交互硬件的原因之一;在利用SPH算法实现流体的过程中,我开始时认为这个算法过于精细,一定有近似算法,在较低计算量的情况下实现不差于它的效果。经过提前答辩时助教的点拨才意识到了问题所在,流体不是通过对每个粒子都进行渲染组成的,而是根据粒子的位置计算出周围流体的位置,然后只对流体看得到的部分进行渲染。

但是由于提前答辩,有些仓促,在sph算法和交互上花了过多的时间,导致时间没有按照打分点来分配,甚至写着代码跨了年,在其它方面的效果不是很好。有很多需要提升的空间,尤其是SPH算法,自己花了大量的力气但渲染这块没做好导致最后的效果不好,之后会深入研究,争取能够实现一开始的设想效果。

总的来说,此次大作业的完成过程中,学到了很多,同时也收获了一定的成就感,后续的改善空间也很大,将来如果要实现adobe宣传片中那种酷炫的效果,此次作业也能提供很多参考。

最后,感谢老师的教学和助教的指导,以及由于提前答辩获得了很多来自老师和助教的反馈,收货很大。

代码量:pc端约1400  安卓端约200

参考博客/论文:

[1]动态的水面模拟,http://blog.csdn.net/zju_fish1996/article/details/52317363

[2]OpenGL中基于粒子系统的喷泉模拟实现,https://wenku.baidu.com/view/c79b56d476eeaeaad1f33068.html

[3]SPH算法简介 https://thecodeway.com/blog/?p=83

Learnopengl  https://learnopengl-cn.github.io/intro/

你可能感兴趣的:(计算机图形学 流体粒子系统)