前言

粒子系统用于模拟一些特定的模糊效果,如爆炸、烟火、雪花、水流等。使用传统的渲染技术实现粒子效果比较困难,但是使用QML粒子系统能十分方便的实现各种粒子效果,使你的界面更加炫酷,动感。

QML中的粒子系统

QML粒子系统的核心是ParticleSystem,用于控制共享时间线。一个场景可以有多个粒子系统,每一个都有自己独立的时间线。粒子由粒子发射器(Emitter)元素发射,使用粒子画笔(ParticlePainter)进行可视化显示,它可以是一张图片、一个QML项或者一个着色器程序。Emitter还使用向量空间定义了粒子方向。粒子一旦发射,就脱离了Emitter的控制。粒子模块提供了粒子控制器(Affector),它可以控制已发射粒子的参数。系统中的粒子可以通过粒子组(ParticleGroup)共享时间变换,默认情况下,粒子都属于空组。

粒子系统主要类如下:

  • 粒子系统(ParticleSystem):管理发射器共享时间线。
  • 粒子发射器(Emiiter):向系统中发射逻辑粒子。
  • 粒子画笔(ParticlePainter):使用粒子画笔绘制粒子。
  • 方向(Direction):已发出粒子使用的空间向量。
  • 粒子组(ParticleGroup):每个粒子都是一个粒子组的成员。
  • 粒子控制器(Affector):控制已发射的粒子。

粒子系统ParticleSystem

ParticleSystem用于控制共享时间线,它将ParticlePainter,Emitter,Affector等元素联合起来实现粒子效果,也就是说其他类型想要交互的话就必须在同一个ParticleSystem中。

导入粒子系统模块:import QtQuick.Particles 2.0

要使用粒子系统第一步要创建一个粒子系统:

ParticleSystem { id: particleSystem }

ParticleSystem包含以下属性:

  • empty: 表示系统中是否还有活着的粒子;
  • particleStates:粒子动画状态,你可以给每一个粒子组的每一个粒子使用一个动画精灵;
  • paused: 粒子系统是否暂停;
  • running:粒子系统是否在这运行。

ParticleSystem提供以下方法:

  • void pause():暂停粒子系统;
  • void reset():丢弃所有已经粒子;
  • void restart():停止所有粒子系统,并重新启动;
  • void resume():当粒子系统处于暂停状态时,恢复粒子系统运行;
  • void start():启动粒子系统;
  • void stop():关闭粒子系统。

粒子发射器Emitter

Emitter向粒子系统中发射逻辑粒子,这些粒子都有自己的轨迹和生命周期,但他们是不可见的,想要可见的话就要用到ParticlePainter了。Emitter定义了粒子的发射区域以及相关发射参数并使用system属性将自己与一个粒子系统关联起来。

使用粒子系统的第二步就是要创建一个粒子发射器:

Emitter { id: emitter; system: particleSystem }

Emitter包含以下属性:

  • acceleration : 粒子的起始加速度
  • emitRate : 发射速率(每秒发射多少个粒子,默认值为10)
  • enabled : 发射器是否可用(默认值为true)
  • endSize : 粒子生命周期结束时大小(默认值为-1)
  • group : 逻辑粒子群(默认为“”空字符串)
  • lifeSpan : 粒子生命周期,单位毫秒(默认值为1000)
  • lifeSpanVariation : 粒子生命周期误差区间
  • maximumEmitted : 粒子最大发射量
  • shape : 粒子的发射区域(默认值为Emitter的填充矩形)
  • size : 粒子初始大小
  • sizeVariation : 粒子初始大小误差区间
  • startTime : 延迟发射时间
  • system : 粒子系统
  • velocity : 粒子发射速率
  • velocityFromMovement : 粒子的发射速率会叠加Emitter运动的速率

Emitter提供以下方法:

void burst(int count, int x, int y):立即向xy点发射count个粒子
void burst(int count):立即发射count个粒子
void pulse(int duration):当发射器未启用时,临时起动发射器duration毫秒

QML还提供一个叫TrailEmitter的发射器,该发射器的特点是起始位置是基于其他粒子的,他会跟随一个粒子移动。例如你需要创建一个跟随一团火焰的烟,你可能就需要用到这个发射器了。

粒子画笔ParticlePainter

使用Emitter发射的只是逻辑粒子,我们还需要使用粒子画笔ParticlePainter对粒子进行可视化渲染。ParticlePainter是个基本类型,本身并不渲染任何东西,我们需要使用ParticlePainter的子类进行渲染。ParticlePainter的子类包括:ItemParticle、ImageParticle和CustomParticle。

