QML BOOK 第八章 Particle Simulations

来源:互联网 发布:淘宝女装销售话术 编辑:程序博客网 时间:2024/05/22 04:31

8. Particle Simulations

注意
最后一次构建:2014年1月20日下午18:00。
这章的源代码能够在http://qmlbook.org/assets/中找到。
粒子模拟是计算机图形技术的可视化图形效果。典型的效果有:落叶,火焰,爆炸,流星,云等等。
它不同于其它图形渲染,粒子是基于模糊来渲染。它的结果在基于像素下是不可预测的。粒子系统的参数描述了随机模拟的边界。传统的渲染技术实现粒子渲染效果很困难。有一个好消息是你可以使用QML元素与粒子系统交互。同时参数也可以看做是属性,这些参数可以使用传统的动画技术来实现动态效果。

8.1 概念(Concept)

粒子模拟的核心是粒子系统(ParticleSystem),它控制了共享时间线。一个场景下可以有多个粒子系统,每个都有自己独立的时间线。一个粒子使用发射器元素(Emitter)发射,使用粒子画笔(ParticlePainter)实现可视化,它可以是一张图片,一个QML项或者一个着色项(shader item)。一个发射器元素(Emitter)也提供向量来控制粒子方向。一个粒子被发送后就再也无法控制。粒子模型提供粒子控制器(Affector),它可以控制已发射粒子的参数。
在一个系统中,粒子可以使用粒子群元素(ParticleGroup)来共享移动时间。默认下,每个例子都属于空("")组。


  • 粒子系统(ParticleSystem)- 管理发射器之间的共享时间线。
  • 发射器(Emitter)- 向系统中发射逻辑粒子。
  • 粒子画笔(ParticlePainter)- 实现粒子可视化。
  • 方向(Direction)- 已发射粒子的向量空间。
  • 粒子组(ParticleGroup)- 每个粒子是一个粒子组的成员。
  • 粒子控制器(Affector)- 控制已发射粒子。

8.2 简单的模拟(Simple Simulation)

让我们从一个简单的模拟开始学习。Qt Quick使用简单的粒子渲染非常简单。下面是我们需要的:
  • 绑定所有元素到一个模拟的粒子系统(ParticleSystem)。
  • 一个向系统发射粒子的发射器(Emitter)。
  • 一个ParticlePainter派生元素,用来实现粒子的可视化。


例子的运行结果如下所示:


我们使用一个80x80的黑色矩形框作为我们的根元素和背景。然后我们定义一个粒子系统(ParticleSystem)。这通常是粒子系统绑定所有元素的第一步。下一个元素是发射器(Emitter),它定义了基于矩形框的发射区域和发射粒子的基础属性。发射器使用system属性与粒子系统进行绑定。
在这个例子中,发射器每秒发射10个粒子(emitRate:10)到发射器的区域,每个粒子的生命周期是1000毫秒(lifeSpan:1000),一个已发射粒子的生命周期变化是500毫秒(lifeSpanVariation:500)。一个粒子开始的大小是16个像素(size:16),生命周期结束时的大小是32个像素(endSize:32)。
绿色边框的矩形是一个跟踪元素,用来显示发射器的几何形状。这个可视化展示了粒子在发射器矩形框内发射,但是渲染效果不被限制在发射器的矩形框内。渲染位置依赖于粒子的寿命和方向。这将帮助我们更加清楚的知道如何改变粒子的方向。
发射器发射逻辑粒子。一个逻辑粒子的可视化使用粒子画笔(ParticlePainter)来实现,在这个例子中我们使用了图像粒子(ImageParticle),使用一个图片链接作为源属性。图像粒子也有其它的属性用来控制粒子的外观。
  • 发射频率(emitRate)- 每秒粒子发射数(默认为10个)。
  • 生命周期(lifeSpan)- 粒子持续时间(单位毫秒,默认为1000毫秒)。
  • 初始大小(size),结束大小(endSize)- 粒子在它的生命周期的开始和结束时的大小(默认为16像素)。
改变这些属性将会彻底改变显示结果:


增加发射频率为40,生命周期增加到2秒,开始大小为64像素,结束大小减少到32像素。


增加结束大小(endSize)可能会导致白色的背景出现。请注意粒子只有发射被限制在发射器定义的区域内,而粒子渲染是不会考虑这个参数的。

8.3 粒子参数(Particle Parameters)

