使用粒子方法进行流体特效模拟需要进行液面重构,构造出流体的自由表面,液面重构方法也是一个独立的研究方向,针对其的研究已经有了很多成果,包括液面的平滑度、精度和并行效率等。
在这里,主要是记录一下我在液面重构实现过程遇到的问题。
SPH的液面重构部分,我使用的是有向距离场方法(Animating Sand as a Fluid),这是一种简单有效的液面重构方法。目前针对有向距离场方法已经有了一些改进方案,如A Unified Particle Model for Fluid-Solid Interactions等。
这里的距离场是为了计算流体的隐式表面。
关于这个方法,我在实现过程中遇到了一个问题,液面在某些情况下存在一些缺口,如下:
流体上方出现了一个缺口,在低精度(单元格精度0.05)表面下可以明显看到。
粒子视图下流体为:
两个视图分别为流体第5次迭代和第800次迭代后的状态。
为了定位这个BUG,在调试时,我对距离场网格进行了一些定制,如单元格边长设为0.05,距离场网格起点设为(-0.2,-0.2,-0.2),然后对距离场规模进行了控制。
通过调试,我找到了出现问题的网格单元,并且发现单元格顶点存在问题,在8个单元格顶点中有4个缺失了。在我的实现过程中,是通过空间中的坐标进行单元格顶点的搜索的。
单元格顶点缺失的原因是:通过对哈希表的遍历,对顶点进行索引时,未能找到哈希表中的此坐标下的顶点,但哈希表中确实存在这个顶点,即查找时未能辨明这个顶点。
在更准确的定位问题点后,我发现错误的根本原因是数据的精度问题,即double值的精度问题。顶点坐标使用的是double类型数据,在查找顶点时,通过判断当前坐标是否等于哈希表中的顶点坐标,如果等于则表明此顶点是需要搜索的顶点。
如下是出现BUG的位置:
if (this->x == gc.x && this->y == gc.y && this->z == gc.z) { return false; }
改进之后为:
const double delt = 0.000000001; if (abs(this->x - gc.x) < delt && abs(this->y - gc.y) < delt && abs(this->z - gc.z) < delt) { return false; }
使用double数据进行判断时,double值在计算累积过程中可能会出现精度缺失,通过设定一个阈值进行一定精度范围下的等于判断,可以在一定程度上解决这个问题。从整体来看,液面重构出现BUG的原因与算法本身是没有关系的,根本原因是数据精度的问题。
改进后的液面(单元格精度0.008)如下:
渲染(blender):
这里暂时先省略液面重构实现方案的部分,只是记录一下偶然遇到的这个问题。