1.ItemParticle

ItemParticle使用QML的Item来渲染粒子,我们可以给ItemParticle的delegate(代理)属性设置一个Item,这样每个粒子都会使用该Item进行渲染。同样,我们也需要使用ItemParticle的system属性将ParticleSystem与ItemParticle关联起来。

ItemParticle { system: particleSystem; delegate: Rectangle { } }

ItemParticle包含以下属性:

  • delegate : 将为每个逻辑粒子创建委托实例,并随之移动;
  • fade : 粒子是否在生命周期结束时自动淡出或淡出

ItemParticle提供以下接口:

  • void freeze(Item item):暂停逻辑粒子代表的时间流,允许您控制它的移动;
  • void give(Item item):获取Item的控制权,该Item将断开和逻辑粒子的关联;
  • void take(Item item, bool prioritize)
  • void unfreeze(Item item):重新启动项目表示的逻辑粒子的时间流,允许它再次被粒子系统移动。

2.ImageParticle

ImageParticle使用图像来渲染逻辑粒子,图像能够上色、旋转、变形或者添加精灵动画。

ImageParticle函数以下属性:

alpha : 图像的alpha值(0-1.0)
alphaVariation : 图像的alpha值误差区间
autoRotation : 自动旋转
blueVariation : 图像蓝色通道变化(0-1)
color : 图像颜色
colorTable : 纹理图像,原图像将与纹理图像混合
colorVariation : 图像颜色误差区间
entryEffect : 粒子创建和消失时的特效
greenVariation : 图像绿色通道变化
opacityTable : 纹理图像,图像的opacity属性将与纹理图像混合
redVariation : 图像红色通道变化
rotation : 图像旋转角度
rotationVariation : 图像旋转角度误差区间
rotationVelocity : 图像旋转速率
rotationVelocityVariation : 图像旋转速率误差区间
sizeTable : 纹理图像
source : 图像
sprites : 精灵动画
spritesInterpolate :平滑精灵动画
status : 加载图像的状态
xVector : StochasticDirection
yVector : StochasticDirection

3.CustomParticle

除了ItemParticle和ImageParticle还有基于着色器的粒子CustomParticle。CustomPartile使用OpenGL着色器语言GLSL定义。

CustomParticle提供以下属性:

  • fragmentShader : 片段着色器
  • vertexShader : 顶点着色器

一个简单的例子

我们从一个简单的例子开始,在该例子中我们创建ParticleSystem和Emitter并用ItemParticle渲染粒子,然后将他们联系起来。代码如下:

import QtQuick 2.0
import QtQuick.Particles 2.0

Item {

    ParticleSystem {
        id: particleSystem
    }

    Emitter {
        id: emitter
        anchors.centerIn: parent
        width: 160
        height: 80
        system: particleSystem
        emitRate: 10
        lifeSpan: 2000
        lifeSpanVariation: 500
        size: 16
        endSize: 32
    }

    ItemParticle {
        system: particleSystem
        delegate: Rectangle {
            id: particleRect
            width: 10
            height: 10
            color: "red"
            radius: 10
        }
    }
}

 

首先我们创建ParticleSystem组件。

然后创建Emitter组件,并设定Emitter粒子发射区域以及相关发射参数。Emitter使用system属性将自己与一个粒子系统关联起来。在这个例子中发射器定义了一个160*80的发射区域,该区域每秒发射10个粒子(emitRate:10),每个粒子的生命周期是1000ms(lifeSpan:1000),生命周期变动区间为500ms(lifeSpanVariation:500),每个粒子发出时起始大小为16px(size:16),消失时大小为32px(endSize:32)。

最后创建ParticlePainter子类型组件。发射器发射的只是逻辑上的粒子,每个粒子都要通过ParticlePainter绘制出来,实现可视化显示。在这个例子中,我们使用了ItemParticle类型。ItemParticle是ParticlePainter的一个子类,它可以设置一个代理(delegate)用于渲染每个粒子。我们同样使用system属性将ItemParticle和ParticleSystem关联起来。

属性参数说明:

  • emitRate:每秒发射出的粒子数(默认为10);
  • lifeSpan:粒子生命周期毫秒数(默认为1000);
  • lifeSpanVariation:粒子生命周期变动区毫秒数(在lifeSpan设置的值上下浮动多少);
  • size:粒子初始化大小(默认为16);
  • endSize:粒子消失时大小(默认为16)。

