流体模拟的边界和平移。

10
这个流体模拟基于Stam的论文。他在第7页介绍了对流背后的基本思想:

从两个网格开始:一个包含上一时间步长的密度值,另一个将包含新值。对于后者的每个网格单元,我们通过速度场向后跟踪单元的中心位置。然后我们从以前的密度值网格进行线性插值,并将该值分配给当前网格单元。

下面是代码。两个密度网格为dd0uv是速度分量,dt是时间步长,N(全局)是网格大小,b可以忽略:
void advect(int b, vfloat &d, const vfloat &d0, const vfloat &u, const vfloat &v, float dt, std::vector<bool> &bound)
{
    float dt0 = dt*N;
    for (int i=1; i<=N; i++)
    {
        for (int j=1; j<=N; j++)
        {
            float x = i - dt0*u[IX(i,j)];
            float y = j - dt0*v[IX(i,j)];
            if (x<0.5) x=0.5; if (x>N+0.5) x=N+0.5;
            int i0=(int)x; int i1=i0+1;
            if (y<0.5) y=0.5; if (y>N+0.5) y=N+0.5;
            int j0=(int)y; int j1=j0+1;

            float s1 = x-i0; float s0 = 1-s1; float t1 = y-j0; float t0 = 1-t1;
            d[IX(i,j)] = s0*(t0*d0[IX(i0,j0)] + t1*d0[IX(i0,j1)]) +
                         s1*(t0*d0[IX(i1,j0)] + t1*d0[IX(i1,j1)]);
        }
    }
    set_bnd(b, d, bound);
}

这种方法简洁有效,但是由于值是向后追踪和插值的,实现对象边界对我来说很棘手。我目前的解决方案是,如果旁边有空白区域(或空格),则将密度推出边界,但这只是在视觉上准确,而且会导致密度在角落和具有对角线速度的区域积累。我现在正在寻求“正确性”。
边界代码的相关部分:
void set_bnd(const int b, vfloat &x, std::vector<bool> &bound)
{
    //...
    for (int i=1; i<=N; i++)
    {
        for (int j=1; j<=N; j++)
        {
            if (bound[IX(i,j)])
            {
                //...
                else if (b==0)
                {
                    // Distribute density from bound to surrounding cells
                    int nearby_count = !bound[IX(i+1,j)] + !bound[IX(i-1,j)] + !bound[IX(i,j+1)] + !bound[IX(i,j-1)];
                    if (!nearby_count) x[IX(i,j)] = 0;
                    else
                        x[IX(i,j)] = ((bound[IX(i+1,j)] ? 0 : x[IX(i+1,j)]) +
                                      (bound[IX(i-1,j)] ? 0 : x[IX(i-1,j)]) +
                                      (bound[IX(i,j+1)] ? 0 : x[IX(i,j+1)]) +
                                      (bound[IX(i,j-1)] ? 0 : x[IX(i,j-1)])) / surround;
                }
            }
        }
    }
}

bound 是一个布尔型向量,其行和列范围为 0N+1。在主循环之前通过将单元格坐标设置为 1 来设置边界对象。

论文模糊地说明“然后我们只需添加一些代码到 set_bnd() 例程中,从它们直接相邻的值来填写占用单元格的值”,这或多或少是我所做的。 我正在寻找更精确实现边界的方法,即具有非流体固体边界,并最终支持多流体边界。视觉质量比物理正确性更重要。


@CraigEstey 我更新了我的问题。为了让问题更简洁,删除了一些代码;set_bnd 的注释部分处理的是不需要回答的边缘情况。我的完整代码在这里 - qwr
我已经构建并运行了它[添加单步调试等]。对于边界矩形,内部区域显然是固体的,但是您希望边缘如何?(例如,[i15,j20]是固体还是流体?)或者从左到右,最后一个流体x值是14还是15?我假设是14?这会影响事情。固体的边缘是否应具有任何非零[插值]密度[或速度]值?我假设不是?如何量化错误?负密度值?还有其他吗?如何从控制台转储中通过视觉或数字方式发现“坏处”? - Craig Estey
@CraigEstey 目前,边缘被视为流体,但理想情况下它们应该是固体的。我认为我已经处理好了速度问题,所以密度是当前的问题。 "糟糕"主要是密度消失(没有被推开)和区域产生密度而不应该产生的情况。从一开始,这个模拟只追求视觉准确性,只要看起来真实就足够了。 - qwr
@CraigEstey 目前我在应用 Stam 的方法来为我的物体计算边界速度:通过将水平和垂直速度变为它们原本的负值,从而使净效应为零。实心边缘是流体的,只是因为论文中是这样描述的。我认为替换边界向量不会带来太大的性能提升,而且向量是最简单的方法。灰色吸积是问题所在,但已经通过 lrm29 提到的方法得到了解决。现在问题并不像我认为的那样重要了——现在关键是正确性。 - qwr
你为了实现Irm29的修复做了什么更改[似乎我是“downrev”]? 我一直怀疑使用nearby_count--,这是你改变的吗?对我来说,矩形的顶部应该像外框的底部一样工作。边界的另一种方法可能只是点列表。然后,forall bpt in bound: bpt.i,bpt.j,bpt.xxx (例如预计算更多的东西,所以循环中就少了if)。为了看得更清楚,我在渲染循环的底部做了网格:SDL_SetRenderDrawColor(renderer,100,100,0,0); SDL_RenderDrawRect(renderer,&r);但由于四舍五入,有些线条是双倍宽的。 - Craig Estey
显示剩余8条评论
2个回答

2
你的答案来自物理而非模拟。由于你正在处理边界,你的速度场需要满足普朗特尔无滑移边界条件,这意味着边界处的速度必须为零。请参见https://en.wikipedia.org/wiki/Boundary_layer了解更多信息。如果你的速度场不满足此条件,你将遇到所描述的困难,包括将质量对流回边界,这是模型的基本违规行为。
你还应该知道,这个对流代码不保持密度(出于设计考虑),并且保持定律在最后修复。你需要注意这一步骤,因为矢量场的霍奇分解也具有适用的边界条件。

3
这个答案有帮助,但最终没有提供解决方案(如果有的话)。 - qwr

2
您可能会对Jos Stam在2015年9月出版的《流体动画艺术》感兴趣。在第69页左右,他详细讨论了边界条件。
另外,也许您会对这个链接感兴趣:https://software.intel.com/en-us/articles/fluid-simulation-for-video-games-part-1/。
“完美风暴”已经过去了,所以现在您的流体模拟必须要非常大、非常快或非常详细,最好三者兼备。如果使用情况允许,一些人可能会使用GPU。
希望能对您有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接