原文地址:https://cesiumjs.org/tutorials/Particle-Systems-More-Effects-Tutorial/javascript
这篇教程学习更多的效果,包括天气和火箭推动器。
若是没有学习过粒子系统基础知识,请学习这篇教程 粒子系统介绍 .html
最开始下雪的教程是来自 追踪圣诞老人项目里的实现。java
咱们即将介绍如何作下雪效果,而后怎么把下雪变为下雨效果。
咱们将给每一个粒子添加雪花图片,而后在updateParticle
函数里定义每一个粒子的移动属性和其余动态属性。git
粒子图片
关于表示粒子的图片,咱们能够从任一纯色(红,绿,白等)图片开始。咱们使用png格式,由于它支持透明度,因此透明的部分不可见。在本教程里,下面三张png图片用来建立粒子效果。最左侧的图片用来表示下雨;中间的图片用来表示下雪;右侧的图片咱们在上一教程里已经用到了。canvas
一旦咱们选定了一张图片,咱们能够在Cesium里修改他的外观,后面会解释。好比,左侧圆形粒子图片将会变成前面效果图里长长的,蓝色的更像雨滴。这个火的图片会变成绿色的树叶,黄色的电火花,甚至白色的瀑布下面的浪花和泡沫。这里须要创造力。数组
除此以外,雨雪效果里咱们设置了最开始透明度为0,最后透明度为可见性要求的值。也就是说粒子在建立后是彻底不透明的。这就是为何在粒子的生成位置不会忽然出现粒子的缘由。闭包
更新函数
使用更新函数,咱们能更自由的去控制粒子的分布、移动、以及可视化。这里能够简单的修改粒子的颜色、图片大小、生命周期等等。使用这个函数根据你需求或多或少的修改相关属性。甚至在这个函数内部能够基于它和相机的距离修改粒子属性(下面是示例代码),也能够相对某个模型或者地球去计算。dom
下面是咱们的跟新函数代码:函数
// 下雪 var snowGravityScratch = new Cesium.Cartesian3(); var snowUpdate = function(particle, dt) { snowGravityScratch = Cesium.Cartesian3.normalize(particle.position, snowGravityScratch); snowGravityScratch = Cesium.Cartesian3.multiplyByScalar(snowGravityScratch, Cesium.Math.randomBetween(-30.0, -300.0), snowGravityScratch); particle.velocity = Cesium.Cartesian3.add(particle.velocity, snowGravityScratch, particle.velocity); var distance = Cesium.Cartesian3.distance(scene.camera.position, particle.position); if (distance > (snowRadius)) { particle.endColor.alpha = 0.0; } else { particle.endColor.alpha = snowSystem.endColor.alpha / (distance / snowRadius + 0.1); } };
第一部分代码,使粒子像受重力影响同样的落下。学习
这个代码还增长一个功能,检测粒子距离相机的距离,距离越远,粒子越模糊(透明度越大),就像一种随距离加剧的雾效果。
其余天气效果
除了随着距离渐隐的粒子效果,这个示例还把 雾 和 大气效果设置成匹配 咱们正在模拟的天气效果 。
hueShift
属性控制了光谱颜色(the color along the color spectrum.)。saturationShift
属性控制了实际效果的明暗分界线(how much color versus black and white the visual actually entails)。brightnessShift
属性控制了颜色对比有多强烈(how vivid the colors are)。
雾的密度(density)属性控制了雾颜色覆盖在地球上有多浓厚。minimumBrightness属性设置了雾颜色明亮度的最小值,小于这个值让雾完全覆盖(acts as a way to darken the fog)。
scene.skyAtmosphere.hueShift = -0.8; scene.skyAtmosphere.saturationShift = -0.7; scene.skyAtmosphere.brightnessShift = -0.33; scene.fog.density = 0.001; scene.fog.minimumBrightness = 0.8;
上面的雪天,大气颜色变得更黑,几乎没有颜色;雾是很是浓的白色。
由于效果彻底不一样,咱们建立两个不一样的粒子系统
,一个模拟下雪,一个模拟下雨。
下雪
下面的代码使用一个基于中心位置(相机位置)的球体发射器去建立粒子系统。另外,每一个粒子的图片大小是随机的,在给定大小和两倍大小之间随机,这样粒子更加多种多样。
这个雪的粒子系统有下面这些 和 前面咱们讨论过的全部属性:
var snowParticleSize = scene.drawingBufferWidth / 100.0; var snowRadius = 100000.0; var snowSystem = new Cesium.ParticleSystem({ modelMatrix : new Cesium.Matrix4.fromTranslation(scene.camera.position), minimumSpeed : -1.0, maximumSpeed : 0.0, lifetime : 15.0, emitter : new Cesium.SphereEmitter(snowRadius), startScale : 0.5, endScale : 1.0, image : "../../SampleData/snowflake_particle.png", emissionRate : 7000.0, startColor : Cesium.Color.WHITE.withAlpha(0.0), endColor : Cesium.Color.WHITE.withAlpha(1.0), minimumImageSize : new Cartesian2(snowParticleSize, snowParticleSize), maximumImageSize : new Cartesian2(snowParticleSize * 2.0, snowParticleSize * 2.0), updateCallback : snowUpdate }); scene.primitives.add(snowSystem);
下雨
下雨的粒子系统和下雪的很接近,只有一点点不一样:
和下雪同样,下面的代码也是建立了一个基于中心位置(相机位置)的球体发射器的粒子系统。但是,咱们用了不一样的图片表示雨滴, circular_particle.png
,咱们把它着上蓝色,并垂直拉长跟想雨滴。和雪不太同样,图片大小不须要随机,而是和imageSize
属性一致,这里设置高度是宽度的2倍。
rainSystem = new Cesium.ParticleSystem({ modelMatrix : new Cesium.Matrix4.fromTranslation(scene.camera.position), speed : -1.0, lifetime : 15.0, emitter : new Cesium.SphereEmitter(rainRadius), startScale : 1.0, endScale : 0.0, image : "../../SampleData/circular_particle.png", emissionRate : 9000.0, startColor :new Cesium.Color(0.27, 0.5, 0.70, 0.0), endColor : new Cesium.Color(0.27, 0.5, 0.70, 0.98), imageSize : new Cesium.Cartesian2(rainParticleSize, rainParticleSize * 2), updateCallback : rainUpdate }); scene.primitives.add(rainSystem);
此外,下雨模拟的更新函数,有一个小小不一样,雨滴的下落速度比雪花的速度快多了。下面代码里咱们对重力乘了一个倍率去模拟这个速度,咱们也无需修改particle.velocity
而是直接修改particle.position
。
rainGravityScratch = Cesium.Cartesian3.normalize(particle.position, rainGravityScratch); rainGravityScratch = Cesium.Cartesian3.multiplyByScalar(rainGravityScratch, -1050.0, rainGravityScratch); particle.position = Cesium.Cartesian3.add(particle.position, rainGravityScratch, particle.position);
最后,确保整个环境和场景匹配,咱们修改大气和雾效果和下雨天匹配。下面代码修改成深蓝色天空,还有一点薄雾。
scene.skyAtmosphere.hueShift = -0.97; scene.skyAtmosphere.saturationShift = 0.25; scene.skyAtmosphere.brightnessShift = -0.4; scene.fog.density = 0.00025; scene.fog.minimumBrightness = 0.01;
若是须要了解多一些,请查看 Sandcastle中雨雪示例.
天气系统里仅仅须要一个粒子系统,为了建立火箭尾焰效果,咱们须要多个粒子系统。示例中每一个位置的一圈粒子实际是一个完整的粒子系统。也就是说咱们建立了一圈粒子系统,每一个系统发射的粒子都是从喷发位置 向外发射。这就让咱们更好的控制了总体系统的移动。一个简单的可视化调试手段是设置cometOptions.numberOfSystems
为2,设置cometOptions.colorOptions
仅仅包含两种颜色,效果就像下面的图片展现的。这样就更容易跟踪每一个系统建立的粒子的运行轨迹。
为了系统的不一样设置,咱们建立了了火箭示例和彗星示例的不一样配置数组。
var rocketSystems = []; var cometSystems = [];
此外,为了便与组织程序,同时建立了两个不一样的配置对象。一个彗星版本,另外一个是火箭版本。不一样的初始化个数,不一样的偏移位置等等配置参数致使了两个效果的巨大差别。
var cometOptions = { numberOfSystems : 100.0, iterationOffset : 0.003, cartographicStep : 0.0000001, baseRadius : 0.0005, colorOptions : [{ red : 0.6, green : 0.6, blue : 0.6, alpha : 1.0 }, { red : 0.6, green : 0.6, blue : 0.9, alpha : 0.9 }, { red : 0.5, green : 0.5, blue : 0.7, alpha : 0.5 }] }; var rocketOptions = { numberOfSystems : 50.0, iterationOffset : 0.1, cartographicStep : 0.000001, baseRadius : 0.0005, colorOptions : [{ minimumRed : 1.0, green : 0.5, minimumBlue : 0.05, alpha : 1.0 }, { red : 0.9, minimumGreen : 0.6, minimumBlue : 0.01, alpha : 1.0 }, { red : 0.8, green : 0.05, minimumBlue : 0.09, alpha : 1.0 }, { minimumRed : 1, minimumGreen : 0.05, blue : 0.09, alpha : 1.0 }] };
此外,每一个的colorOptions是一个数组,包含了随机颜色,那么效果更加随机化。这就是说不是采用一个固定的初始化颜色,而是依据当前正在建立的粒子系统的序号来决定用哪一个颜色。下面代码里,i表示当前的遍历序号。
var color = Cesium.Color.fromRandom(options.colorOptions[i % options.colorOptions.length]);
使用下面的代码初始化每一个系统
function createParticleSystems(options, systemsArray) { var length = options.numberOfSystems; for (var i = 0; i < length; ++i) { scratchAngleForOffset = Math.PI * 2.0 * i / options.numberOfSystems; scratchOffset.x += options.baseRadius * Math.cos(scratchAngleForOffset); scratchOffset.y += options.baseRadius * Math.sin(scratchAngleForOffset); var emitterModelMatrix = Cesium.Matrix4.fromTranslation(scratchOffset, matrix4Scratch); var color = Cesium.Color.fromRandom(options.colorOptions[i % options.colorOptions.length]); var force = forceFunction(options, i); var item = viewer.scene.primitives.add(new Cesium.ParticleSystem({ image : getImage(), startColor : color, endColor : color.withAlpha(0.0), particleLife : 3.5, speed : 0.00005, imageSize : new Cesium.Cartesian2(15.0, 15.0), emissionRate : 30.0, emitter : new Cesium.CircleEmitter(0.1), bursts : [ ], lifetime : 0.1, forces : force, modelMatrix : particlesModelMatrix, emitterModelMatrix : emitterModelMatrix })); systemsArray.push(item); } }
下来过一遍这个粒子系统建立函数,options
表示咱们要建立一个彗星尾焰或者火箭尾焰。就像前面提到得,systemsArray
保存了依据输入的options
建立的全部粒子系统。
两个尾焰很是类似,除了color和force以外其余的配置都相同。另外,emitterModelMatrix
也是对每一个粒子系统都彻底不一样,在咱们建立的圆环上作一个旋转偏移,那么它产生的粒子和前一个粒子系统产生的粒子会有一点点偏移。
咱们没有加载一个图片文件。而是使用 HTML canvas直接绘制了一张图片。 尽管这里咱们只是绘制一个圆圈,可是这种方法很是有扩展性。好比,给 getImage
增长一个当前遍历序号的参数,依据这个参数作一个小小的改变,那就会产生不一样的可视化效果。
既然咱们已经有了思路,那就实现这个过程。不像前面直接加载图片,咱们用代码来建立图片,使用代码还能实现更多的方法。
var particleCanvas; function getImage() { if (!Cesium.defined(particleCanvas)) { particleCanvas = document.createElement('canvas'); particleCanvas.width = 20; particleCanvas.height = 20; var context2D = particleCanvas.getContext('2d'); context2D.beginPath(); context2D.arc(8, 8, 8, 0, Cesium.Math.TWO_PI, true); context2D.closePath(); context2D.fillStyle = 'rgb(255, 255, 255)'; context2D.fill(); } return particleCanvas; }
准备好,咱们开始最关键的一步,让粒子动起来。下面的代码是咱们要在updateCallback
函数内实现的:
var func = function(particle) { scratchCartesian3 = Cesium.Cartesian3.normalize(particle.position, new Cesium.Cartesian3()); scratchCartesian3 = Cesium.Cartesian3.multiplyByScalar(scratchCartesian3, -1.0, scratchCartesian3); particle.position = Cesium.Cartesian3.add(particle.position, scratchCartesian3, particle.position); scratchCartographic = Cesium.Cartographic.fromCartesian(particle.position, Cesium.Ellipsoid.WGS84, scratchCartographic); var angle = Cesium.Math.PI * 2.0 * iterationOffset / options.numberOfSystems; iterationOffset += options.iterationOffset; scratchCartographic.longitude += Math.cos(angle) * options.cartographicStep; scratchCartographic.latitude += Math.sin(angle) * options.cartographicStep; particle.position = Cesium.Cartographic.toCartesian(scratchCartographic); };
可是,这是什么?这个函数和设置到粒子系统里的回调函数不一样。在建立粒子系统部分,咱们设置 force
参数用了 var force = forceFunction(options, i);
。这个其实调用了一个辅助函数,辅助函数内部返回了实际的更新函数。
var scratchCartesian3 = new Cesium.Cartesian3(); var scratchCartographic = new Cesium.Cartographic(); var forceFunction = function(options, iteration) { var iterationOffset = iteration; var func = function(particle) { scratchCartesian3 = Cesium.Cartesian3.normalize(particle.position, new Cesium.Cartesian3()); scratchCartesian3 = Cesium.Cartesian3.multiplyByScalar(scratchCartesian3, -1.0, scratchCartesian3); particle.position = Cesium.Cartesian3.add(particle.position, scratchCartesian3, particle.position); scratchCartographic = Cesium.Cartographic.fromCartesian(particle.position, Cesium.Ellipsoid.WGS84, scratchCartographic); var angle = Cesium.Math.PI * 2.0 * iterationOffset / options.numberOfSystems; iterationOffset += options.iterationOffset; scratchCartographic.longitude += Math.cos(angle) * options.cartographicStep; scratchCartographic.latitude += Math.sin(angle) * options.cartographicStep; particle.position = Cesium.Cartographic.toCartesian(scratchCartographic); }; return func; };
咱们这么作有两个缘由。首先,在 JavaScript语言里,虽然能够在for循环里内建立函数,可是强烈不推荐 这么作。其次,咱们粒子更新函数须要访问迭代器,经过它计算合适的旋转偏移(根据angle
和iterationOffset
参数计算)(这里实际利用了js语言的闭包特性)。为了解决这些问题,咱们建立一个辅助函数,在它内部返回了一个适合的更新函数。
updateCallback
函数和 forceFunction
函数实际都干了什么? createParticleSystems
的时候,咱们沿着圆形偏移建立了每一个粒子系统,同时咱们也但愿,当粒子从他们的初始位置移动的时候,也是也是沿着圆形偏移的方向来再偏移。
这个粒子的迭代偏移不只仅是建立了圆形旋转效果,而其是控制了这个圆形旋转效果的平滑度,就像彗星和火箭的可视化效果对比。不只仅是依据当前角度的cosine和sin计算一个位置,咱们实际上跌加了前一个位置。所以,过小的迭代迭代偏移也不足以调整这个角度,让半径稳定的变大才能保证系统的连续性。想法,更大的迭代偏移将会使角度更快的增长到原始位置。这么作才能作出来很密集形的圆柱状喷射效果。 (这块我读起来也很费解,个人理解大概是说,在火箭尾端构造了一圈粒子系统,可是这些粒子系统并非向下喷射,而是说一边向下,一边旋转,因此不一样粒子系统里的粒子实际在更新位置的时候也会考虑它在圆圈的位置,这种旋转下来的效果,让粒子看起来更密集,若是不这么作,那么这一圈不管增长多少粒子系统,老是会有一些缝隙,效果很差)
在这个教程里,咱们大量使用了sine和cosine函数来生成圆圈效果。但是,用户也能够扩展一下,作成各类形状,好比 Lissajous curve, Gibbs phenomenon, 甚至 square wave 。另外,用户也能够不用三角函数,而是基于位置的噪音函数来控制粒子的位置,那样也许更有趣。这样将是很是有创意的。
particleOffset
属性作一个细微偏移。使用
particlesModelMatrix
看成每一个系统的全局位置矩阵。如同
createParticleSystems
函数里,对于每个咱们建立的粒子系统,咱们使用
emitterModelMatrix
来体现它在发射圆圈上的相对偏移位置。
// 设置飞机的位置 var planePosition = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883, 800.0); var particlesOffset = new Cesium.Cartesian3(-8.950115473940969, 34.852766731753945, -30.235411095432937); // 设置粒子系统的相对位置 var transl = Cesium.Matrix4.fromTranslation(particlesOffset, new Cesium.Matrix4()); var translPosition = Cesium.Matrix4.fromTranslation(planePosition, new Cesium.Matrix4()); var particlesModelMatrix = Cesium.Matrix4.multiplyTransformation(translPosition, transl, new Cesium.Matrix4());
更多能够参考Sandcastle 中关于尾焰的粒子.
更多示例代码: