参考网络上VC++实现水波纹
水波有如下几个特性:
- 扩散:当你投一块石头到水中,你会看到一个以石头入水点为圆心所形成的一圈圈的水波,这里,你可能会被这个现象所误导,以为水波上的每一点都是以石头入水点为中心向外扩散的,这是错误的。实际上,水波上的任何一点在任何时候都是以自己为圆心向四周扩散的,之所以会形成一个环状的水波,是因为水波的内部因为扩散的对称而相互抵消了。
- 衰减:因为水是有阻尼的,否则,当你在水池中投入石头,水波就会永不停止的震荡下去。
- 水的折射:因为水波上不同地点的倾斜角度不同,所以,因为水的折射,我们从观察点垂直往下看到的水底并不是在观察点的正下方,而有一定的偏移。如果不考虑水面上部的光线反射,这就是我们能感觉到水波形状的原因。
- 反射:水波遇到障碍物会反射。
- 衍射:忽然又想到这一点,但是在程序里却看不到,如果能在水池中央放上一块礁石,或放一个中间有缝的隔板,那么就能看到水波的衍射现象了。
对于水波纹
波形计算
有这样的一个公式
X0'=(X1+X2+X3+X4)/ 2- X0
已知某一时刻水面上任意一点的波幅,那么,在下一时刻,任意一点的波幅就等于与该点紧邻的前、后、左、右四点的波幅的和除以2、再减去该点的波幅。
// 计算出下一个时刻所有点的波幅
void nextFrame()
{
for(int i = PIC_WIDTH; i < PIC_HEIGHT*(PIC_WIDTH-1); i++)
{
// 公式:X0'= (X1+X2+X3+X4) / 2 - X0
buf2[i] = ((buf[i-PIC_WIDTH] + buf[i+PIC_WIDTH] + buf[i-1] + buf[i+1]) >> 1) - buf2[i];
// 波能衰减
buf2[i] -= buf2[i] >> 5;
}
short *ptmp = buf;
buf = buf2;
buf2 = ptmp;
}
模拟水的折射效果
因为水的折射,当水面不与我们的视线相垂直的时候,我们所看到的水下的景物并不是在观察点的正下方,而存在一定的偏移。偏移的程度与水波的斜率,水的折射率和水的深度都有关系,如果要进行精确的计算的话,显然是很不现实的。同样,我们只需要做线形的近似处理就行了。因为水面越倾斜,所看到的水下景物偏移量就越大,所以,我们可以近似的用水面上某点的前后、左右两点的波幅之差来代表所看到水底景物的偏移量。
水的折射,受到波形的影响
// 处理当前时刻波幅影响之后的位图,保存在 dest_img 中
void RenderRipple()
{
int i = 0;
for (int y = 0; y < PIC_HEIGHT; y++)
{
for (int x = 0; x < PIC_WIDTH; x++)
{
short data = 1024 - buf[i];
// 偏移
int a = ((x - PIC_WIDTH / 2) * data / 1024) + PIC_WIDTH / 2;
int b = ((y - PIC_HEIGHT / 2) * data / 1024) + PIC_HEIGHT / 2;
// 边界处理
if (a >= PIC_WIDTH) a = PIC_WIDTH - 1;
if (a < 0) a = 0;
if (b >= PIC_HEIGHT) b = PIC_HEIGHT - 1;
if (b < 0) b = 0;
// 处理偏移
img_ptr2[i] = img_ptr1[a + (b * PIC_WIDTH)];
i++;
}
}
}
gtk实现代码:
//gtk实现水波纹效果
//by GeXingli
//2022/08/02
#include <gtk/gtk.h>
#include <stdio.h>
//malloc()
#include <stdlib.h>
//memset()
#include <string.h>
int PIC_WIDTH;
int PIC_HEIGHT;
GtkWidget *window;
int *waveBuff_0;
int *waveBuff_1;
GdkPixbuf *pixbuf_0,*pixbuf_1;
guint timer_0;
char *fileName;
//计算下一时刻各点的波幅
void nextFrame()
{
int i;
for(i = PIC_WIDTH; i < PIC_HEIGHT*(PIC_WIDTH-1); i++)
{
// 公式:X0'= (X1+X2+X3+X4) / 2 - X0
waveBuff_1[i] = ((waveBuff_0[i-PIC_WIDTH] + waveBuff_0[i+PIC_WIDTH] + waveBuff_0[i-1] + waveBuff_0[i+1]) >> 1) - waveBuff_1[i];
// 波能衰减
waveBuff_1[i] -= waveBuff_1[i] >> 5;
}
int *ptmp = waveBuff_0;
waveBuff_0 = waveBuff_1;
waveBuff_1 = ptmp;
}
// 显示当前时刻受波幅影响的位图
void RenderRipple()
{
int i = 0;
int x,y;
guchar *rgbbuf_0,*rgbbuf_1;
pixbuf_0 = gdk_pixbuf_new_from_file(fileName,NULL);
pixbuf_1 = gdk_pixbuf_new_from_file(fileName,NULL);
//获得pixbuf的像素数据指针
rgbbuf_0 = gdk_pixbuf_get_pixels (pixbuf_0);
rgbbuf_1 = gdk_pixbuf_get_pixels (pixbuf_1);
for (y = 0; y < PIC_HEIGHT; y++)
{
for (x = 0; x < PIC_WIDTH; x++)
{
short data = 1024 - waveBuff_0[i];
// 偏移
int a = ((x - PIC_WIDTH / 2) * data / 1024) + PIC_WIDTH / 2;
int b = ((y - PIC_HEIGHT / 2) * data / 1024) + PIC_HEIGHT / 2;
// 边界处理
if (a >= PIC_WIDTH){
a = PIC_WIDTH - 1;
}
if (a < 0){
a = 0;
}
if (b >= PIC_HEIGHT){
b = PIC_HEIGHT - 1;
}
if (b < 0){
b = 0;
}
// 处理偏移
//这里是处理没有alpha 通道RGB像素数据
rgbbuf_0[i*3] = rgbbuf_1[(a + (b * PIC_WIDTH))*3];
rgbbuf_0[i*3+1] = rgbbuf_1[(a + (b * PIC_WIDTH))*3+1];
rgbbuf_0[i*3+2] = rgbbuf_1[(a + (b * PIC_WIDTH))*3+2];
i++;
}
}
//绘制图像
gdk_draw_pixbuf(GTK_WIDGET (window)->window,NULL,pixbuf_0,0, 0,0, 0,PIC_WIDTH, PIC_HEIGHT,GDK_RGB_DITHER_NORMAL,0,0);
}
//定时器函数,实时渲染画面
gboolean RenderFun()
{
//显示当前波幅的图像
RenderRipple();
//计算下一时刻波幅
nextFrame();
return TRUE;
}
//投石头产生波纹
//参数x、y是石头掉落的位置,stonesize是石头大小(影响的区域),stoneweight是石头重量(影响波的幅度)
void disturb(int x, int y, int stonesize, int stoneweight)
{
int posx,posy;
// 突破边界不处理
if ((x >= PIC_WIDTH - stonesize) ||
(x < stonesize) ||
(y >= PIC_HEIGHT - stonesize) ||
(y < stonesize))
return;
for (posx=x-stonesize; posx<x+stonesize; posx++)
{
for (posy=y-stonesize; posy<y+stonesize; posy++)
{
if ((posx-x)*(posx-x) + (posy-y)*(posy-y) < stonesize*stonesize)
{
waveBuff_0[PIC_WIDTH*posy+posx] += stoneweight;
}
}
}
}
//鼠标移动回调函数
void mouse_move(GtkWidget *widget,GdkEventMotion *event,gpointer data){
//鼠标移动产生波纹较小
disturb((int)event->x,(int)event->y,2,222);
}
//鼠标释放回调函数
void mouse_release(GtkWidget *widget,GdkEventButton *event,gpointer data){
//鼠标点击产生波纹较大
disturb((int)event->x,(int)event->y,5,2222);
}
int main() {
gtk_init(NULL, NULL);
fileName = "bg.jpg";
//获取图片大小
pixbuf_0 = gdk_pixbuf_new_from_file(fileName,NULL);
PIC_WIDTH = gdk_pixbuf_get_width(pixbuf_0);
PIC_HEIGHT = gdk_pixbuf_get_height(pixbuf_0);
//建立窗口
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size(GTK_WINDOW(window), PIC_WIDTH, PIC_HEIGHT);
gtk_window_set_title(GTK_WINDOW(window), "Demo");
//初始化波幅数组
waveBuff_0 = malloc(PIC_HEIGHT*PIC_WIDTH*sizeof(int));
waveBuff_1 = malloc(PIC_HEIGHT*PIC_WIDTH*sizeof(int));
memset(waveBuff_0,0,PIC_HEIGHT*PIC_WIDTH*sizeof(int));
memset(waveBuff_1,0,PIC_HEIGHT*PIC_WIDTH*sizeof(int));
//为窗体添加鼠标事件,添加回调函数
gtk_widget_add_events(window,GDK_BUTTON_MOTION_MASK|GDK_BUTTON_RELEASE_MASK);
g_signal_connect(window,"motion-notify-event",G_CALLBACK(mouse_move),NULL);
g_signal_connect(window,"button-release-event",G_CALLBACK(mouse_release),NULL);
gtk_widget_show_all(window);
g_signal_connect(G_OBJECT(window),"destroy",G_CALLBACK(gtk_main_quit),NULL);
//启动定时器,实时渲染画面
timer_0 = g_timeout_add(30,(GSourceFunc)RenderFun,window);
gtk_main();
}
参考VC++代码能够实现水波纹效果,但是gtk与vc的绘图机制不一样,并且涉及到像素的操作。但是从最终效果来看还是达到了目的

对于水平两侧边界的波纹,会互相影响,如右边界的波纹超出的部分会在左侧显示

猜测是因为这个波纹计算算法
在垂直方向上数组中的像素不是连续的,而水平方向的像素是连续的,因此会彼此影响,因为对于水平边界实际上在数组中是连续存在的