水波纹,鼠标点击后水会四散,产生涟漪的感觉,十分真实.
实现原理:
扩散:当你投一块石头到水中,你会看到一个以石头入水点为圆心所形成的一圈圈的水波,这里,你可能会被这个现象所误导,以为水波上的每一点都是以石头入水点为中心向外扩散的,这是错误的。实际上,水波上的任何一点在任何时候都是以自己为圆心向四周扩散的,之所以会形成一个环状的水波,是因为水波的内部因为扩散的对称而相互 抵消了。
衰减:因为水是有阻尼的,否则,当你在水池中投入石头,水波就会永不停止的震荡下去。
水的折射:因为水波上不同地点的倾斜角度不同,所以,因为水的折射,我们从观察点垂直往下看到的水底并不是在观 察点的正下方,而有一定的偏移。如果不考虑水面上部的光线反射,这就是我们能感觉到水波形状的原因。
反射:水波遇到障碍物会反射。
衍射:如果能在水池中央放上一块礁石,或放一个中间有缝的隔板,那么就能看到水波的衍射现象了
多说无益,看一下效果图
这样子看起来还是极好的.
我先要定义两个波能缓冲区
int *m_pWaveBuf1; //波能缓冲区1
int *m_pWaveBuf2; //波能缓冲区2
在初始化函数里,波能缓冲区水池图象一样大小的数组
//获取位图宽、高、一行的字节数
m_iBmpWidth = stBitmap.bmWidth;
m_iBmpHeight = stBitmap.bmHeight;
//分配波能缓冲区
m_pWaveBuf1 = new int[m_iBmpWidth*m_iBmpHeight];
m_pWaveBuf2 = new int[m_iBmpWidth*m_iBmpHeight];
水波扩散函数
void CRipple::WaveSpread()
{
int *lpWave1 = m_pWaveBuf1;
int *lpWave2 = m_pWaveBuf2;
for(int i = m_iBmpWidth; i < (m_iBmpHeight - 1)*m_iBmpWidth; i++)
{
//波能扩散
lpWave2[i] = ((lpWave1[i - 1] + lpWave1[i - m_iBmpWidth] +
lpWave1[i + 1] + lpWave1[i + m_iBmpWidth]) >> 1) - lpWave2[i];
//波能衰减
lpWave2[i] -= (lpWave2[i] >> 5);
}
//交换缓冲区
m_pWaveBuf1 = lpWave2;
m_pWaveBuf2 = lpWave1;
}
某一时刻,X0点的振幅除了受X0点自身振幅的影响外,同时受来自它周围前、后、左、右四个点(X1、X2、X3、X4)
的影响.这四个点对a0点的影响力可以说是机会均等的。那么我们可以假设这个一次公式为:
X0’=a(X1+X2+X3+X4)+bX0 (公式1)
最终我们得到这个函数
X0’=(X1+X2+X3+X4)/ 2- X0
水在实际中是存在阻尼的,否则,用上面这个公式,一旦你在水中增加一个波源,水面将永不停止的震荡下去。所以,
还需要对波幅数据进行衰减处理,让每一个点在经过一次计算后,波幅都比理想值按一定的比例降低。这个衰减率经过
测试,用1/32比较合适,也就是1/2^5。可以通过移位运算很快的获得。
渲染函数:
在程序中,用一个页面装载原始的图象,用另外一个页面来进行渲染。取得指向页面内存区的指针,然后用根据偏
移量将原始图象上的每一个象素复制到渲染页面上。进行页面渲染.
void CRipple::WaveRender()
{
int iPtrSource = 0;
int iPtrRender = 0;
int lineIndex = m_iBmpWidth;
int iPosX = 0;
int iPosY = 0;
//扫描位图
for(int y = 1; y < m_iBmpHeight - 1; y++)
{
for(int x = 0; x < m_iBmpWidth; x++)
{
//根据波幅计算位图数据偏移值,渲染点(x,y)对应原始图片(iPosX,iPosY)
// iPosX = x + (m_pWaveBuf1[lineIndex - 1] - m_pWaveBuf1[lineIndex + 1]);
// iPosY = y + (m_pWaveBuf1[lineIndex - m_iBmpWidth] - m_pWaveBuf1[lineIndex + m_iBmpWidth]);
//另外一种计算偏移的方法
int waveData = (1024 - m_pWaveBuf1[lineIndex]);
iPosX = (x - m_iBmpWidth/2)*waveData/1024 + m_iBmpWidth/2;
iPosY = (y - m_iBmpHeight/2)*waveData/1024 + m_iBmpHeight/2;
if(0 <= iPosX && iPosX < m_iBmpWidth &&
0 <= iPosY && iPosY < m_iBmpHeight)
{
//分别计算原始位图(iPosX,iPosY)和渲染位图(x,y)对应的起始位图数据
iPtrSource = iPosY*m_iBytesPerWidth + iPosX*3;
iPtrRender = y*m_iBytesPerWidth + x*3;
//渲染位图,重新打点数据
for(int c = 0; c < 3; c++)
{
m_pBmpRender[iPtrRender + c] = m_pBmpSource[iPtrSource + c];
}
}
lineIndex++;
}
}
//设置渲染后的位图
SetDIBits(m_hRenderDC, m_hRenderBmp, 0, m_iBmpHeight, m_pBmpRender, &m_stBitmapInfo, DIB_RGB_COLORS);
}
增加波源:
我们必须在水池中加入波源,你可以想象成向水中投入石头,形成的波源的大小和能量与石头的半径和你扔石头的力量都有关系。知道了这些,那么好,我们只要修改波能数据缓冲区m_pWaveBuf1,让它在石头入水的地点来一个负的“尖脉冲”.
void CRipple::DropStone(int x, int y, int stoneSize, int stoneWeight)
{
int posX = 0;
int posY = 0;
for(int i = -stoneSize; i < stoneSize; i++)
{
for(int j = -stoneSize; j < stoneSize; j++)
{
posX = x + i;
posY = y + j;
//控制范围,不能超出图片
if(posX < 0 || posX >= m_iBmpWidth ||
posY < 0 || posY >= m_iBmpHeight)
{
continue;
}
//在一个圆形区域内,初始化波能缓冲区1
if(i*i + j*j <= stoneSize*stoneSize)
{
m_pWaveBuf1[posY*m_iBmpWidth + posX] = stoneWeight;
}
}
}
}