Pygame水波纹效果

37 投票
2 回答
6979 浏览
提问于 2025-04-17 03:38

我在网上查了很久,但找不到现成的脚本,跟Flash效果相比,这个问题更难解决。我查看了《水效果解析》上的算法,也测试了Perlin噪声的实现,这个可以很好地模拟平面上波浪的结束效果。我想要的是在多个Flash效果中找到的那种实现,基于鼠标悬停的动作。这是针对一个互动地板库的项目,我希望能摆脱Flash,特别是为了避免代码被轻易逆向工程。是的,我知道可以直接用一些现成的Flash代码,但我只会在最后无奈的时候才考虑这样做。

有没有人见过适合Pygame的这种效果实现(不管是否使用OpenGL)?

编辑:有没有人能提供一个使用OpenCV/OpenGL和Pygame实现的合适效果?

问题的关键在于(代码)接口,用来传递一组值,这些值会通过Python从外部解释器(跟踪器,但不是TUIO)发送过来。我尝试了好几天,但Pygame生成的速度远不及用C/C++代码(在OpenGL的着色器中使用的)那么快,而我对C/C++的了解几乎为零。所以目标是至少能从Python代码中实现这个效果。

一个不错的例子,虽然和Flash效果不同,但依然很好的是使用Java小程序的水模拟

(悬赏显示的答案细节不够,因为这是最接近“提问者由于缺乏基本技能而无法创建他想要的代码,这个答案可能对很多人有用”的情况)。

2 个回答

5

下面这个用numpy的例子可以让你入门。它的速度应该足够快,不过在Python中你可以做得更快(可以看看这里了解更多 http://www.scipy.org/PerformancePython)。

顺便提一下,这种方法有几个缺点:

  1. 你无法控制波纹的速度——如果想做到这一点,你需要修改波纹函数中的公式(如果你能搞清楚它和波动方程的关系 http://en.wikipedia.org/wiki/Wave_equation,那就好了)。
  2. 这个“池塘”的“深度”是固定的(而且可能太浅了)。我添加了一个深度参数来放大效果。
  3. 文章中读取的是整数像素偏移量——如果用插值值的话,效果会更好(我想你可以用opengl做到这一点,但我对这方面的了解几乎为零)。

代码:

import numpy

def ripple(w1, w2, damp, n = 1):
    for _ in xrange(n):
        w2 *= -2
        w2[1:-1,1:-1] += w1[0:-2, 1: -1]
        w2[1:-1,1:-1] += w1[2:  , 1: -1]
        w2[1:-1,1:-1] += w1[1:-1, 0: -2]
        w2[1:-1,1:-1] += w1[1:-1, 2:   ]
        w2 *= .5 * (1. - 1./damp)
        w1, w2 = w2, w1

def refract(x, y, w, rindex, depth = 10):
    sx = x[0,1] - x[0,0]
    sy = y[1,0] - y[0,0]

    dw_dx = (w[2: ,1:-1] - w[:-2,1:-1]) / sx * .5
    dw_dy = (w[1:-1,2: ] - w[1:-1,:-2]) / sy * .5

    xang = numpy.arctan(dw_dx)
    xrefract = numpy.arcsin(sin(xang) / rindex)
    dx = numpy.tan(xrefract) * dw_dx * depth

    yang = numpy.arctan(dw_dy)
    yrefract = numpy.arcsin(sin(yang) / rindex)
    dy = numpy.tan(yrefract) * dw_dy * depth

    dx *= numpy.sign(dw_dx)
    dy *= numpy.sign(dw_dy)

    xmin = x[0,0]
    xmax = x[0,-1]
    x[1:-1,1:-1] += dx
    x[:,:] = numpy.where(x < xmin, xmin, x)
    x[:,:] = numpy.where(x > xmax, xmax, x)

    ymin = y[0,0]
    ymax = y[-1,0]
    y[1:-1,1:-1] += dy
    y[:,:] = numpy.where(y < ymin, ymin, y)
    y[:,:] = numpy.where(y > ymax, ymax, y)

x和y应该是通过numpy.meshgrid调用生成的网格:这里有个示例用法:

    x,y = meshgrid(x,y)
    w = 10 * exp(- (x*x + y*y))
    w1 = w.copy()
    x1,y1 = meshgrid(r_[0:len(x):1.0], r_[0:len(y):1.0])
    ripple(w, w1, 16) # w1 will be modified
    refract(x1, y1, w1, rindex=2, depth=10) # x1 and y1 will be modified
    numpy.around(x1, out=x1) # but you will get better results with interpolate
    numpy.around(y1, out=y1) # 
9

在做了一些功课(也就是研究)后,我尝试把问题中提到的Java代码直接转换成Python,但在用Python/Numpy更新一个巨大的像素颜色数组时,经历了非常非常糟糕的体验。这个数组是根据像素的位置来实现波纹效果的(抱歉,我的母语不是英语)。我需要解析多个(x,y)位置来计算效果,并把结果显示在屏幕上的表面上(surfarray随之而来)。经过这些尝试,我得出了一个结论,这个结论也得到了其他评论者的支持,那就是Pygame根本不够强大,无法以每秒至少24帧的速度遍历整个像素数组,并将计算结果应用到屏幕上的每个像素上(这对于体验来说已经算是很差了)。

引用了Last Light Productions的开发者和前Project Geometrian的Ian Mallet的话:

Pygame在处理像素方面并不好。除了GPU,其他的都不行。

接着,我的搜索变成了寻找一种被称为Alkahest的东西——这实际上是永远无法真正找到的东西——它基于同样的波纹图像的想法,但这次是通过使用透明度来透视多个Pygame表面。我在Gamedev上发布了一个问题关于Pygame圆形裁剪/遮罩。而被选中的答案实际上证实了我之前担心的事情:Pygame永远不会强大到可以完成这个任务。

一天后,我回到了之前的开发想法,发现了Ogre3D。结果发现(1)Ogre3D及其示例是开源的,(2)其中一个示例是一个与移动物体互动的3D水模型,正是我在2D中尝试实现的效果,但以更专业的方式。

由于我对C/C++的知识几乎为零,我决定询问如何定制Ogre3D水演示,以便找到一个起点。其中一个答案让我找到了Touchscape的软件,那里提供了一个SDK(见这个答案)。

Ogre3D几乎解决了我的问题。水波纹效果、OpenGL(它可能会根据硬件可选使用)、游戏引擎以及通过Python-Ogre提供的Python封装——所以我对自己问题的回答是:

有没有人能提供一个使用OpenCV/OpenGL和Pygame实现这个效果的合适方案?

基本上是:

可以。查看Ogre3D的水演示,随SDK提供——然后通过Python-Ogre将其接入Python。

撰写回答