除了ItemParticle,我们还可以使用ImageParticle。ImageParticle使用图片进行渲染,我们可以设置其source属性指定图片,如如下代码:

    ImageParticle {
        system: particleSystem
        source: "qrc:/star.png"
    }

运行效果如下:

我们还可以给粒子设置颜色,代码如下:

    ImageParticle {
        system: particleSystem
        source: "qrc:/star.png"
        color: "#FFD700"
        colorVariation: 0.6
    }

我们设置粒子颜色为"#FFD700"金色,且允许有60%的误差。

显示效果如下:

为了让星星更生动,我们还可以旋转粒子,代码如下:

    ImageParticle {
        system: particleSystem
        source: "qrc:/star.png"
        color: "#FFD700"
        colorVariation: 0.6
        rotation: 15
        rotationVariation: 5
        rotationVelocity: 45
        rotationVelocityVariation: 15
    }

将每个粒子顺时针旋转15°,另外有一个5°的误差范围。然后将这些粒子继续以每秒45°的速度旋转,也会有一个15°的误差范围。

我们还可以修改粒子进入场景的效果。当粒子生命周期开始时,就会应用这个效果。在这个例子中,我们添加一个缩放效果:

ImageParticle {
        system: particleSystem
        source: "qrc:/star.png"
        color: "#FFD700"
        colorVariation: 0.6
        rotation: 15
        rotationVariation: 5
        rotationVelocity: 45
        rotationVelocityVariation: 15
        entryEffect: ImageParticle.Scale
    }

效果如下:

 

粒子方向

我们还可以设置粒子轨迹的方向。轨迹取决于一个指定的向量空间,该向量定义了粒子的速度和加速度,以及一个随机的方向。QML提供了三种不同的向量空间,用于定义粒子的速度和加速度:

  • 点方向(PointDirection):使用x和y值定义的方向。
  • 角度方向(AngleDirection):使用角度定义方向。
  • 目标方向(TargetDirection):使用一个目标点坐标定义方向。

1.AngleDirection

要使用AngleDirection,我们需要将其赋值给Emitter的velocity属性:

velocity: AngleDirection { }

粒子发射角度使用angle属性定义。angle属性的取值范围是[0, 360],0为水平向右。

在我们例子中我们希望粒子水平向右发射,因此,angle: 0,粒子发射误差范围则是15度。

velocity: AngleDirection {

angle: 0

angleVariation: 15

}

现在我们设置好了粒子方向,下面继续设置粒子速度。粒子速度由magnitude属性决定。magnitude属性单位是像素/秒。如果我们的场景宽度是640px,那么将magnitude设置为100还不错,这就意味着,粒子平均要消耗6.4秒从场景一段移动到另一端。为了让粒子速度更有趣,我们还要设置magnitudeVariation属性,这会为该速度设置一个可变的范围区间:

velocity: AngleDirection {

magnitude: 100

magnitude: 50

}

下面是Emitter完整代码:

    Emitter {
        id: emitter
        anchors.left: parent.left
        anchors.verticalCenter: parent.verticalCenter
        width: 1
        height: 1
        system: particleSystem
        emitRate: 10
        lifeSpan: 6400
        lifeSpanVariation: 400
        size: 32
        velocity: AngleDirection {
            angle: 0
            angleVariation: 15
            magnitude: 100
            magnitudeVariation: 50
        }
    }

我们将magnitude设置为100,因此粒子的平均生命周期为6.4秒。另外,我们将发射器的长宽都设置为1px,意味着所有粒子都将从同一位置发射,也就是相同的轨迹起点。

运行效果如下:

接下来我们看加速度。加速度为每一个粒子添加一个加速度向量,该向量会随着时间流逝而改变速度。例如,我们创建一个类似星轨的轨迹,为了达到这一目的,我们将方向修改为-45°,并且移除速度变量区间:

velocity: AngleDirection {

angle: -45

magnitude: 100

}

加速度方向为90°向下,数值为25:

acceleration: AngleDirection {

angle: 90

magnitude: 25

}

Emitter代码如下:

Emitter {
        id: emitter
        anchors.left: parent.left
        anchors.verticalCenter: parent.verticalCenter
        width: 1
        height: 1
        system: particleSystem
        emitRate: 10
        lifeSpan: 6400
        lifeSpanVariation: 400
        size: 32
        velocity: AngleDirection {
            angle: -45
            magnitude: 100
        }
        acceleration: AngleDirection {
            angle: 90
            magnitude: 25
        }
    }

