首页 Qt 学习之路 2 Qt 学习之路 2(90):粒子系统

Qt 学习之路 2(90):粒子系统

5 1.9K

粒子系统是一种计算机图形学的技术,用于模拟一些特定的模糊现象,这些现象用传统的渲染技术难以达到一定的真实感。虽然名为“粒子”,但却可以模拟爆炸、烟、水流、落叶、云、雾、流星尾迹或其它发光轨迹这样的抽象视觉效果。粒子系统的特色是“模糊”,其渲染效果并非完全取决于像素,而是使用特定的边界参数描述随机粒子。幸运的是,使用 QML 可以很方便的实现粒子系统。

粒子系统的核心是ParticleSystem,用于控制共享时间线。一个场景可以有多个粒子系统,每一个都有自己独立的时间线。粒子由Emitter元素发射,使用ParticlePainter进行可视化显示,这个显示可以是图像、QML 项目或者阴影元素等。Emitter还使用向量空间定义了粒子的方向。粒子一旦发射,就完全脱离了发射器的管理。粒子模块则提供了Affector,允许控制发射出的粒子。系统中的粒子可以通过ParticleGroup共享时间变换,默认情况下,粒子都是属于空组(即'')。

粒子系统

按照上面的简介,粒子系统包含以下重要的类:

  • ParticleSystem - 管理发射器共享的时间线
  • Emitter - 向系统中发射逻辑粒子
  • ParticlePainter - 使用粒子画笔绘制粒子
  • Direction - 已经发射出的粒子使用的向量空间
  • ParticleGroup - 每一个粒子都隶属于一个组
  • Affector - 维护已经发射出的粒子

下面我们从一个简单的示例开始。使用 Qt Quick 粒子系统非常简单,我们需要使用:

  • 为模拟系统构建所有元素的ParticleSystem
  • 向系统中发射粒子的Emitter
  • 继承自ParticlePainter的元素,用于实现粒子可视化的
import QtQuick 2.0
import QtQuick.Particles 2.0

Rectangle {
    id: root;
    width: 300; height: 160
    color: "#1f1f1f"

    ParticleSystem {
        id: particles
    }

    Emitter {
        id: emitter
        anchors.centerIn: parent
        width: 160; height: 80
        system: particles
        emitRate: 10
        lifeSpan: 1000
        lifeSpanVariation: 500
        size: 16
        endSize: 32

        Rectangle {
            anchors.fill: parent
            color: 'transparent'
            border.color: 'green'
            border.width: 2
            opacity: 0.8
        }
    }

    ItemParticle {
        system: particles
        delegate: Rectangle {
            id: rect
            width: 10
            height: 10
            color: "red"
            radius: 10
        }
    }
}

程序运行如下:

粒子系统

首先,我们创建了一个 300x160 的深色矩形作为背景;然后声明一个ParticleSystem组件。通常这是使用粒子系统的第一步:正是ParticleSystem组件连接起其它元素。第二步一般是创建Emitter,定义了一个粒子发射区域以及发射粒子的相关参数。Emitter使用system属性将其自己与一个粒子系统关联起来。在这个例子中,发射器所定义的区域每秒发射 10 个粒子(emitRate: 10),每个粒子的生命周期是 1000 毫秒(lifeSpan : 1000),发射出的粒子的生命周期变动区间为 500 毫秒(lifeSpanVariation: 500)。每一个粒子发出时的起始大小为 16px(size: 16),消失时的大小为 32px(endSize: 32)。

为了显示出发射器的范围,我们特意添加了一个绿色矩形,用于标记处发射器的边框。注意观察,大部分粒子都会出现在这个绿色矩形内,但是也会有少量粒子超出边界。粒子渲染的位置取决于其生命周期和粒子的方向。我们会在后面详细介绍有关粒子方向的概念。

发射器仅仅发射逻辑上的粒子,每一个逻辑粒子都要通过ParticlePainter绘制出来,以便可视化显示。在这个例子中,我们使用了ItemParticle类型。ItemParticle可以设置一个代理,用于渲染每个粒子。注意,我们同样使用system属性,将ItemParticleParticleSystem关联起来。

下面着重说明几个参数:

  • emitRate:每秒钟射出的粒子数(默认是每秒 10 个)
  • lifeSpan:粒子生命周期的毫秒数(默认是 1000 毫秒),注意,这个参数是一个“建议值”,系统并不会严格设置每一个粒子都是这么长的生命周期,可以看作有一个误差范围
  • sizeendSize:粒子的起始大小和终止大小(默认是 16px)

修改这些参数,可以非常明显的影响到一个粒子系统的运行行为。例如,我们将上面的Emitter修改为:

    Emitter {
        id: emitter
        anchors.centerIn: parent
        width: 160; height: 80
        system: particles
        emitRate: 40
        lifeSpan: 2000
        lifeSpanVariation: 500
        Rectangle {
            anchors.fill: parent
            color: 'transparent'
            border.color: 'green'
            border.width: 2
            opacity: 0.8
        }
    }

运行结果如下:
粒子系统
注意观察由于增大了emitRate,同时延长了lifeSpanlifeSpanVariation,系统中同时存在的粒子比之前的版本增加了很多。

除了ItemParticle,我们还可以使用ImageParticle。顾名思义,ImageParticle使用图像渲染粒子。我们可以设置其source属性指定图像,例如下面的代码片段:

    ImageParticle {
        system: particles
        source: "star.png"
    }

其中,star.png 图像放在下面,有兴趣的话可以右键另存为保存在本地运行代码。

star.png

代码运行结果如下:

粒子系统-图像

如果所有粒子都使用同一个图像,这个粒子系统会显得很假。事实上,即便使用图像,粒子也可以设置其颜色,例如,下面我们将粒子的主体颜色设置为金色,但是允许一个 +/-60% 的误差范围:

color: '#FFD700'
colorVariation: 0.6

为了让场景更生动,我们还可以旋转粒子:将每一个粒子顺时针旋转 15 度,另外有一个 +/-5 度的误差范围;然后,这些粒子继续以每秒 45 度的速度旋转。这个速度因粒子而异,会有一个 +/-15 度每秒的误差范围。

rotation: 15
rotationVariation: 5
rotationVelocity: 45
rotationVelocityVariation: 15

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

entryEffect: ImageParticle.Scale

最后,我们的代码变成了这个样子:

    ImageParticle {
        system: particles
        source: "star.png"
        color: '#FFD700'
        colorVariation: 0.6
        rotation: 0
        rotationVariation: 45
        rotationVelocity: 15
        rotationVelocityVariation: 15
        entryEffect: ImageParticle.Scale
    }

现在,我们有了一堆能够旋转的五颜六色的星星:

粒子系统-旋转的星星

现在,我们介绍了两种粒子:基于代理的ItemParticle和基于图像的ImageParticle。另外还有第三种粒子,基于着色器的CustomParticleCustomParticle使用 OpenGL 着色器语言定义,由于这部分内容需要结合 GLSL 语言,感兴趣的朋友可以自己查阅相关文档,这里不再详述。

5 评论

LinWM 2016年1月13日 - 20:26

豆子老兄,你好!有这么一种情况:A项目要用B项目的头文件可以在Qt的.pro文件中通过INCLUDEPATH += XXX实现;但是在编译时会报“undefined reference to 某个类::某个方法”的错误;
查了一些资料,大致原因可能是A项目在编译时无法链接到在B项目中目标类产生的.o文件(可能不是这个原因,绞尽脑汁好久,也不知道确切的原因。如果豆子兄知道真正的原因,还望指点迷津^_^);
如果是上面我搜索到的原因,那么.pro文件要添加或者修改哪个变量可以让A项目能够链接到B项目中目标类产生的.o文件????

回复
豆子 2016年1月13日 - 22:16

按照你的说法,A 项目可以看作生成 exe 的应用项目,B 项目可以看作生成 dll (Windows 平台的动态链接库,静态链接库或者 Linux 平台以此类推)的库项目。A 项目中需要使用 B 项目提供的函数,这需要两个步骤:1. 编译时需要提供 B 项目的头文件,即你说的使用 INCLUDEPATH;2. 链接时需要提供 B 项目生成的 dll,需要在 pro 文件中添加 LIBS += -lB.dll 等参数,指定库文件,可以查阅有关 LIBS 的用法。至于你说的链接到 .o 文件,因为 .o 文件一般作为中间文件,这种方法是不可取的;否则的话,链接到 .o 文件与把两个项目源代码放在一处就没有任何区别了(因为同一项目即链接到各个编译单元的 .o 文件)。

回复
LinWM 2016年1月14日 - 02:00

谢谢豆子老兄,我还在为这个问题烦恼呢。我这个问题的实际情况刚好跟你举例的相反:A项目是一个插件程序,B项目是主程序;然后A项目实现在B项目定义的接口(事先在A项目.pro中用了INCLUDEPATH导入B项目的头文件,包括实现的接口文件),但是由于B项目的接口包含B项目内部的一个类,编译的时候就会报:“undefined reference to 目标类::某个方法”;和“undefined reference to vtable for 目标类”;这两个错误。
你上面说的使用LIBS += -lB.dll,是不是相当于下面这个函数呢?

PluginInterfacePointer loadPlugin()
{
QDir pluginsDir(qApp->applicationDirPath());
#if defined(Q_OS_WIN)
if(pluginsDir.dirName().toLower() == "debug"
|| pluginsDir.dirName().toLower() == "release")
pluginsDir.cdUp();
#elif defined(Q_OS_MAC)
if(pluginsDir.dirName() == "MacOS") {
pluginsDir.cdUp();
pluginsDir.cdUp();
pluginsDir.cdUp();
}
#endif
pluginsDir.cd("plugins");
foreach(QString fileName, pluginsDir.entryList(QDir::Files)) {
QString pluginPath = pluginsDir.absoluteFilePath(fileName);
QPluginLoader pluginLoader(pluginPath);
QObject *plugin = pluginLoader.instance();
if(plugin != NULL) {
PluginInterfacePointer temp = qobject_cast(plugin);
if(temp != NULL){//just for one plugin
return temp;
}
}
}

return NULL;
}

回复
mz 2016年11月18日 - 18:28

请在 pro 文件中的 SOURCES 和HEADERS 里加上文件的路径

回复
LinWM 2016年1月14日 - 08:03

有一个奇怪的现象:B项目的这个类如果带.cpp文件则报“undefined reference to 某个类::某个方法”的错误;但是如果将函数体都写在头文件中,则不会报错。不过我这边的问题,真是特殊啊,那几个函数是信号函数,头文件中只能有声明,所以,这个错,真是郁闷了。

回复

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2