Cesium官方教程10--高級粒子特效

原文地址: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循環裏內建立函數,可是強烈不推薦 這麼作。其次,咱們粒子更新函數須要訪問迭代器,經過它計算合適的旋轉偏移(根據angleiterationOffset參數計算)(這裏實際利用了js語言的閉包特性)。爲了解決這些問題,咱們建立一個輔助函數,在它內部返回了一個適合的更新函數。

解析這個 Force Function

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 中關於尾焰的粒子.

更多示例代碼:

相關文章
相關標籤/搜索