运行效果如下图:

2.PointDirection

PointDirection使用x、y值导出向量空间。例如,你想让粒子轨迹沿着45°角方向,那么就要将xy值设置成相同的值。例如,我们希望粒子轨迹从左向右,形成一个15°的角,为了设置粒子轨迹,我们首先需要将PointDirection赋值给Emitter的velocity属性:

velocity: PointDirection { }

指定粒子速度为100px/s,我们将x的值设置为100,15°是90°的1/6,因此,我们将y的变化范围设置(yVariation)为100/6:

代码如下:

Emitter {
        id: emitter
        anchors.left: parent.left
        anchors.verticalCenter: parent.verticalCenter
        width: 1
        height: 1
        system: particleSystem
        emitRate: 10
        lifeSpan: 6400
        lifeSpanVariation: 400
        size: 32
        velocity: PointDirection {
            x: 100
            y: 0
            xVariation: 0
            yVariation: 100 / 6
        }
    }

运行效果如下:

3.TargetDirection

TargetDirection使用某个项目指定一个目标点,targetItem指定目标位置关联Item,Item的中心会成为目标点。

首先我们在界面右侧中心设置一个目标点:

Rectangle {
        id: targetPoint
        anchors.right: parent.right
        anchors.verticalCenter: parent.verticalCenter
        width: 10
        height: 10
        radius: 10
        color: "red"
    }

我们将TargetDirection的targetItem属性设置为targetPoint,targetVariation的值为100/6,这大约会形成一个15°的角。代码如下:

    Emitter {
        id: emitter
        anchors.left: parent.left
        anchors.verticalCenter: parent.verticalCenter
        width: 1
        height: 1
        system: particleSystem
        emitRate: 10
        lifeSpan: 6400
        lifeSpanVariation: 400
        size: 32
        velocity: TargetDirection {
            targetItem: targetPoint
            targetVariation: 100 / 6
            magnitude: 100
        }
    }

运行效果如下:

粒子控制

 粒子由发射器发射,一旦粒子发射出来,发射器的任务也就完成了,不会再对粒子有任何影响。如果我们需要控制已发射的粒子,需要使用粒子控制器(Affector)。

QML提供如下控制器:

  • Age:改变粒子生命周期,一般用于提前结束粒子的生命周期;
  • Attractor:将粒子吸引到一个指定的点;
  • Friction:按比例降低粒子的当前速率;
  • Gravity:添加一个一定角度的加速度;
  • Turbulence:为粒子添加一个图像噪音;
  • Wander:随机改变粒子轨迹;
  • GroupGoal:改变粒子组状态;
  • SpriteGoal:改变精灵粒子状态。

1.Age

Age可以改变粒子生命周期,lifeLeft属性指定粒子还能存活多少时间,代码如下:

    Age {
        anchors.centerIn: parent
        width: 140
        height: 120
        system: particleSystem
        advancePosition: true
        lifeLeft: 3200
        once: true

        Rectangle {
            anchors.fill: parent
            color: "transparent"
            border.color: "green"
            border.width: 2
        }
    }

我们利用Age控制器将粒子生命周期缩短到3200ms。当粒子进入控制器范围时,其生命周期只剩下3200ms。将advancePosition设置为true,我们会看到一旦粒子的生命周期只剩下3200ms,粒子又会在其预期的位置从新出现。

运行效果如下:

2.Attractor

Attractor将粒子将粒子吸引到pointX和pointY指定的点,strength属性指定吸引强度,代码如下:

    Attractor {
        anchors.centerIn: parent
        width: 160
        height: 70
        system: particleSystem
        pointX: 0
        pointY: 0
        strength: 1.0

        Rectangle {
            anchors.fill: parent
            color: "transparent"
            border.color: "green"
            border.width: 2
        }
    }

我们的控制器位于界面中间,当粒子进入控制器返回时,控制器会影响粒子向控制器0,0点方向移动。效果如下:

3.Friction

Friction会按一定比列降低粒子速度,代码如下:

    Friction {
        anchors.centerIn: parent
        width: 160
        height: 70
        system: particleSystem
        factor: 0.8
        threshold: 25

        Rectangle {
            anchors.fill: parent
            color: "transparent"
            border.color: "green"
            border.width: 2
        }
    }

粒子会以factor:0.8的比例降低粒子速度,直到降低到threshold:25px/s。