我们已经知道通过改变发射器的行为就可以改变我们的粒子模拟。粒子画笔被用来绘制每一个粒子。
回到我们之前的粒子中,我们更新一下我们的图片粒子画笔(ImageParticle)。首先我们改变粒子图片为一个小的星形图片:


粒子使用金色来进行初始化,不同的粒子颜色变化范围为+/- 20%。


为了让场景更加生动,我们需要旋转粒子。每个粒子首先按顺时针旋转15度,不同的粒子在+/-5度之间变化。每个例子会不断的以每秒45度旋转。每个粒子的旋转速度在+/-15度之间变化:


最后,我们改变粒子的入场效果。 这个效果是粒子产生时的效果,在这个例子中,我们希望使用一个缩放效果:


现在我们可以看到旋转的星星出现在我们的屏幕上。


下面是我们如何改变图片粒子画笔的代码段。


8.4 粒子方向(Directed Particle)

我们已经看到了粒子的旋转,但是我们的粒子需要一个轨迹。轨迹由速度或者粒子随机方向的加速度指定,也可以叫做矢量空间。
有多种可用矢量空间用来定义粒子的速度或加速度:
  • 角度方向(AngleDirection)- 使用角度的方向变化。
  • 点方向(PointDirection)-  使用x,y组件组成的方向变化。
  • 目标方向(TargetDirection)- 朝着目标点的方向变化。


让我们在场景下试着用速度方向将粒子从左边移动到右边。
首先使用角度方向(AngleDirection)。我们使用AngleDirection元素作为我们的发射器(Emitter)的速度属性:


粒子的发射将会使用指定的角度属性。角度值在0到360度之间,0度代表指向右边。在我们的例子中,例子将会移动到右边,所以0度已经指向右边方向。粒子的角度变化在+/-15度之间:


现在我们已经设置了方向,下面是指定粒子的速度。它由一个梯度值定义,这个梯度值定义了每秒像素的变化。正如我们设置大约640像素,梯度值为100,看起来是一个不错的值。这意味着平均一个6.4秒生命周期的粒子可以穿越我们看到的区域。为了让粒子的穿越看起来更加有趣,我们使用magnitudeVariation来设置梯度值的变化,这个值是我们的梯度值的一半:



下面是完整的源码,平均的生命周期被设置为6..4秒。我们设置发射器的宽度和高度为1个像素,这意味着所有的粒子都从相同的位置发射出去,然后基于我们给定的轨迹运动。


那么加速度做些什么?加速度是每个粒子加速度矢量,它会在运动的时间中改变速度矢量。例如我们做一个星星按照弧形运动的轨迹。我们将会改变我们的速度方向为-45度,然后移除变量,可以得到一个更连贯的弧形轨迹:


加速度的方向为90度(向下),加速度为速度的四分之一:


结果是中间左方到右下的一个弧。


这个值是在不断的尝试与错误中发现的。
下面是发射器完整的代码。



在下一个例子中,我们将使用点方向(PointDirection)矢量空间来再一次演示粒子从左到右的运动。
一个点方向(PointDirection)是由x和y组件组成的矢量空间。例如,如果你想粒子以45度的矢量运动,你需要为x,y指定相同的值。
在我们的例子中,我们希望粒子在从左到右的例子中建立一个15度的圆锥。我们指定一个坐标方向(PointDirection)作为我们速度矢量空间:

为了达到运动速度每秒100个像素,我们设置x为100,。15度角(90度的1/6),我们指定y变量为100/6:


结果是粒子的运动从左到右构成了一个15度的圆锥。


现在是最后一个方案,目标方向(TargetDirection)。目标方向允许我们指定发射器或者一个QML项的x,y坐标值。当一个QML项的中心点成为一个目标点时,你可以指定目标变化值是x目标值的1/6来完成一个15度的圆锥:


注意
当你期望发射粒子朝着指定的x,y坐标值流动时,目标方向是非常好的方案。
我没有再贴出结果图,因为它与前一个结果相同,取而代之的有一个问题留给你。
在下图的红色和绿色圆指定每个目标项的目标方向速度的加速属性。每个目标方向有相同的参数。那么哪一个负责速度,哪一个负责加速度?

8.5 粒子画笔(Particle Painter)

到目前为止我们只使用了基于粒子画笔的图像来实现粒子可视化。Qt也提供了一些其它的粒子画笔:
  • 粒子项(ItemParticle):基于粒子画笔的代理
  • 自定义粒子(CustomParticle):基于粒子画笔的着色器
粒子项可以将QML元素项作为粒子发射。你需要制定自己的粒子代理。


在这个例子中,我们的代理是一个随机图片(使用Math.random()完成),有着白色边框和随机大小。


每秒发出四个粒子,每个粒子拥有4秒的生命周期。粒子自动淡入淡出。


对于更多的动态情况,也可以由你自己创建一个子项,让粒子系统来控制它,使用take(item, priority)来完成。粒子系统控制你的粒子就像控制普通的粒子一样。你可以使用give(item)来拿回子项的控制权。你也可以操作子项粒子,甚至可以使用freeze(item)来停止它,使用unfreeze(item)来恢复它。

8.6 粒子控制(Affecting Particles)

粒子由粒子发射器发出。在粒子发射出后,发射器无法再改变粒子。粒子控制器允许你控制发射后的粒子参数。
控制器的每个类型使用不同的方法来影响粒子:
  • 生命周期(Age)- 修改粒子的生命周期
  • 吸引(Attractor)- 吸引粒子朝向指定点
  • 摩擦(Friction)- 按当前粒子速度成正比减慢运动
  • 重力(Gravity)- 设置一个角度的加速度
  • 紊流(Turbulence)- 强制基于噪声图像方式的流动
  • 漂移(Wander)- 随机变化的轨迹
  • 组目标(GroupGoal)- 改变一组粒子群的状态
  • 子粒子(SpriteGoal)- 改变一个子粒子的状态
生命周期(Age)
允许粒子老得更快,lifeLeft属性指定了粒子的有多少的生命周期。


在这个例子中,当粒子的生命周期达到1200毫秒后,我们将会缩短上方的粒子的生命周期一次。由于我们设置了advancePosition为true,当粒子的生命周期到达1200毫秒后,我们将会再一次在这个位置看到粒子出现。


吸引(Attractor)
吸引会将粒子朝指定的点上吸引。这个点使用pointX与pointY来指定,它是与吸引区域的几何形状相对的。strength指定了吸引的力度。在我们的例子中,我们让粒子从左向右运动,吸引放在顶部,有一半运动的粒子会穿过吸引区域。控制器只会影响在它们几何形状内的粒子。这种分离让我们可以同步看到正常的流动与受影响的流动。


很容易看出上半部分粒子受到吸引。吸引点被设置为吸引区域的左上角(0/0点),吸引力为1.0。


摩擦(Friction)
摩擦控制器使用一个参数(factor)减慢粒子运动,直到达到一个阈值。


在上部的摩擦区域,粒子被按照0.8的参数(factor)减慢,直到粒子的速度达到25像素每秒。这个阈值像一个过滤器。粒子运动速度高于阈值将会按照给定的参数来减慢它。

重力(Gravity)
重力控制器应用在加速度上,在我们的例子中,我们使用一个角度方向将粒子从底部发射到顶部。右边是为控制区域,左边使用重力控制器控制,重力方向为90度方向(垂直向下),梯度值为50。


左边的粒子试图爬上去,但是稳定向下的加速度将它们按照重力的方向拖拽下来。


紊流(Turbulence)
紊流控制器,对粒子应用了一个混乱映射方向力的矢量。这个混乱映射是由一个噪声图像定义的。可以使用noiseSource属性来定义噪声图像。strength定义了矢量对于粒子运动的影响有多大。


在这个例子中,上部区域被紊流影响。它们的运动看起来是不稳定的。不稳定的粒子偏差值来自原路径定义的strength。


漂移(Wander)
漂移控制器控制了轨迹。affectedParameter属性可以指定哪个参数控制了漂移(速度,位置或者加速度)。pace属性制定了每秒最多改变的属性。yVariance指定了y组件对粒子轨迹的影响。


在顶部漂移控制器的粒子被随机的轨迹改变。在这种情境下,每秒改变粒子y方向的位置200次。


8.7 粒子组(Particle Group)

在本章开始时,我们已经介绍过粒子组了,默认下,粒子都属于空组("")。使用GroupGoal控制器可以改变粒子组。为了实现可视化,我们创建了一个烟花示例,火箭进入,在空中爆炸形成壮观的烟火。


这个例子分为两部分。第一部分叫做“发射时间(Launch Time)”连接场景,加入粒子组,第二部分叫做“爆炸烟花(Let there be firework)”,专注于粒子组的变化。
让我们看看这两部分。
发射时间(Launch Time)
首先我们创建一个典型的黑色场景:


tracer使用被用作场景追踪的开关,然后定义我们的粒子系统:


