65.9K
CodeProject 正在变化。 阅读更多。
Home

交互式水效果。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (53投票s)

2001年4月24日

6分钟阅读

viewsIcon

344474

downloadIcon

13677

一个简短的OpenGL演示,展示了多纹理效果和巧妙的数学优化。

Sample Image

引言

该应用程序演示了多纹理效果和巧妙的数学优化,以产生交互式水效。

在代码中使用的计算中,我们基于以下假设:

  • 不可压缩流体:给定质量的流体在任何形状下都占据相同的体积。体积守恒。
  • 流体表面可以用网格高度表示,即我们可以将流体表面看作是由一系列垂直的流体柱组成的。我们可以通过一个NxM矩阵来实现,该矩阵给出表面上每个点的高度(N表示纵坐标上的点数,M表示x坐标上的点数)。这种近似仅适用于不太深的容器,并且只要施加到流体上的力不是非常显著,就可以容忍。流体不会飞溅,波浪也不会破碎(无涌浪)。
  • 可以忽略流体粒子的垂直速度分量。该模型对于陡峭的斜坡或深渊不再有效。
  • 垂直流体柱中的流体水平速度分量大致恒定。一个垂直流体柱内的运动是均匀的。当存在湍流时,该模型不再有效。

这些假设使我们能够使用简化的流体力学方程。为了简化推理,我们首先在二维(高度h取决于x坐标x和时间t):

  1. du(x,t)/dt + u(x,t).du(x,t)/dx + g.dh(x,t)/dx = 0
  2. 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)是容器底的高度(我们假设它不随时间变化t), p(x, t) = h(x, t)-b(x)是流体的深度,而u(x, t)是流体垂直柱的水平速度。方程(1)表示牛顿定律(F=ma),根据作用力给出运动,方程(2)表示体积守恒。

    这两个方程都是非线性的(系数非恒定),但如果流体速度很小且深度变化缓慢,我们可以进行以下近似(我们忽略了乘以u(x, t)p不再依赖于时间 t)

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

    通过对第一个方程关于x求导,对第二个方程关于t求导,我们得到

  5. d2u(x,t)/dxdt + g.d2h(x,t)/dx2 = 0
  6. d2h(x,t)/dt2 + p(x).d2u(x,t)/dtdx = 0

    Asu是一个“合适”的函数(其二阶导数连续),其二阶偏导数相等,我们可以推导出以下单个方程,其中u不存在

  7. d2h(x,t)/dt2 = gp(x)*d2h(x,t)/dx2

    第二个成员(7)的微分方程是一个波动方程,表示高度随时间和x坐标的变化。

    在三维中,方程(7)形式如下:

  8. d2h(x,y,t)/dt2 = gp(x,y) * (d2h(x,y,t)/dx2 + d2h(x,y,t)/dy2)

    为了能够使用我们的高度网格,我们必须有一个离散公式,但这是连续的。此外,由于存在p(x, y),该方程总是非线性的。为简化起见,我们将考虑p常数,即波速不随深度变化(我们将考虑平底容器,这将限制深度的“可变性”)。然后我们得到方程(9)进行离散化

  9. d2h(x,y,t)/dt2 = gp(d2h(x,y,t)/dx2 + d2h(x,y,t)/dy2)

    我们可以使用中心差分法离散化方程的各项:

    d2h(x,y,t)/dt2 => (Dh(x,y,t+1) - Dh(x,y,t))/Dt2 = (h(x,y,t+1) - h(x,y,t) - h(x,y,t) + h(x,y,t-1)) / Dt2

    d2h(x,y,t)/dx2 => (Dh(x+1,y,t) - Dh(x,y,t))/Dx2 = (h(x+1,y,t) - 2h(x,y,t) + h(x-1,y,t))/Dx2

    d2h(x,y,t)/dy2 => (Dh(x,y+1,t) - Dh(x,y,t))/Dy2 = (h(x,y+1,t) - 2h(x,y,t) + h(x,y-1,t))/Dy2

    Dr = Dx = Dy= 网格分辨率,我们得到方程(9)的离散模拟

  10. (h(x,y,t+1) + h(x,y,t-1) - 2h(x,y,t))/Dt2 = gp/Dr2 . (h(x+1,y,t)+h(x-1,y,t)+h(x,y+1,t)+h(x,y-1,t)-4h(x,y,t))

    其中Dt是时间的增量。我们可以为这个递推关系使用更紧凑的记法

                                        [0   1   0]
    1/Dt<sup>2</sup> * [1  -2  1] h(x,y) = gp/Dr<sup>2</sup> . 
                                                 [1  -4   1] h(t)
                                        [0   1   0]

    然后我们可以得到h(x, y, t+1):

                            [0  1  0]
    h(x,y,t+1) = gpDt<sup>2</sup>/Dr<sup>2</sup> . 
                                  [1 -4  1]h(t) -h(x,y,t-1) + 2h(x,y,t)
                            [0  1  0]
    
    
                            [0  1  0]
    h(x,y,t+1) = gpDt<sup>2</sup>/Dr<sup>2</sup> . 
                                  [1 -4  1]h(t) -h(x,y,t-1) + 2h(x,y,t)
                            [0  1  0]
    
    
                            [0  1  0]
    h(x,y,t+1) = gpDt<sup>2</sup>/Dr<sup>2</sup> . 
                                  [1  0  1]h(t) -h(x,y,t-1) + 2h(x,y,t) - 
                                  4gpDt<sup>2</sup>/Dr<sup>2</sup> . 
                                  h(x,y,t)
                            [0  1  0]
    
    
                            [0  1  0]
    h(x,y,t+1) = gpDt<sup>2</sup>/Dr<sup>2</sup> . 
                                  [1  0  1]h(t) -h(x,y,t-1) + 
                                  (2 - 4gpDt<sup>2</sup>/Dr<sup>2</sup>) . 
                                  h(x,y,t)
                            [0  1  0]

    通过设置gpDt2/Dr2 = 1/2,我们消除了最后一项,递推关系得以简化,得到

  11. 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的高度相关。我们可以使用两个高度矩阵来实现这一点:H1H2: 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个顶点的imerick 高度由矩阵中4个相邻盒imerick 的高度给出。

在我们的例子中,我们使用 GL_TRIANGLE_STRIP 对模型进行网格划分,并使用一些多通道效果使其看起来逼真。

首先,我们根据顶点法线(logo的纹理坐标)成比例地扰动一组纹理坐标。这看起来像是折射。

    /* calculate ourself normalization */

    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];

       }
    }

然后,我们使用一个假的反射映射公式(与观察者视线位置无关,只需将法线投影到xy平面)计算第二组纹理坐标(天空的纹理坐标)。

// 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);

最后,使用每行扫描线的 TRIANGLE_STRIP 进行网格划分。

    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();
  }
© . All rights reserved.