运行效果如下:

4.Gravity

Gravity为粒子添加一个加速度,代码如下:

    Gravity {
        anchors.centerIn: parent
        width: 160
        height: 70
        system: particleSystem
        magnitude: 50
        angle: 90

        Rectangle {
            anchors.fill: parent
            color: "transparent"
            border.color: "green"
            border.width: 2
        }
    }

所有进入控制器范围的粒子都会被添加一个向下90°的加速度,值为50.

运行效果如下:

5.Turbulence

Trubulence为粒子添加一个力向量,每个粒子获得的力向量都是随机的,这是由一个噪音图像决定的,使用noiseSource可以自定义噪音图像。strength定义了力向量强度。

代码如下:

    Turbulence {
        anchors.centerIn: parent
        width: 160
        height: 70
        system: particleSystem
        strength: 100

        Rectangle {
            anchors.fill: parent
            color: "transparent"
            border.color: "green"
            border.width: 2
        }
    }

运行效果如下:

粒子一旦进入控制器范围,就会发疯一样乱串,而不是按照从左向右的方向保持一个大致轨迹。

6.Wander

Wander控制轨迹。affectedParameter属性指定控制哪一属性(速度/位置/加速度),pace属性指定每秒该属性变化的最大值,xVariance和yVariance指定粒子轨迹x和y坐标的浮动区间。代码如下:

    Wander {
        anchors.centerIn: parent
        width: 160
        height: 70
        system: particleSystem
        affectedParameter: Wander.Position
        pace: 200
        yVariance: 240

        Rectangle {
            anchors.fill: parent
            color: "transparent"
            border.color: "green"
            border.width: 2
        }
    }

控制器作用于粒子轨迹的位置属性,轨迹会以每秒200次的频率在y轴方向上随机振动。

运行效果如下:

粒子组

每一个粒子都是粒子组(ParticleGroup)的成员,通过其name属性区分,当没有指定粒子所属的粒子组时,这些粒子默认也都在一个粒子组,不过name属性值是个空字符串,粒子组主要是为了方便地控制粒子行为状态。

以下代码演示了粒子组的使用方法:

import QtQuick 2.0
import QtQuick.Particles 2.0

Rectangle {
    color: "black"

    ParticleSystem { id: particleSystem }

    Rectangle {
        id: targetPoint
        anchors.right: parent.right
        anchors.verticalCenter: parent.verticalCenter
        width: 10
        height: 10
        radius: 10
        color: "red"
    }

    Emitter {
        id: emitter
        anchors.left: parent.left
        anchors.verticalCenter: parent.verticalCenter
        group: "A"
        width: 1
        height: 1
        system: particleSystem
        emitRate: 10
        lifeSpan: 6400
        lifeSpanVariation: 400
        size: 32
        velocity: TargetDirection {
            targetItem: targetPoint
            targetVariation: 100 / 6
            magnitude: 100
        }
    }

    Emitter {
        id: emitter2
        anchors.left: parent.left
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 20
        system: particleSystem
        group: "B"
        width: 10
        height: 10
        emitRate: 10
        lifeSpan: 6400
        lifeSpanVariation: 400
        size: 10
        velocity: AngleDirection {
            angle: 0
            magnitude: 100
        }
    }

    Turbulence {
        anchors.centerIn: parent
        width: 160
        height: parent.height
        groups: ["A","B"]
        strength: 32
        system: particleSystem
    }

    ImageParticle {
        system: particleSystem
        groups: "A"
        source: "qrc:/star.png"
        color: "#FFD700"
        colorVariation: 0.6
        rotation: 15
        rotationVariation: 5
        rotationVelocity: 45
        rotationVelocityVariation: 15
        entryEffect: ImageParticle.Scale
    }

    ItemParticle {
        system: particleSystem
        groups: "B"
        delegate: Rectangle {
            color: "red"
            width: 10
            height: 10
            radius: 10
        }
    }
}

首先我们将粒子系统里的元素分为AB两组,其中A组由emitter发射,ImageParticle渲染;B组由emitter2发射,ItemParticle渲染,AB两组都使用Tubulence进行控制,随机添加一个力向量,运行效果如下如:

可以看到AB两组元素分别发射并渲染粒子,且同时获得了"扩散"效果。

总结

粒子系统用于模拟很多自然现象,比如云、烟、火、雪花等强有力的工具。QML的粒子系统让我们能轻松完成这些工作。适当的粒子往往会成为用户界面上最吸引人的部分。