我们添加两种粒子图片画笔(一个用于火箭,一个用于火箭喷射烟雾):


你可以看到在这些画笔定义中,它们使用groups属性来定义粒子的归属。只需要定义一个名字,Qt Quick将会隐式的创建这个分组。
现在我们需要将这些火箭发射到空中。我们在场景底部创建一个粒子发射器,将速度设置为朝上的方向。为了模拟重力,我们设置一个向下的加速度:


发射器属于'rocket'粒子组,与我们的火箭粒子画笔相同。通过粒子组将它们联系在一起。发射器将粒子发射到'rocket'粒子组中,火箭画笔将会绘制它们。
对于烟雾,我们使用一个追踪发射器,它将会跟在火箭的后面。我们定义'smoke'组,并且它会跟在'rocket'粒子组后面:


向下模拟从火箭里面喷射出的烟。emitHeight与emitWidth指定了围绕跟随在烟雾粒子发射后的粒子。如果不指定这个值,跟随的粒子将会被拿掉,但是对于这个例子,我们想要提升显示效果,粒子流从一个接近于火箭尾部的中间点发射出。
如果你运行这个例子,你会发现一些火箭正常飞起,一些火箭却飞出场景。这不是我们想要的,我们需要在它们离开场景前让他们慢下来,这里可以使用摩擦控制器来设置一个最小阈值:


在摩擦控制器中,你也需要定义哪个粒子组受控制器影响。当火箭经过从顶部向下80像素的区域时,所有的火箭将会以0.9的factor减慢(你可以试试100,你会发现它们立即停止了),直到它们的速度达到每秒5个像素。随着火箭粒子向下的加速度继续生效,火箭开始向地面下沉,直到它们的生命周期结束。
由于在空气中向上运动是非常困难的,并且非常不稳定,我们在火箭上升时模拟一些紊流:


当然,紊流控制器也需要定义它会影响哪些粒子组。紊流控制器的区域从底部向上160像素(直到摩擦控制器边界上),它们也可以相互覆盖。
当你运行程序时,你可以看到火箭开始上升,然后在摩擦控制器区域开始减速,向下的加速度仍然生效,火箭开始后退。下一步我们开始制作爆炸烟花。


注意
使用tracers跟踪区域可以显示场景中的不同区域。火箭粒子发射的红色区域,蓝色区域是紊流控制器区域,最后在绿色的摩擦控制器区域减速,并且再次下降是由于向下的加速度仍然生效。
爆炸烟花(Let there be fireworks)
让火箭变成美丽的烟花,我们需要添加一个粒子组来封装这个变化:


我们使用GroupGoal控制器来改变粒子组。这个组控制器被放置在屏幕中间垂直线附近,它将会影响'rocket'粒子组。使用groupGoal属性,我们设置目标组改变为我们之前定义的'explosion'组:


jump属性定义了粒子组的变化是立即变化而不是在某个时间段后变化。
注意
在Qt5的alpha发布版中,粒子组的持续改变无法工作,有好的建议吗?

由于火箭粒子变为我们的爆炸粒子,当火箭粒子进入GroupGoal控制器区域时,我们需要在粒子组中添加一个烟花:


爆炸释放粒子到'sparkle'粒子组。我们稍后会定义这个组的粒子画笔。轨迹发射器跟随火箭粒子每秒发射200个火箭爆炸粒子。粒子的方向向上,并改变180度。
由于向'sparkle'粒子组发射粒子,我们需要定义一个粒子画笔用于绘制这个组的粒子:


闪烁的烟花是红色的星星,使用接近透明的颜色来渲染出发光的效果。
为了使烟花更加壮观,我们也需要添加给我们的粒子组添加第二个轨迹发射器,它向下发射锥形粒子:


其它的爆炸轨迹发射器与这个设置类似,就这样。
下面是最终结果。


下面是火箭烟花的所有代码。


 


8.8 总结(Summary)

粒子是一个非常强大且有趣的方法,用来表达图像现象的一种方式,比如烟, 火花,随机可视元素。Qt5的扩展API非常强大,我们仅仅只使用了一些浅显的。有一些元素我们还没有使用过,比如精灵(spirites),尺寸表(size tables),颜色表(color tables)。粒子看起来非常有趣,它在界面上创建引人注目的东西是非常有潜力的。在一个用户界面中使用非常多的粒子效果将会导致用户对它产生这是一个游戏的印象。粒子的真正力量也是用来创建游戏。
0 0
原创粉丝点击