模拟流体动力学的数学与编程实现

在计算机图形学中,模拟真实世界的流体动力学是一个复杂但非常有趣的挑战。本文将探讨如何通过数学模型和编程技术实现交互式水面效果。将从流体动力学的基本原理出发,逐步深入到具体的编程实现

首先,需要理解流体的一些基本假设:

  • 不可压缩流体:流体的质量在任何形状下都保持恒定,即体积守恒。
  • 液体表面可以用网格高度表示,即可以将液体表面视为由一系列垂直柱体组成的。
  • 液体粒子的垂直速度分量可以忽略不计,模型不适用于陡坡或深谷。
  • 液体在垂直柱体内的水平速度分量大致恒定,运动是均匀的。

基于这些假设,可以使用简化的流体力学方程。在二维情况下,有以下方程:

du(x,t)/dt + u(x,t).du(x,t)/dx + g.dh(x,t)/dx = 0 dp(x,t)/dt + d(u(x,t)p(x,t))/dx = d(h(x,t)-b(x))/dt + p(x,t).du(x,t)/dx + u(x,t).dp(x,t)/dx = 0

其中,g 是重力加速度,h(x, t) 是液体表面的高,b(x) 是容器底部的高度,p(x, t) = h(x, t) - b(x) 是液体的深度,u(x, t) 是垂直柱体的液体水平速度。第一个方程表示牛顿定律(F=ma),描述了液体的运动;第二个方程表示体积守恒。

这两个方程是非线性的,但如果液体速度较小且深度变化缓慢,可以进行以下近似:

du(x,t)/dt + g.dh(x,t)/dx = 0 dh(x,t)/dt + p(x).du(x,t)/dx = 0

通过对第一个方程关于x求导,第二个方程关于t求导,可以得到:

d^2u(x,t)/dxdt + g.d^2h(x,t)/dx^2 = 0 d^2h(x,t)/dt^2 + p(x).d^2u(x,t)/dt^2 = 0

由于u是一个合适的函数(其二阶导数是连续的),其二阶偏导数相等,可以得到一个不包含u的方程:

d^2h(x,t)/dt^2 = gp(x)*d^2h(x,t)/dx^2

这个方程是一个波动方程,表示高度随时间和x坐标的变化。在三维情况下,方程的形式为:

d^2h(x,y,t)/dt^2 = gp(x,y) * (d^2h(x,y,t)/dx^2 + d^2h(x,y,t)/dy^2)

为了能够使用高度网格,需要一个离散公式。此外,这个方程是非线性的,因为p(x, y)的存在。为了简化,假设p是常数,即无论深度如何,波速都是恒定的。得到离散化的方程:

d^2h(x,y,t)/dt^2 = gp(d^2h(x,y,t)/dx^2 + d^2h(x,y,t)/dy^2)

可以使用中心差分法来离散化这个方程的项:

d^2h(x,y,t)/dt^2 => (Dh(x,y,t+1) - Dh(x,y,t))/Dt^2 d^2h(x,y,t)/dx^2 => (Dh(x+1,y,t) - Dh(x,y,t))/Dx^2 d^2h(x,y,t)/dy^2 => (Dh(x,y+1,t) - Dh(x,y,t))/Dy^2

通过设置Dr = Dx = Dy = 网格分辨率,得到方程的离散形式:

(h(x,y,t+1) + h(x,y,t-1) - 2h(x,y,t))/Dt^2 = gp/Dr^2 . (h(x+1,y,t)+h(x-1,y,t)+h(x,y+1,t)+h(x,y-1,t)-4h(x,y,t))

可以使用更紧凑的符号表示这个递推关系:

[0 1 0] * h(x,y,t+1) = gpDt^2/Dr^2 * [1 -4 1] * h(t) - h(x,y,t-1) + 2h(x,y,t)

然后可以得到:

h(x,y,t+1) = gpDt^2/Dr^2 * [1 0 1] * h(t) - h(x,y,t-1) + (2 - 4gpDt^2/Dr^2) * h(x,y,t)

通过设置gpDt^2/Dr^2 = 1/2,可以简化这个关系,得到:

h(x,y,t+1) = 1/2 * (h(x+1,y,t) + h(x-1,y,t) + h(x,y+1,t) + h(x,y-1,t) - h(x,y,t-1))

这个递推关系有一个2的步长:t+1时刻的高度与t和t-1时刻的高度有关。可以使用两个高度矩阵H1和H2来实现这一点:

H2[x, y] = 1/2(H1[x+1, y] + H1[x-1, y] + H1[x, y+1] + H1[x, y-1]) - H2[x, y]

然后可以在每一步交换这两个矩阵。计算一个点的新高度只需要4次加法,1次减法和1次右移(用于除以2)。

从得到的结果中,减去1/2n的结果(即这个结果右移n位),以实现一定的阻尼(n=4给出相当美观的结果,n < 4给出更多的粘性,n > 4给出更多的流动性)。

需要注意的是,这些点的高度是有符号的,0是静止的中性水平。从高度矩阵中,可以很容易地构建一个多边形表示,通过考虑每个网格盒作为一个四边形(或2个三角形),其4个顶点的高度由相邻4个网格盒的高度给出。

在示例中,使用GL_TRIANGLE_STRIP进行网格化,并使用一些多通道效果来实现现实感。

首先,根据顶点法线按比例扰动第一组纹理坐标(标志的纹理坐标)。这看起来像是折射。

/* 计算归一化 */ for (x = 0; x < FLOTSIZE*2; x++) { for (y = 0; y < FLOTSIZE*2; y++) { sqroot = sqrt(_snormal[x][y][0]*_snormal[x][y][0] + _snormal[x][y][1]*_snormal[x][y][1] + 0.0016f); _snormaln[x][y][0] = _snormal[x][y][0]/sqroot; _snormaln[x][y][1] = _snormal[x][y][1]/sqroot; _snormaln[x][y][2] = 0.04f/sqroot; // perturbate coordinates of background // mapping with the components X,Y of normals... // simulate refraction _newuvmap[x][y][0] = _uvmap[x][y][0] + 0.05f*_snormaln[x][y][0]; _newuvmap[x][y][1] = _uvmap[x][y][1] + 0.05f*_snormaln[x][y][1]; } }

然后使用一个假的环境映射公式计算第二组纹理坐标(天空的纹理坐标)。

// really simple version of a fake envmap generator for (x = 0; x < FLOTSIZE; x++) { for (y = 0; y < FLOTSIZE; y++) { // trick : xy projection of normals -> // assume reflection in direction of the normals // looks ok for non-plane surfaces _envmap[x][y][0] = 0.5f + _snormaln[x][y][0]*0.45f; _envmap[x][y][1] = 0.5f + _snormaln[x][y][1]*0.45f; } }

然后使用多纹理和混合将纹理混合在一起。

glEnable(GL_BLEND); // use texture alpha-channel for blending glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glActiveTextureARB(GL_TEXTURE0_ARB); glBindTexture(GL_TEXTURE_2D, 2); // 2nd texture -> background .. glActiveTextureARB(GL_TEXTURE1_ARB); glBindTexture(GL_TEXTURE_2D, 1); // 2nd texture -> envmap glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); /* enable texture mapping and specify ourself texcoords */ glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); for (x = 0; x < strip_width; x++) { glBegin(GL_TRIANGLE_STRIP); // WARNING: glTexCoord2fv BEFORE glVertex !!! glMultiTexCoord2fvARB(GL_TEXTURE0_ARB, _newuvmap[x+1][1]); glMultiTexCoord2fvARB(GL_TEXTURE1_ARB, _envmap[x+1][1]); glVertex3fv(_sommet[x+1][1]); // otherwise everything is scrolled !!! for (y = 1; y < strip_width; y++) { glMultiTexCoord2fvARB(GL_TEXTURE0_ARB, _newuvmap[x][y]); glMultiTexCoord2fvARB(GL_TEXTURE1_ARB, _envmap[x][y]); glVertex3fv(_sommet[x][y]); glMultiTexCoord2fvARB(GL_TEXTURE0_ARB, _newuvmap[x+1][y+1]); glMultiTexCoord2fvARB(GL_TEXTURE1_ARB, _envmap[x+1][y+1]); glVertex3fv(_sommet[x+1][y+1]); } glMultiTexCoord2fvARB(GL_TEXTURE0_ARB, _newuvmap[x][y]); glMultiTexCoord2fvARB(GL_TEXTURE1_ARB, _envmap[x][y]); glVertex3fv(_sommet[x][y]); glEnd(); }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485