【AS3 Coder】任務四:噪音的魅力(上)

使用框架:AS3
任務描述:使用AS3中BitmapData的noise方法以及perlinNoise方法構建天然景觀效果以及其餘一些比較cool的效果
難度係數:2php

本文章源碼下載:www.iamsevent.com/upload/AS3Coder4/AS3Coder4_1.rarhtml

音污染是四大污染之一,被人們深惡痛絕啊,我就很討厭隔壁一大早就開始響起的裝修聲,打斷寡人的春夢,真是的,正要辦正事呢……不過,在計算機領域也存在一種噪聲,我不明白它爲何要取noise這個名字,可是咱們沒必要糾結於名字這種虛的東東(就像咱們不知道爲何要稱大便爲大便同樣),咱們只關心它能幹嘛,還有S兄此次爲何要介紹這玩意兒。
       其實在初涉Flash AS3領域的時候我就對BitmapData中的noise方法以及perlinNoise這兩個方法有好奇心,可是當時的參考資料過少,也就沒怎麼管它了。不過在09年的時候出了一本書叫作《Foundation ActionScript 3.0 Image Effects》(以下圖)的書,中文翻譯過來應該是《ActionScript3.0基礎圖像效果教程》,因爲其封面是一隻山羊,因此叫它山羊書得了。git

