【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做为月亮,演示效果就不看了哈^_^

相关文章
相关标签/搜索