3D粒子编辑器
一、3D粒子编辑器基础
Section titled “一、3D粒子编辑器基础”什么是3D粒子
Section titled “什么是3D粒子”在百科中,粒子是指能够以自由状态存在的最小物质组成部分。
在LayaAir引擎中,3D粒子系统中的粒子可用于模拟中烟、雾、水、火、雨、雪、流光等非固定形态的自然现象。而上述这些自然物体的形态由于没有固定的形态,所以不能用固定的模型来模拟实现,需要用多个模型组合成一个完整的视觉效果,而3D粒子正是组合效果的最小单元,但需要注意的是,粒子并非是三维模型,而是面片模型。
火焰效果展示
Section titled “火焰效果展示”
(动图1)
二、LayaAir引擎中创建3D粒子
Section titled “二、LayaAir引擎中创建3D粒子”2.1 粒子节点
Section titled “2.1 粒子节点”在场景中的Scene3D节点下,可以通过鼠标右键来创建3D粒子
(图2.1.1-1)
默认粒子系统添加完成效果
(动图2)
2.2 预制体
Section titled “2.2 预制体”如果考虑到3D粒子是需要复用的,建议使用预制体,在Assets下创建预制体,双击打开预制体后,在Sprite3D节点下通过鼠标右键来创建3D粒子
(图2.1.2)
三、3D粒子的使用
Section titled “三、3D粒子的使用”3D粒子的使用由三部分构成
Section titled “3D粒子的使用由三部分构成”-
粒子系统组件
Section titled “粒子系统组件” -
粒子渲染模块
Section titled “粒子渲染模块” -
(图3.0)从图3.0中看到,当创建一个3D粒子后,会自动添加这三个组件,下面我们来详细了解各系统的使用
3.1 粒子系统组件 ShurikenParticleSystem
Section titled “3.1 粒子系统组件 ShurikenParticleSystem”粒子系统是LayaAir引擎特效表现的基础,通常粒子系统在三维空间中的位置与运动是由发射器控制的。发射器主要由一组粒子行为参数以及在三维空间中的位置所表示。粒子行为参数可以包括粒子生成速度(即单位时间粒子生成的数目)、粒子初始速度向量(例如什么时候向什么方向运动)、粒子寿命(经过多长时间粒子湮灭)、粒子颜色、在粒子生命周期中的变化以及其它参数等等。
粒子系统组件分类
Section titled “粒子系统组件分类”在LayaAir编辑器中,粒子系统组件由五个部分构成
(图3.1)
3.1.1 基础面板 General
Section titled “3.1.1 基础面板 General”默认的是粒子系统的通用模块,用于设置粒子系统的基础性的设置。此模块为固有模块,不可禁用。该模块定义了粒子初始化时的持续时间、循环方式、发射速度、大小等一些列基本的参数。
(图3.1.1-1)
Duration:粒子系统不断发射粒子的持续时间,或者说一个周期时间内可以发射多次粒子,到达设置的时间后,粒子停止发射
注意:不是一个粒子的生命周期时间,下面会介绍一个粒子的生命周期时间
(动图3)Duration为5的效果,5秒到达时,粒子不在发射,同时Loop要设为不启动
Loop:如果启用,粒子系统在上述持续时间结束时,再次启动并继续重复循环
Play On Awake:如果启用,粒子系统会在创建对象时自动启动
Start Delay:启用后系统开始发射前的延迟时间,可选择两种延迟方式
Constant 固定时间
Random Between Two Constant 从最小到最大两个时间中随机取值
(动图4)展示了使用最小2到最大5秒随机的方式,可以看到当第4秒时,粒子开始发射
Start Lifetime:控制每个粒子的生命周期,也就是粒子发生多长时间后消失,它可以是两个数字之间的随机值
(动图5)展示了使用Start Lifetime为3时的效果,显示每个粒子从发射出生到消失用时3秒
Start Speed:每个粒子在适当方向上的初始速度,可选择两种延迟方式
Constant 固定值
Random Between Two Constant 从最小到最大中随机取值
动图5中展示了使用Start Speed为5时的速度效果
Start Size:每个粒子的初始大小,如果想分别控制每个轴的大小,请启用3D选项。可选择两种延迟方式
Constant 固定值
Random Between Two Constant 从最小到最大中随机取值
(图3.1.1-2) 展示了Start Size在1和5之间随机,可以看到左边粒子有大有小
注意:粒子的大小与它所使用的贴图大小是无关的,一个100像素的贴图与一个500像素的贴图绘制出来的同种粒子几乎差不多大,由此可见,StartSize规定了这个粒子的直径,而不是贴图缩放
Start Rotation:每个粒子的初始旋转角度。如果您想分别控制每个轴的旋转,请启用3D选项。可选择两种延迟方式
Constant 固定值
Random Between Two Constant 从最小到最大中随机取值
(图3.1.1-3) 展示了Start Rotation在1和360之间随机,可以看到左边粒子有各个方向的旋转
Start Color:每个粒子的初始颜色
(图3.1.1-4) 展示了Start Color为红色的效果
注意,即便在StartColor中规定了粒子为红色,显示出来的效果也不是纯红色,粒子与粒子,天空,红色方块叠加的地方都变成了不一样的颜色,这是因为画面的最终显示效果是由着色器调控的,我们在设置粒子材质的时候,选择的RenderingMode为Additive(叠加的),所以最终显示效果会受到粒子背后传来的光的影响从而发生变色。可以尝试选择不同的着色器选项观察变化
(图3.1.1-5) 展示了RenderingMode为Additive的效果
Gravity Modifier:设置物理重力值。零值会关闭重力
(动图6)展示了使用Gravity Modifier为5时的效果
Simulation Space:控制粒子是否跟随粒子发射器移动
Local:粒子生成后跟随粒子发射器坐标的移动而移动,这个模式下,粒子发射器的移动会表现在每个粒子上。
World:粒子生成后不跟随粒子发射器,直接在世界坐标系中移动。
Simulation Speed:调整整个粒子系统更新的速度
Scale Mode:控制粒子的缩放模式
Hierarchy(层次结构):同时受自己与父节点的缩放影响
Local(本地):只受自己的影响
Shape(形状):缩放只应用于发射形状,即影响粒子的发射位置,粒子自身的大小不受缩放影响
Max Particles:一次系统中的最大粒子数。如果达到限制,则会移除一些粒子。如果设置为1,则粒子系统会一个一个发射粒子
Auto Random Seed:自动粒子随机种子,启用后每次播放都会有不同。去掉勾选后,可以填随机种子的数值,不同的数值,发射粒子的表现略有不同
3.1.2 发射模块 Emission
Section titled “3.1.2 发射模块 Emission”该模块是粒子系统组件的一部分,用来指定发射粒子的属性。当创建新的粒子系统时,Emission 模块会默认启用。
(图3.1.2)
Enable:是否启用
Rate over Time:每秒发射的粒子数
Rate over Distance 每个移动距离单位发射的粒子数,此模式对于模拟实际由对象运动产生的粒子非常有用(例如,泥路上车轮留下的尘土)
Bursts:爆发是产生粒子的事件。这些设置允许在指定时间发射粒子。可以设置多组爆发点,分别修改时间,最小粒子数,最大粒子数
(动图7)展示了使用Rate Over Time为5,每秒发射5个粒子,同时使用一组Bursts,在第3秒时,突然发射30个粒子
3.1.3 形状模块 Shape
Section titled “3.1.3 形状模块 Shape”该模块定义了发射粒子的体积或表面,以及起始速度的方向。
(图3.1.3-1)
Shape Type:形状的选择会影响可以发射粒子的区域,还会影响粒子的初始方向。例如,一个Sphere向各个方向向外发射粒子,一个Cone发射一个发散的粒子流。
1,Sphere :球,可以向四面八方发射粒子
Radius:半径
Emit from shell:根据壳发射
Randomize Direction:随机化方向
(动图8)展示了使用球形,半径是3,从球体的边缘发射
2,Hemisphere:半球形状
Radius:半径
Emit from shell:根据壳发射
Randomize Direction:随机化方向
(图3.1.3-2)
3,cone:锥形,让粒子像手电筒的光一样从一点开始向一个圆型扇出
Angle DEG:形状的圆形方面的角度
Radius:半径
Length:长度
Emit from:发射方式
Base:基于锥形底部
Base Shell:基于锥形底部壳
Volume:基于锥形内部
Volume Shell:基于锥形内部壳
Randomize Direction:随机化方向
(动图9)展示了使用锥形,半径是2,锥形长度是6,从锥形内部发射
4,Box:盒子形,可以让所有粒子向单一方向发射,可以很好的模拟雨雪类粒子效果
Length:XYZ各个方向的长度
Randomize Direction:随机化方向
(图3.1.3-3)
5,circle:环形
Radius:半径
Angle DEG:环形的角度
Emit From Edge:基于边缘发射
Randomize Direction:随机化方向
(图3.1.3-4)
3.1.4 生命周期Lifetime
Section titled “3.1.4 生命周期Lifetime”该模块定义了发射出的粒子的生命周期内的属性
(图3.1.4)
1,Velocity over Lifetime:生命周期中的速度
Constant:常数模式,速度是恒定的
Curve:曲线模式
Random from two Constant:随机速度模式
Random between two Curve:在两个曲线中随机取值
Space:空间
Local:模型空间
World:世界空间
2,Color over Lifetime:生命周期中的颜色
Constant:常数模式,颜色是恒定的
Gradient:梯度模式
Random from two Constant:随机两个颜色模式
Random between two Gradient:在两个梯度中随机取值
3,Size over Lifetime:生命周期中的大小
Separate Axes:按轴分离
Curve:曲线模式
Random Between Two Contants:在两个常数中随机取值
Random between two Curve:在两个曲线中随机取值
4,Rotation over Lifetime:生命周期中的旋转
Separate Axes:按轴分离
Constant:常数
Curve:曲线模式
Random Between Two Contants:在两个常数中随机取值
Random between two Curve:在两个曲线中随机取值
3.1.5 纹理动画 Texture Sheet
Section titled “3.1.5 纹理动画 Texture Sheet”用来播放粒子动画的模块,粒子动画的原材料是一种纹理,它包含了一组帧动画,能够以动画方式渲染粒子。
帧动画:使用多张图片,每张图片为一帧,构成一个完整的动画叫帧动画。或一张大图片,图片中包含了一个动画的所有帧的图片
(图3.1.5-1)
(图3.1.5-2)
目前LayaAir采用网格模式(Grid)
Tiles:纹理在 X(水平)和 Y(垂直)方向上划分的图块数量
Animation:动画模式可以设置为整张或单行(即,每行代表一个单独的动画序列)
Frame:设置帧
Type:帧类型
Constant:固定帧数
Curve:一条曲线,指定动画帧如何随着时间的推移而增加。
Random Between two constant:两个固定帧数之间随机
Random Between two curve:两个曲线之间随机
Start Frame:起始帧,允许您指定粒子动画应该从哪一帧开始
Cycles:动画序列在粒子生命周期内重复的次数
(图3.1.5-3)第四节火焰示例中会介绍使用过程
3.2 粒子渲染模块 ShurikenParticleRenderer
Section titled “3.2 粒子渲染模块 ShurikenParticleRenderer”渲染器模块的设置决定了一个粒子的图像,模型,如何被其它粒子变换、着色和过度绘制。
(图3.2)
Receive Shadows:决定此系统中的粒子是否可以接收来自其它来源的阴影。只有不透明的材质才能接收阴影。
Cast Shadows:如果启用此属性,粒子系统会在投射阴影的灯光照射时创建阴影。
Scale In Lightmap:调整特定物体在最终LightMap中的像素密度。
Materials:用来渲染粒子的材质
Render Mode:如何从图形图像(或网格)生成渲染图像。
1,Billboard:将粒子渲染为广告牌,永远面向摄像机
2,Stretched Billboard:粒子面向摄像机的同时,允许使用粒子缩放
Speed Scale: 根据粒子速度设置长度
Length Scale :通过比较粒子的宽度决定粒子的长度
3,Horizontal Billboard:粒子平面平行于XZ底平面
4,Vertical Billboard:粒子在Y轴上是直立的,但是面向相机
5,Mesh:粒子是从3D网格而不是纹理渲染的
(动图)
Sorting Fudge:排序校正,使用这个将影响绘画顺序。粒子系统带有更低Sorting Fudge值,更有可能被最后绘制,从而显示在透明物体和其他粒子系统的前面
3.3 粒子着色器 PARTICLESHURIKEN
Section titled “3.3 粒子着色器 PARTICLESHURIKEN”在材质中选择Laya的particle,可以添加Laya内置的粒子着色器(PARTICLESHURIKEN),其可渲染各种粒子系统 效果。所有的粒子都是用使用的这个材质。
(图3.3)
Color:指定粒子的颜色。
Texture:指定粒子使用的纹理贴图
Alpha Test Value:透明测试开启时,当前像素根据设定条件决定是否输出颜色
Tiling Offset:获取纹理平铺和偏移
Material Render Mode:设置渲染模式
Opaque:默认设置,适用于没有透明区域的普通实体对象。
Cutout:允许创建在不透明和透明区域之间具有硬边的透明效果。在此模式下,没有半透明区域,纹理要么 100% 不透明,要么不可见。这在使用透明度来创建材料的形状(例如树叶或带孔和破烂的布)时很有用。
Transparent: 适用于渲染逼真的透明材质,例如透明塑料或玻璃。在此模式下,材质本身将采用透明度值(基于纹理的 Alpha 通道和色调颜色的 Alpha),但反射和照明高光将在完全清晰的情况下保持可见,就像真正的透明材质一样。
Additive: 叠加方式
AlphaBlended: 透明混合方式
Cull:剔除方式
四、火焰效果制作示例
Section titled “四、火焰效果制作示例”4.1 创建火焰预制体
Section titled “4.1 创建火焰预制体”
(图4.1)
在Scene3D场景下,点击鼠标右键,选择创建Effects->Particle3D 默认创建一个3D粒子系统,命名为FireEffect,拖到Assets->Prefab目录下,创建好预制体
4.2 火焰序列帧动画
Section titled “4.2 火焰序列帧动画”
(图4.2)
准备好火焰序列帧动画贴图文件,放到Assets目录下,点击贴图,勾选sRGB和Alpha Channel,TextureType依然为Default,点击Apply按钮,确保修改成功
4.3 设置火焰材质
Section titled “4.3 设置火焰材质”
(图4.3)
在Assets下创建一个材质,命名为FlameRoundYellowParticle,Shader使用Laya.Particle,基本上所有的粒子特效都使用此Shader。Color设置为191,191,191,255,texture选择上面添加的贴图,Material Render Mode选择ADDITIVE方式
4.4 设置粒子系统渲染模块
Section titled “4.4 设置粒子系统渲染模块”
(图4.4-1)
创建粒子系统后,Inspector面板中默认会添加ShurikenParticleRenderer组件,选择FlameRoundYellowParticle材质
(图4.4-2)
在Scene窗口中,可以看到粒子效果已经换成贴图,需要进一步设置贴图动画
4.5 使用贴图动画
Section titled “4.5 使用贴图动画”
(图4.5-1)
在粒子系统的TextureSheet中,创建一个Instance,由于火焰贴图的组成方式为10x5,此时修改Tiles为X:10,Y:5。修改后粒子系统贴图变为火焰效果,但是依然是静态图,下面来修改Frame帧动画,修改Frame->Type为Curve,点击Curve打开面板,横轴为时间线,纵轴为帧动画的帧数,我们希望的效果是1秒中火焰帧动画循环播放一遍,也就是从0到50帧,那么我们修改Curve为下图
(图4.5-2)
完成Curve后,再看火焰效果已经可以播放帧动画
(动图10)
4.6 设置基础属性
Section titled “4.6 设置基础属性”
(图4.6)
Start Speed的Constant为0,火焰发射时的初始速度为0,Start Size的Constant为2,放大2倍火焰的尺寸,Simulation Speed为2,可以加快火焰播放的速度
4.7 设置发射器
Section titled “4.7 设置发射器”
(图4.7)
修改每单位时间发射的粒子数为5,相当于每秒中会燃烧5个火焰
4.8 设置形状模块
Section titled “4.8 设置形状模块”
(图4.8)
我们希望粒子在一个圆形内发射,可以达到火焰聚集燃烧效果
4.9 设置粒子生命周期
Section titled “4.9 设置粒子生命周期”
(图4.9-1)
最重要一环为设置粒子生命周期,首先设置火焰生命周期内颜色的过程,创建Color Over Lifetime实例,Type设置为Gradient梯度变化曲线,打开Gradient面板,上面3个箭头向下的指示标表明颜色的透明度从 0%的不透明->80%的不透明->100%的全透明,下面的2个箭头向上的指示标表明颜色的区间变化从c99451到ff4500
(图4.9-2)
由于火焰是粒子向上运动到消失,创建Velocity Over Lifetime实例,选择Curve曲线,只需要修改Y轴的位移为1秒钟从0到1,向上移动1个单位
(图4.9-3)
由于火焰是会尺寸上有缩小的过程,创建Size Over Lifetime实例,选择Curve曲线,只需要修改0.5秒时间内,Size从1到0.5,缩小一倍
(动图11)
此时在Scene窗口中看到,火焰效果已制作完成
五、应用场景及代码示例
Section titled “五、应用场景及代码示例”往往在游戏的战斗过程中,需要大量创建粒子,那么需要用到对象池。对象池优化是游戏开发中非常重要的优化方式,也是影响游戏性能的重要因素之一。在游戏中有许多对象在不停的创建与移除,比如角色攻击子弹、特效的创建与移除,NPC的被消灭与刷新等,在创建过程中非常消耗性能,特别是数量多的情况下。对象池技术能很好解决以上问题,在对象移除消失的时候回收到对象池,需要新对象的时候直接从对象池中取出使用。优点是减少了实例化对象时的开销,且能让对象反复使用,减少了新内存分配与垃圾回收器运行的机会。
注意:对象移除时并不是立即从内存中抹去,只有认为内存不足时,才会使用垃圾回收机制清空,清空时很耗内存,很可能就会造成卡顿现象。用了对象池后将减少程序的垃圾对象,有效的提高程序的运行速度和稳定性。
5.1 自定义Particle3D类
Section titled “5.1 自定义Particle3D类”import Node = Laya.Node;import Sprite3D = Laya.Sprite3D;import ShuriKenParticle3D = Laya.ShuriKenParticle3D;import ShurikenParticleSystem = Laya.ShurikenParticleSystem;import { Pool } from "./Pool";
//粒子特效的基类,包括创建,播放,暂停,销毁,清理对象池export class Particle3D extends Sprite3D {
private _isInited: boolean = false; private _filePath: string = null; private _particle: Laya.Sprite = null; private _shuriKenParticle3D: Array<ShuriKenParticle3D>= []; private _shurikenParticleSystem: Array<ShurikenParticleSystem>= []; constructor() { super(); }
//通过传入粒子特效的路径,创建一个粒子特效,从对象池里拿一个 static Create(path: string): Particle3D { var ret:Particle3D = Pool.getInstance().getItemByClass("Particle3D@" + path, Particle3D); ret.Init(path); return ret; }
//粒子特效初始化 private Init(file_path:string): void { if (this._isInited) { return; } this._filePath = file_path;
console.log("Particle3D"); //从拿到的粒子系统克隆一个 var res = Laya.loader.getRes(file_path); var particle = res.clone();
this._particle = particle; //获取这个粒子特效的所有粒子系统,用于后面整体播放 for (var i = 0, len = this._particle.numChildren; i < len; i++) { var child:Node = this._particle.getChildAt(i); if (child instanceof Laya.ShuriKenParticle3D) { this._shuriKenParticle3D.push(child); this._shurikenParticleSystem.push(child.particleSystem); } }
this.addChild(this._particle); this._isInited = true; }
//粒子特效播放,由于一个复杂的粒子特效由多个粒子系统组成,此时遍历粒子特效所有粒子系统对象调用play() play(): void { for (var i = 0, len = this._shurikenParticleSystem.length; i < len; i++) { var particle_system = this._shurikenParticleSystem[i]; particle_system.simulate(0, true); particle_system.play(); } }
//粒子特效暂停和恢复,由于一个复杂的粒子特效由多个粒子系统组成,此时遍历粒子特效所有粒子系统对象调用pause()和play() pause(): void { for (var i = 0, len = this._shurikenParticleSystem.length; i < len; i++) { var particle_system:ShurikenParticleSystem = this._shurikenParticleSystem[i]; if (this._isPaused) { particle_system.play(); this._isPaused = false; } else { particle_system.pause(); this._isPaused = true; } } }
//粒子系统对象池回收 Recover(): void { this.removeSelf(); Pool.getInstance().recover(this._filePath, this); };
//彻底销毁清理一个粒子特效对象 Clean(): void { if (this.destroyed) { return; }
this.Recover();
if (this._particle && !this._particle.destroyed) { this._particle.removeSelf(); this._particle.destroy(true); this._particle = null; }
this._shuriKenParticle3D = null; this._shurikenParticleSystem = null;
this._isInited = false;
this.destroy(true); };
//通过传入粒子特效的路径,清除缓冲池 static ClearPool(root_path: string): void { if (root_path == null) { root_path = ""; } Pool.getInstance().ClearGroup("Particle3D@" + root_path, this, function(particle_3d:Particle3D) { particle_3d.Clean(); }); }
}5.2 自定义对象池类
Section titled “5.2 自定义对象池类”export class Pool {
private _poolDic:{[key: string]: any;} = {}; private InPoolSign: string = "__InPool";
constructor() { }
private static _instance: Pool = new Pool(); public static getInstance() { return this._instance; }
//通过名字找到对应的对象池 getPoolBySign(sign:string): any { return this._poolDic[sign] || (this._poolDic[sign] = []); };
//回收 recover(sign:string, item:any): void { item["__InPool"] = true; };
//通过名字获得一个对象,如果对象池内没有对象,则创建一个 getItemByClass(sign:string, cls:any): any { var ret = null; var pool = this.getPoolBySign(sign); for (var i = 0, len = pool.length; i < len; i++) { var item = pool[i]; if (item["__InPool"] && item instanceof cls) { ret = item; break; } } if (!ret) { ret = new cls(); pool.push(ret); } ret["__InPool"] = false; return ret; };
//通过名字,清理一组对象池 ClearGroup(head_sign:string, caller:any, func:Function): void { for (var key in this._poolDic) { if (key.indexOf(head_sign) == 0) { var pool = this._poolDic[key]; if (func) { for (var i = 0, len = pool.length; i < len; i++) { var item = pool[i]; func.call(caller, item); } } pool.length = 0; } } };
//清理所有的对象池 ClearAll(caller:any, func:Function): void { for (var key in this._poolDic) { var pool = this._poolDic[key]; if (func) { for (var i = 0, len = pool.length; i < len; i++) { var item = pool[i]; func.call(caller, item); } } pool.length = 0; } };}5.3 代码调用
Section titled “5.3 代码调用”const { regClass, property } = Laya;import { Particle3D } from "./Particle3D";
@regClass()export class Main extends Laya.Script {
//粒子特效的路径 private filePath = "FireEffect"; onStart() { console.log("Game start"); //加载粒子特效资源 Laya.loader.load(this.filePath, Handler.create(this, () => { })); }
//每次鼠标点下屏幕后,会创建一个特效 mouseDown(e: Event): void { var particle = Particle3D.Create(this.filePath); this.owner.addChild(particle); }
//鼠标抬起后,会释放对象池 mouseUp(e: Event): void { Particle3D.ClearPool(this.filePath); }}