可是因爲一直沒有出中文版的,因此就一直沒有去看過。然而最近咱們的拉登兄在他的博客開始了義務翻譯這本書,你們有空能夠給他捧捧場,裏面還附帶了英文原版電子書以及配套源碼的下載地址哦(http://blog.sina.com.cn/s/blog_4bfac6ef0100wwyn.html)。在他的宣傳下,我去他的博客下載了英文原版看,如今的我英文水平已經提升很多,因此對於英文書籍的閱讀是沒有問題的。在看過以後發現,這本書上對於噪聲(noise)這種圖形算法的介紹是如此之詳細,讓我不禁得被吸引住了,在更加深刻了解以後我已對AS3中BitmapData類自帶的兩個噪聲生成方法noise, perlinNoise有了初步瞭解併爲它們所能建立出的美麗效果震撼不已(難怪最近總是蛋疼)。那麼今天我將帶領你們一塊兒來與我分享這種超爽的體驗。
(PS:很差意思,本教程未使用視頻方式來表達,讓一直慫恿我使用視頻形式作教程的朋友們失望了,sorry,sorry )
        先來了解一下noise的概念吧。數據噪聲(Digital Noise)是一種像素的隨機化表現,它們的亮度及顏色屬性會有無數種組合可能性,所以,一般沒法預知一副由噪聲生成的圖像的樣子是怎麼樣的。既然結果不可預料,那麼爲何咱們還須要使用噪聲來生成圖片呢?在現實生活中,沒什麼東西是完美的,若是一個東西太四四方方,形狀十分規則平整那麼咱們會以爲其不夠真實。那麼此時咱們就能夠爲圖像增長一些噪聲圖案部分以使其看起來更加接近現實。更多的時候,噪聲可讓咱們很容易地在計算機圖像中模擬一些天然效果,好比在放映老電影時大屏幕上運動的顆粒,佈滿星星的夜空等。這裏就不去探討noise算法的歷史了,接下來直接看看AS3中的BitmapData提供的noise方法的使用方式。
        在BitmapData中存在一個noise方法可以幫助咱們很直接,很快速地建立噪聲圖像。可是在BitmapData中使用noise方法不一樣於在PhotoShop中運用噪聲,在BitmapData中掉用noise方法後將會打亂整個圖像,所以若是你只想對原圖像的一小部分運用噪聲,那麼請新創建一個BitmapData實例並在它生成噪聲圖像後使用像素拷貝技術(CopyPixels、draw等)來作到。讓咱們來看看noise方法的簽名:

public function noise(randomSeed:int, low:uint = 0, high:uint = 255, channelOptions:uint = 7, grayScale:Boolean = false):void
randomSeed        要使用的隨機種子數。若是您保持使全部其餘參數不變,能夠經過改變隨機種子值來生成不一樣的僞隨機結果。雜點函數是一個映射函數,不是真正的隨機數生成函數,因此它每次都會根據相同的隨機種子建立相同的結果。 
low、high        指定要爲每一個通道生成的顏色值區間
channelOptions        指定將生成雜點的顏色通道,可用值爲BitmapDataChannel中常量
grayScale        若是該值爲 true,則會經過將全部顏色通道設置爲相同的值來建立一個灰度圖像。將此參數設置爲 true 不會影響 Alpha 通道的選擇。 

因爲參數較少,因此就不須要多解釋什麼,直接來看一個例子,這個例子是山羊書裏面的一個例子,爲咱們展現瞭如何實現電視機無信號的圖像(點擊圖片在線預覽)web

package {

        import flash.display.Bitmap;
        import flash.display.BitmapData;
        import flash.display.Sprite;
        import flash.events.Event;
        import flash.utils.getTimer;

        [SWF(width=400, height=300, backgroundColor=0x000000)]

        public class NoiseTest extends Sprite {

                private var _bitmapData:BitmapData;

                public function NoiseTest() {
                        _bitmapData = new BitmapData(stage.stageWidth, stage.stageHeight);
                        makeNoise();
                        addChild(new Bitmap(_bitmapData));
                        addEventListener(Event.ENTER_FRAME, onSpriteEnterFrame);
                }

                /**
                * 爲bitmapData生成噪聲圖像
                */
                private function makeNoise():void {
                         //第一個參數randomSeed每次取到的都不同,所以每次調用此方法都會生成
                        //一副全新的噪聲圖像。第二個、三個參數決定了像素顏色的變化範圍,以致於
                        //像素顏色不會太暗(越接近0越暗,越黑)。第四個參數保留了默認值,表示
                        //生成的隨機像素顏色包含RGB三個通道。第四個參數設置爲true,則所有像素都
                        //是通過灰化了的
                        _bitmapData.noise(getTimer(), 100, 255, 7, true);
                }

                /**
                * 每幀都生成新的一副噪聲圖像
                */
                private function onSpriteEnterFrame(event:Event):void {
                        makeNoise();
                }

        }
}

 上述代碼主要部分在於對noise各個參數的設置上,對於各個參數設置的意義我都在註釋中寫明瞭,你能夠更改一些參數來看看效果怎樣,好比把最後一個參數grayScale設置爲false,這樣將會看見你的「電視屏幕」中的雪花點變成彩色的了……值得注意的是,調用noise方法是會花很多時間來生成噪聲雜點圖像的,因此你每幀都調用noise去生成新的噪聲雜點圖像會比較消耗CPU,在這個例子中因爲雜點圖像生成目標BimapData的尺寸不大,因此CPU使用率還能夠,大概在10如下,如果你增大BitmapData的尺寸會發現CPU使用率也會水漲船高。算法

      對於noise的應用咱們就講這一個例子,由於它的使用範圍實在是狹窄,爲何狹窄?由於noise建立的雜點是平均分佈的,所以看起來哪裏都同樣。爲了建立出更加真實的天然效果,咱們可使用柏林噪聲(perlinNoise)來作到。使用柏林噪聲生成算法,咱們能夠建立出近乎「天然」的雜點。下面是山羊書中對於柏林噪聲的描述文字:數組

 Perlin 雜點生成算法會內插單個隨機雜點函數名爲 octave 並將它們組合成一個函數,該函數生成多個看起來很天然的隨機雜點。就像音樂上的八音度,每一個 octave 函數的頻率都是其前面一個 octave 函數頻率的兩倍。Perlin 雜點被描述爲「雜點的碎片總和」,由於它將多組雜點數據與不一樣級別的細節組合在一塊兒。 您可使用 Perlin 雜點函數來模擬天然現象和風景,例如,木材紋理、雲彩或山脈。在大多數狀況下,Perlin 雜點函數的輸出不會直接顯示出來,而是用於加強其餘圖像併爲其餘圖像提供僞隨機變化。 簡單的數字隨機雜點函數一般生成具備粗糙的對比度點的圖像。這種粗糙的對比度在天然界中一般是找不到的。Perlin 雜點算法混合了在不一樣的詳細級別上進行操做的多個雜點函數。此算法在相鄰的像素值間產生較小的變化。app

固然,沒有哪一個天才是一看這段抽象的描述文字就能明確地理解perlinNoise的使用方式,咱們仍是一步步地來看,首先天然是對於其參數的理解:
public function perlinNoise(baseX:Number, baseY:Number, numOctaves:uint, randomSeed:int, stitch:Boolean, fractalNoise:Boolean, channelOptions:uint = 7, grayScale:Boolean = false, offsets:Array = null):void
baseX, baseY        要在 x 、y方向上使用的頻率
numOctaves                要組合以建立此雜點的 octave 函數或各個雜點函數的數目。octave 的數目越多,建立的圖像越細膩。octave 的數目越多,須要的處理時間也會越長。
randomSeed                要使用的隨機種子數。若是您保持使全部其餘參數不變,能夠經過改變隨機種子值來生成不一樣的僞隨機結果。Perlin 雜點函數是一個映射函數,不是真正的隨機數生成函數,因此它會每次根據相同的隨機種子建立相同的結果
stitch                若是該值爲 true,則該方法將嘗試平滑圖像的轉變邊緣以建立無縫的紋理,用於做爲位圖填充進行平鋪。
fractalNoise                若是該值爲 true,則該方法將生成碎片雜點;不然,它將生成湍流。帶有湍流的圖像具備可見的不連續性漸變,可使其具備更接近銳化的視覺效果,例如火焰或海浪。 
channelOptions                雜點所用顏色通道
grayScale                是否建立灰度圖像
offsets                與每一個 octave 的 x 和 y 偏移量相對應的點數組。經過操做這些偏移量值,您能夠平滑滾動 perlinNoise 圖像的圖層。偏移數組中的每一個點將影響一個特定的 octave 雜點函數。 

      僅經過文字的解釋那是絕對不能說服我軍的,咱們只相信咱們的狗眼不是嗎?來吧,給個perlinNoise方法的參數測試結果在線試驗程序(點擊圖片打開),是老外寫的哈:框架

聰明人一看就知道,左邊是參數設置的面板,而右邊是bitmapData.perlinNoise方法根據左邊這些參數生成的圖像,你能夠拖拽這個圖像進行尺寸縮放,還能夠在上面調整背景顏色,很人性化,嗯……
      若是你仔細試驗,你會發現如下一些規則:
1.把baseX值調低則圖像會變窄,即單位長度中存在的「彩色團」數量少了,反之,則會讓圖像變寬。而對於baseY也是同樣的規則,值小則單位高度中存在的「彩色團」數量少……所以咱們能夠姑且把baseX和baseY做爲拉伸圖像的元兇
2.octaves的值越高圖像越細膩,反之則粗糙。由於octaves值決定了使用柏林噪聲算法生成的圖像層數量,層越多看起來天然越細膩,就像你過濾純淨水同樣,水的純度天然是和你過濾次數成正比的,可是次數越多消耗的時間越長
3.randomSeed的值就沒必要多說了,反之每次改變後都會出現不同的圖像,可是須要注意的是,若是你起始值是1,而後隨便改一個值後生成一副新圖像後再把randomSeed值改回1會發現生成的仍是起始狀況下的圖像,因此這就叫作「僞隨機」,即生成的不會是真正的隨機圖像。
4.stitch效果正如上面參數解釋中說的那樣,的確能建立平滑的邊緣哦,親!
5.fractalNoise爲false的狀況下生成的圖像貌似一團一團的圖像,用它來作煙霧(或者棉花?靠,你家樓下彈棉花的跟你很要好是不?是否是已經發展成基友關係了?)感受挺不錯。若此參數值爲true,則生成的圖像比較連續,如果你保持R/G/B三個ChannelOption都處於選中狀態,即三個顏色都被打開着的話生成的圖像就好像一堆顏色撲在一塊兒的一個塗鴉,不過在實際應用中咱們一般只開其中幾個通道,不會所有通道都開着,這個稍後會有具體例子的介紹哦親~
6.grayScale勾選則會讓圖像變成黑白
7.改變xOffset和yOffset會平移圖像

         那麼如今咱們已經對於perlinNoise的參數應用有了一個比較全面的瞭解了,OK,讓咱們一塊兒來作幾個實際例子吧。事實上,在幾年前咱們的Ryanliu老劉大神就已經有一個使用perlinNoise作的火焰效果了(http://uh.9ria.com/link.php?url=http://bbs.9ria.com%2Fviewthread.php%3Ftid%3D26498),從這個帖子的點擊率能夠看出perlinNoise的魅力所在,這……這不就是吸引咱們Flash編碼人員入行的一個因素麼?作出讓人看了可以雞動的東西,讓別人誇你猛實乃人生一大快事也,我哈哈~
        衆所周知的是,國外的技術發展老是比國內快不少,所以對於perlinNoise應用的探索必定也比國內多不少,我爲你們找了不少不一樣類型的應用場合,讓咱們一塊兒來享用這饕餮盛宴吧少年們!

藍天白雲效果
效果出自:http://www.flashandmath.com/intermediate/clouds/
英文好的話能夠直接閱讀原帖子哦親~不然就看貧道的接下來的翻譯吧……
實際效果(點擊圖片打開……下同,不在重複了,累了=.=):dom

很逼真是否是?我當初看到也顫抖了,是的,我確實顫抖了,不過幸虧我定力好,沒有溼。若是直接看源碼可能沒那麼好理解,那就讓咱們一步步來哈,先寫如下代碼試驗一把:函數

package
{
        import flash.display.Bitmap;
        import flash.display.BitmapData;
        import flash.display.Sprite;
        import flash.events.Event;
        import flash.geom.Point;
        
        public class CloudEffect extends Sprite
        {
                private var display:Sprite;
                private var perlinData:BitmapData;
                private var perlinBitmap:Bitmap;
                
                                
                private var displayWidth:Number;
                private var displayHeight:Number;

                
                private var periodX:Number;
                private var periodY:Number;
                private var seed:int;
                private var numOctaves:int;
                
                public function CloudEffect()
                {
                        init();
                }
                
                
                private function init():void {
                        
                        var i:int;
                        
                        display=new Sprite();
                        
                        this.addChild(display);
                        
                        displayWidth = stage.stageWidth;
                        displayHeight = stage.stageHeight;

                        
                        periodX=150;
                        periodY=150;
                        
                        numOctaves = 5;

                        perlinData = new BitmapData(displayWidth,displayHeight,true);
                        perlinBitmap = new Bitmap(perlinData);
                        
                        display.addChild(perlinBitmap);
                        
                        
                        seed = int(Math.random()*10000);

                         perlinData.perlinNoise(periodX,periodY,numOctaves,seed,false,true,7,false);
                }
                
        }
}

 上述代碼應該不難理解,就是在舞臺上添加一個bitmapData對象併爲此bitmapData對象生成柏林噪聲圖像。最終運行結果應該以下圖所示這樣:

看起來又像剛纔那個彩色塗鴉了,和咱們的親愛的藍天白雲效果相差甚遠。沒事,接下來咱們要想還有哪些地方須要改進,嗯……白雲看起來應該不會這麼連貫,上面這個圖裏面多種顏色混雜,如果我去掉其中幾個通道的顏色異或我只留下一個顏色通道會不會看起來像雲的分佈效果呢?試試看吧,把perlinNoise的第7個參數channelOption的值由默認的三通道7(這個數字是由BitmapDataChannel.RED(1) 、 BitmapDataChannel.BLUE(2)、BitmapDataChannel.GREEN(4)這三個數字經過邏輯或運算獲得的 )改爲一個通道1或者2或者4試試:

perlinData.perlinNoise(periodX,periodY,numOctaves,seed,false,true,1,false);

 

哈哈,效果果真如寡人所料,看起來有點雲的那種分佈感受了,只不過顏色有點不對,回頭想想,出現這種結果也是應該的,由於我只開了一個紅色的顏色通道。so,接下來要作的事情就是把顏色給塗成白色。說到改變像素顏色,第一個想到的工具就是ColorMatrixFilter這個濾鏡,它提供的強大圖片色彩改變功能在大多數狀況下都能知足咱們的需求(詳細使用方法可參考eko大神的文章http://uh.9ria.com/link.php?url=http://bbs.9ria.com%2Fviewthread.php%3Ftid%3D47853)。
那麼,我因爲剛纔爲perlinNoise傳入的第七個參數是1(BitmapDataChannel.RED),只開啓了紅色通道,所以我只須要在ColorMatrixFilter中設置紅色通道的RGB均爲255,透明度alpha爲1便可,見下面源碼:

        public class CloudEffect extends Sprite
        {
                ……
                private var cmf:ColorMatrixFilter;
                
                ……
                
                private function init():void {
                        
                        ……
                        
                        //將生成的雜點顏色都塗成白色,perlinNoise方法第7個參數開放的通道是哪一個
                        //就對哪一個通道設置爲徹底不透明
                        cmf = new ColorMatrixFilter([0,0,0,0,255,
                                0,0,0,0,255,
                                0,0,0,0,255,
                                1,0,0,0,0]);
                        
                        ……

                        //只開放一個顏色通道,這樣就可讓咱們的
                        //「雲」看起來是時而零散時而連貫的
                        perlinData.perlinNoise(periodX,periodY,numOctaves,seed,false,true,1,false);
                        perlinData.applyFilter(perlinData, perlinData.rect, new Point(), cmf);

                }
                
        }
}

 咱們在bitmapData生成雜點圖像後爲它用上一個ColorMatrixFilter將它開放的惟一一個通道的顏色染色爲白色。

爲了讓視覺效果愈加有Feeling,再給他加個藍色的底就更好了對不對:

package
{
        
        public class CloudEffect extends Sprite
        {
                ……
                
                private var blueBackground:Shape;
                private var skyColor:uint;
                                
                ……
                
                private function init():void {
                        
                        //藍天在我眼前
                        skyColor = 0x2255AA;
                        blueBackground = new Shape();
                        blueBackground.graphics.beginFill(skyColor);
                        blueBackground.graphics.drawRect(0,0,displayWidth,displayHeight);
                        blueBackground.graphics.endFill();
                        
                        
                        display.addChild(blueBackground);
                        display.addChild(perlinBitmap);
                                                
                        ……

                        perlinData.perlinNoise(periodX,periodY,numOctaves,seed,false,true,1,false,offsets);
                        perlinData.applyFilter(perlinData, perlinData.rect, new Point(), cmf);
                }
                
        }
}

 好了,如今應該已經看見了靜態的雲了。只差最後一步就是讓雲飄起來了。剛纔在分析perlinNoise的參數的時候咱們已經注意到它最後一個參數offsets,靠它應該能夠平移咱們的雜點圖像!

那就讓咱們試試看在ENTER_FRAME的事件處理函數裏面經過改變offsets這個perlinNoise最後一個參數的值來達到圖像移動的效果。不過在動手以前我發現這個offsets參數是一個數組Array對象,爲何是一個數組呢?以前有說過,perlinNoise的第三個參數numOctaves表明了使用柏林噪聲算法生成的雜點圖層數量,那麼因爲一個perlinNoise生成的圖像將是由numOctaves個雜點圖層組成的,所以在移動一個perlinNoise圖像的時候咱們就必須明確地指定組成它的每個雜點圖層的移動目標。因此,這個offsets參數是一個長度和numOctaves值同樣的的數組。看下面的源碼:

public class CloudEffect extends Sprite
{
……
        private var offsets:Array;
        
        private function init():void {
                
                ……
                
                //numOctaves能夠用來表示柏林圖像層的數量,有幾層圖像就建立幾個移動點
                offsets = new Array();
                for (i = 0; i<=numOctaves-1; i++) {
                        offsets.push(new Point());
                }
                
                
                this.addEventListener(Event.ENTER_FRAME, onEnter);
        }
        
        
        
        private function onEnter(evt:Event):void {
                
                var i:int;

                //移動每一層的雜點圖像
                for (i = 0; i<=numOctaves-1; i++) {
                        
                        offsets[i].x += 1;
                        offsets[i].y += 0.2;
                }
                
                //只開放一個顏色通道,這樣就可讓咱們的
                //「雲」看起來是時而零散時而連貫的
                perlinData.perlinNoise(periodX,periodY,numOctaves,seed,false,true,1,false,offsets);
                perlinData.applyFilter(perlinData, perlinData.rect, new Point(), cmf);
                
        }
}

 添加到這些代碼後運行,發現雲確實飄動起來了,飄動橫速度是1,縱速度是0.2,你能夠根據喜愛來設置飄動速度和方向。不過咱們發現一點就是運行這個程序的話CPU消耗率過大了,一度能衝到40以上(我是雙核CPU,因此單個程序的CPU最高佔有率是50),想一想也是,由於每一幀都調用perlinNoise這個方法去生成圖像,消耗不大才怪了。這樣子可不行啊,這樣還玩個P啊。固然,做者也意識到了這個效率的問題,因此他經過探索,給出了咱們另外一種思路來移動咱們的雲朵。

原文章地址:http://www.flashandmath.com/intermediate/cloudsfast/
仍是那句話,英文好的直接看原文,不然就……你懂的。
      縱觀咱們的這個飄雲的效果,咱們的「雲」通常只須要生成一次以後就不必再次生成新的了,所以咱們主要的工做仍是在於如何讓「雲」飄動起來。那麼既然經過頻繁調用perlinNoise方法來實現雲朵飄動的辦法效率低下,咱們就得繼續尋找新的可以讓圖像動起來的辦法。咱們注意到,在BitmapData類中存在一個scroll 的方法,經過這個方法可以讓一個BitmapData中的像素動起來。不過新的問題出現了,就是一旦我把整個bitmapData移動起來後那麼這些像素在移動以前的位置上不就出現了像素缺口了?換句話說,(0,0)這個點原先的顏色是白色,那麼當他x,y方向分別移動1像素後(1,1)點的顏色就變成了白色,那麼此時(0,0)點的像素就不對了,它會保持原有的顏色白色,最終結果可能就以下圖所示,拉出一條很長的軌跡來,這明顯不是咱們想要的結果:

那麼做者利用了一種相似「傳送帶」的思想,用大家地球人的一句話叫作「拆東牆補西牆」,以下圖所示:

上面兩個插圖都是引用的原文插圖,前面這張插圖表示進入新的一幀之後將有三部分:豎直切分塊(綠色部分)、水平切分塊(紅色部分)以及拐角切分塊(紫色部分)將會移出可視區域(假設整個圖像是向右下方向移動),那麼在它們移動至可視區域外以後能夠補回到左上方那些新出如今可視區域內的位置,就如後面這張圖所示。這樣子就能夠給人一種源源不斷的平滑感受,比如機場或移動扶梯的傳送帶。
        原理易懂實現犯難,這是不少人的通病,先上所有代碼再來一點點吸取:

public class MovingClouds extends Sprite {
        
        public var numOctaves:int;
        public var skyColor:uint;
        public var cloudsHeight:int;
        public var cloudsWidth:int;
        public var periodX:Number;
        public var periodY:Number;
        /** 圖像橫向滾動速度 */
        public var scrollAmountX:int;
        /** 圖像縱向滾動速度 */
        public var scrollAmountY:int;
        /** 最大滾動速度 */
        public var maxScrollAmount:int;
        
        private var cloudsBitmapData:BitmapData;
        private var cloudsBitmap:Bitmap;
        private var cmf:ColorMatrixFilter;
        private var blueBackground:Shape;
        private var displayWidth:Number;
        private var displayHeight:Number;
        private var seed:int;
        private var offsets:Array;
        /** 水平方向需置換的像素暫存於此 */
        private var sliceDataH:BitmapData;
        /** 垂直方向需置換的像素暫存於此 */
        private var sliceDataV:BitmapData;
        private var sliceDataCorner:BitmapData;
        private var horizCutRect:Rectangle;
        private var vertCutRect:Rectangle;
        private var cornerCutRect:Rectangle;
        private var horizPastePoint:Point;
        private var vertPastePoint:Point;
        private var cornerPastePoint:Point;
        private var origin:Point;
        private var cloudsMask:Shape;
        
        /**
         * @param w                雲朵渲染矩形寬度
         * @param h                雲朵渲染矩形高度
         * @param scX        雲朵滾動橫速度
         * @param scY        雲朵滾動縱速度
         * @param useBG        是否自動繪製背景
         * @param col        背景顏色
         * 
         */                
        public function MovingClouds(w:int = 300, h:int = 200, scX:int = -1, scY:int = 2, useBG:Boolean = true, col:uint = 0x2255aa) {
                
                displayWidth = w;
                displayHeight = h;
                
                cloudsWidth = Math.floor(1.5*displayWidth);
                cloudsHeight = Math.floor(1.5*displayHeight);
                periodX = periodY = 150;
                
                scrollAmountX = scX;
                scrollAmountY = scY;
                maxScrollAmount = 50;
                
                numOctaves = 5;
                
                skyColor = col;
                        
                cloudsBitmapData = new BitmapData(cloudsWidth,cloudsHeight,true);
                cloudsBitmap = new Bitmap(cloudsBitmapData);
                        
                origin = new Point(0,0);
                
                cmf = new ColorMatrixFilter([0,0,0,0,255,
                                                                         0,0,0,0,255,
                                                                         0,0,0,0,255,
                                                                         1,0,0,0,0]);
                
                
                cloudsMask = new Shape();
                cloudsMask.graphics.beginFill(0xFFFFFF);
                cloudsMask.graphics.drawRect(0,0,displayWidth,displayHeight);
                cloudsMask.graphics.endFill();
                
                if (useBG) {
                        blueBackground = new Shape();
                        blueBackground.graphics.beginFill(skyColor);
                        blueBackground.graphics.drawRect(0,0,displayWidth,displayHeight);
                        blueBackground.graphics.endFill();
                        
                        this.addChild(blueBackground);
                }
                this.addChild(cloudsBitmap);
                this.addChild(cloudsMask);
                cloudsBitmap.mask = cloudsMask;
                                                
                makeClouds();
                setRectangles();
        
                this.addEventListener(Event.ADDED_TO_STAGE, addedToStage);
        }
        
        private function addedToStage(evt:Event):void {
                this.removeEventListener(Event.ADDED_TO_STAGE, addedToStage);
                this.addEventListener(Event.REMOVED_FROM_STAGE, removedFromStage);
                this.addEventListener(Event.ENTER_FRAME, onEnter);
        }
        
        private function removedFromStage(evt:Event):void {
                this.removeEventListener(Event.REMOVED_FROM_STAGE, removedFromStage);
                this.addEventListener(Event.ADDED_TO_STAGE, addedToStage);
                this.removeEventListener(Event.ENTER_FRAME, onEnter);
        }
        
        private function setRectangles():void {
                
                //約束滾動速度
                scrollAmountX = (scrollAmountX > maxScrollAmount) ? maxScrollAmount : ((scrollAmountX < -maxScrollAmount) ? -maxScrollAmount : scrollAmountX);
                scrollAmountY = (scrollAmountY > maxScrollAmount) ? maxScrollAmount : ((scrollAmountY < -maxScrollAmount) ? -maxScrollAmount : scrollAmountY);
                
                //建立臨時存儲出界像素的bitmapData
                if (scrollAmountX != 0) {
                        sliceDataV = new BitmapData(Math.abs(scrollAmountX), cloudsHeight - Math.abs(scrollAmountY), true);
                }
                if (scrollAmountY != 0) {
                        sliceDataH = new BitmapData(cloudsWidth, Math.abs(scrollAmountY), true);
                }
                if ((scrollAmountX != 0)&&(scrollAmountY != 0)) {
                        sliceDataCorner = new BitmapData(Math.abs(scrollAmountX), Math.abs(scrollAmountY), true);
                }
                
                //建立用以管理出界像素的矩形
                horizCutRect = new Rectangle(0, cloudsHeight - scrollAmountY, cloudsWidth - Math.abs(scrollAmountX), Math.abs(scrollAmountY));
                vertCutRect = new Rectangle(cloudsWidth - scrollAmountX, 0, Math.abs(scrollAmountX), cloudsHeight - Math.abs(scrollAmountY));
                cornerCutRect = new Rectangle(cloudsWidth - scrollAmountX, cloudsHeight - scrollAmountY,Math.abs(scrollAmountX), Math.abs(scrollAmountY));
                
                //建立出界像素矩形將粘貼到的新位置
                horizPastePoint = new Point(scrollAmountX, 0);
                vertPastePoint = new Point(0, scrollAmountY);
                cornerPastePoint = new Point(0, 0);
                
                //若滾動方向爲向左,則將管理像素出界的矩形放在左邊緣
                if (scrollAmountX < 0) {
                        cornerCutRect.x = vertCutRect.x = 0;
                        cornerPastePoint.x = vertPastePoint.x = cloudsWidth + scrollAmountX;
                        horizCutRect.x = -scrollAmountX;
                        horizPastePoint.x = 0;
                }
                //若滾動方向爲向上,則將管理像素出界的矩形放在上邊緣
                if (scrollAmountY < 0) {
                        cornerCutRect.y = horizCutRect.y = 0;
                        cornerPastePoint.y = horizPastePoint.y = cloudsHeight + scrollAmountY;
                        vertCutRect.y = -scrollAmountY;
                        vertPastePoint.y = 0;
                }
                
        }
        
        private function makeClouds():void {
                seed = int(Math.random()*0xFFFFFFFF);
                
                offsets = new Array();
                for (var i:int = 0; i<=numOctaves-1; i++) {
                        offsets.push(new Point());
                }
        
                cloudsBitmapData.perlinNoise(periodX,periodY,numOctaves,seed,true,true,1,true,offsets);
                cloudsBitmapData.applyFilter(cloudsBitmapData, cloudsBitmapData.rect, new Point(), cmf);
                
        }
        
        private function onEnter(evt:Event):void {
                
                //BitmapData像素級操做的經常使用伎倆,先鎖住bitmapData讓咱們在未處理徹底部像素前
                //不渲染此bitmapData,避免讓未完成品被看見
                cloudsBitmapData.lock();
                
                //把將移除範圍的像素塊存起來
                if (scrollAmountX != 0) {
                        sliceDataV.copyPixels(cloudsBitmapData, vertCutRect, origin);
                }
                if (scrollAmountY != 0) {
                        sliceDataH.copyPixels(cloudsBitmapData, horizCutRect, origin);
                }
                if ((scrollAmountX != 0)&&(scrollAmountY != 0)) {
                        sliceDataCorner.copyPixels(cloudsBitmapData, cornerCutRect, origin);
                }
                
                //滾動全圖
                cloudsBitmapData.scroll(scrollAmountX, scrollAmountY);
                
                //將保存了的像素塊貼至新位置
                if (scrollAmountX != 0) {
                        cloudsBitmapData.copyPixels(sliceDataV, sliceDataV.rect, vertPastePoint);
                }
                if (scrollAmountY != 0) {
                        cloudsBitmapData.copyPixels(sliceDataH, sliceDataH.rect, horizPastePoint);
                }
                if ((scrollAmountX != 0)&&(scrollAmountY != 0)) {
                        cloudsBitmapData.copyPixels(sliceDataCorner, sliceDataCorner.rect, cornerPastePoint);
                }
                
                cloudsBitmapData.unlock();
        }


}

 若是你直接看原文給出的源碼,估計你很難理解,我這裏加了一點註釋幫助理解。做者如今將雲朵對象封裝成了一個單獨的類,容許咱們在建立雲朵的時候可以傳入一些設置參數,如雲朵的渲染尺寸以及移動速度等。所謂的「渲染尺寸」是咱們可以看見的雲朵尺寸,實際的雲朵尺寸應該比這個尺寸還要大一些,這裏就用到了以前我用過屢次的遮罩(mask)的功能,以下圖:

使用一個矩形遮罩可讓一個圖像只有中間一部分能被看到,那麼這樣就不會讓咱們在後臺作「像素裁剪」這種事情的時候不容易被用戶意識到,如圖中的紅球。在上面這段代碼中,原做者將雲朵的實際大小設置爲渲染尺寸的1.5倍大。以後,咱們注意到,他只調用了一次makeCloud方法,這正如以前所說,咱們的雲朵只須要建立一次就夠了,不須要建立屢次,一旦建立後咱們須要作的就是調用setRectangles方法來定義咱們每幀都要裁剪的像素區域。在setRectangles方法中的代碼是咱們須要額外用心去研究的,首先,將每幀須要移動的像素值(即移動速度)限制在-scrollAmountX/Y和scrollAmountX/Y之間,以後,按照上面給出的示意圖進行那三塊像素矩形的建立。爲了防止在裁剪過程當中出現像素遺失,須要一個第三方bitmapData對象來暫存咱們剪下來的像素塊。
      最後,偵聽ENTER_FRAME事件並在onEnter事件處理函數裏面進行像素裁剪工做,遵循的順序是:剪—> 存入第三方bitmapData對象 —> 滾動全圖 —> 將以前剪下像素貼到滾動後留下的像素缺口。
       看完了源碼,咱們就能夠在文檔類中很容易地建立一個MovingClouds對象了:

public class OptimizedCloudEffect extends Sprite
{
        public function OptimizedCloudEffect()
        {
                super();
                
                var clouds:MovingClouds = new MovingClouds(500,380, -2, 1, true);
                addChild( clouds );
        }
}

 運行結果和以前講得同樣,可是感受流暢不少,CPU使用率不足10,很是不錯的表現……

爲了讓結果更加真實,咱們能夠建立疊層雲,即兩個或多個MovingClouds對象,這樣有一種層次感:

public class OptimizedCloudEffect extends Sprite
{
        public function OptimizedCloudEffect()
        {
                super();

                //疊層雲
                var clouds1:MovingClouds = new MovingClouds(500,380,-2,3,false);
                clouds1.alpha = 0.4;
                
                var clouds2:MovingClouds = new MovingClouds(500,380,-1,1,false);
                clouds2.alpha = 1;
                
                var blueBackground:Shape = new Shape();
                blueBackground.graphics.beginFill(0x1E4A95);
                blueBackground.graphics.drawRect(0,0,500,380);
                blueBackground.graphics.endFill();

                addChild(blueBackground);
                addChild(clouds1);
                addChild(clouds2);
        }
}

 疊層雲效果以下:

相似地,若是你把雲朵顏色改爲黑色,天空底色改爲暗藍色,就能夠變成夜晚的天空,你甚至還能夠添加一個Shape做爲月亮,演示效果就不看了哈^_^

相關文章
相關標籤/搜索