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


原文地址:https://cesiumjs.org/tutorials/Particle-Systems-More-Effects-Tutorial/

高級粒子系統特效

這篇教程學習更多的效果,包括天氣和火箭推進器。
如果沒有學習過粒子系統基礎知識,請學習這篇教程 粒子系統介紹 .

天氣

 
下雪
 
下雨

最開始下雪的教程是來自 追蹤聖誕老人項目里的實現。

步驟

我們即將介紹如何做下雪效果,然后怎么把下雪變為下雨效果。
我們將給每個粒子添加雪花圖片,然后在updateParticle函數里定義每個粒子的移動屬性和其他動態屬性。

粒子圖片
關於表示粒子的圖片,我們可以從任一純色(紅,綠,白等)圖片開始。我們使用png格式,因為它支持透明度,所以透明的部分不可見。在本教程里,下面三張png圖片用來創建粒子效果。最左側的圖片用來表示下雨;中間的圖片用來表示下雪;右側的圖片我們在上一教程里已經用到了。

 
粒子圖片

一旦我們選定了一張圖片,我們可以在Cesium里修改他的外觀,后面會解釋。比如,左側圓形粒子圖片將會變成前面效果圖里長長的,藍色的更像雨滴。這個火的圖片會變成綠色的樹葉,黃色的電火花,甚至白色的瀑布下面的浪花和泡沫。這里需要創造力。

除此之外,雨雪效果里我們設置了最開始透明度為0,最后透明度為可見性要求的值。也就是說粒子在創建后是完全不透明的。這就是為什么在粒子的生成位置不會突然出現粒子的原因。

更新函數
使用更新函數,我們能更自由的去控制粒子的分布、移動、以及可視化。這里可以簡單的修改粒子的顏色、圖片大小、生命周期等等。使用這個函數根據你需求或多或少的修改相關屬性。甚至在這個函數內部可以基於它和相機的距離修改粒子屬性(下面是示例代碼),也可以相對某個模型或者地球去計算。

下面是我們的跟新函數代碼:

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

更多示例代碼:





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM