2D特效之水波

2D特效之水波

http://dev.gameres.com/Program/Visual/2D/water.htm
  水波特效在游戏当中也比较多见,波纹实时生成算法很多,这里介绍一种快速算法。之所以说它是一种快速算法,是因为它的计算既没有用到sin、cos函数也没有用到sin、cos函数的查表算法,它只是根据波的传播原理,通过少量的加减、位移运算来完成。如要多了解一些波的知识,赶快去翻书哦,现在去学或复习还来得及。当然快速的代价是内存的消耗,这一点我们曾在文章里也提到过,为了追求更高的效率,往往可采取牺牲部分内存这样一种手段来达到目的。

在开始之前我们先作一些假设,同时你可参考左面的图。首先假设波是从右往左传播,0为波在当前时刻的波形,1为前1/8周期时的波形,2为前1/4周期时的波形。设波的周期为T。图中向下的箭头是各点处的振动速度,箭头线长表示振动速度的大小。

2D特效之水波_第1张图片

现设有任一点x(Y方向暂不考虑),它在三个波形上分别对应点P0、P1和P2,三点在振幅方向的偏移量(含方向)分别为D0、D1和D2。设波形函数为D0 = sin(2*Pi/T*t+b),则D1 = sin(2*Pi/T*(t-T/8)+b),D2 = sin(2*Pi/T*(t-T/4)+b),我们下面来证明21/2*D1 = D0+D2:

设2*Pi/T+b=a,则
D0 = sin(a),D1=sin(a-Pi/4),D2 = sin(a-Pi/2) ==》
D0+D2 = sin(a)+sin(a-Pi/2)
=2*sin((a+(a-Pi/2))/2)*cos((a-(a-Pi/2))/2)
=2*sin(a-Pi/4)*cos(pi/4)
=21/2*D1

利用这一特征我们就可以来计算波形了,我们可以以1/8个周期为一显示帧,然后用两个缓冲区来保留前两帧的波形,用上述公式就可以依次推算出后续波的波形。这里需要做一些优化,首先将21/2放大为2,则有D0=2*D1-D2,计算后的D0会偏大一些(相当于波能加大),这样的波会越振越厉害,不停地振动下去永不止,所以要将D0的值减少一部分,办法是减去1/n个D0,将n值取成2的5(或其它?)次方,这样就可以用移位来计算:D0-=D0>>5,最后要处理的是波的传播了,一般我们在某一点给出一个干扰源(波源),这一点的能量要向四周传播出去我们才能看出波的抖动,否则光一个点上下抖动看起来是不会象水波的,我们用平均算法来传递波能,每一点的波形都是其前后左右各点的平均值。如右图,若蓝点是波源,则它由绿、黄、红依次向外传播,各位注意不要被此图所误导,波形绝不是如图示那样呈菱形向外传播,这个图只不过是为了说明波的传播次序,因为即使是颜色相同的点其能量(或者说偏移)都不会是相同的。


由上面所分析我们有如下的波传播代码:

void CWaterApp::RippleSpread()
{
    // m_buf1 is the previous frame,
    // and m_buf2 is the frame before that

    int i;

    for (i=320; i<64000-320; i++)
    {
        // Spread out
        m_buf2[i] = (
            ((m_buf1[i-1]+
            m_buf1[i+1]+
            m_buf1[i-320]+
            m_buf1[i+320])>>1)) - m_buf2[i];

        // Energy damping
        m_buf2[i] -= m_buf2[i]>>5;
    }

    // Swap m_buf1 and m_buf2
    short *ptmp = m_buf1;
    m_buf1 = m_buf2;
    m_buf2 = ptmp;
}


最后一步就是根据计算出来的波形做渲染了,我们可以随便选一幅位图来作为我们的渲当背景(纹理)。由于我们的缓冲区是各点的偏移(偏离水平面的高度),所以我们可以将当前点前后和左右两个落差作为光折射后的偏移量来计算光的折射。为了让纹理图看起来更象水,可将图的蓝色加深。代码如下:

void CWaterApp::RenderRipple()
{
    DDSURFACEDESC2 ddsd, ddsd1;
    ddsd.dwSize = sizeof (DDSURFACEDESC2);
    m_pTexture->Lock(&ddsd);
    ddsd1.dwSize = sizeof(DDSURFACEDESC2);
    m_pRender->Lock(&ddsd1);

    DWORD dwPixel;
    int xoff, yoff;
    int k = 320;
    for (int i=1; i<199; i++)
    {
        for (int j=0; j<320; j++)
        {
            // do refraction
            xoff = m_buf1[k-1]-m_buf1[k+1];
            yoff = m_buf1[k-320]-m_buf1[k+320];
            dwPixel = m_pTexture->GetPixel(&ddsd, 160+j+xoff, 140+i+yoff);

            // do shading
            int p = dwPixel & 0x1F;
            p += xoff;
            if (p>31) p = 31;
            if (p<0) p = 0;
           dwPixel = (dwPixel & 0xFFFFFFE0) | p;

            m_pRender->PutPixel(&ddsd1, j, i, dwPixel);
            k++;
        }
    }
    m_pTexture->Unlock();
    m_pRender->Unlock();
}


在作渲染的同时不停的加入波源,你们会发现无论波源的多少,其运算速度是一样的快捷,事实上这可从代码中看出来,算法与波源的多少是无关的。

 

你可能感兴趣的:(2D特效之水波)