BitmapData粒子的优化
虽然使用BitmapData来处理粒子可以有比较高的效率,但也有进一步优化的余地。一个最简单的BitmapData粒子类如下所示:[code lang="as3"]package com.particles{
public class BitmapParticle1{
public var x:Number;
public var y:Number;
public var vx:Number;
public var vy:Number;
public function BitmapParticle1(x:Number,y:Number,vx:Number,vy:Number){
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
}
}
}[/code]
代码是十分简单,只包含了使粒子运动的属性。为了实现更好的面向对象,在BitmapData粒子中实现update方法,调用它使粒子运动。
[code lang="as3"]package com.particles{
import flash.display.BitmapData;
public class BitmapParticle2{
public var x:Number;
public var y:Number;
public var vx:Number;
public var vy:Number;
public function BitmapParticle2(x:Number,y:Number,vx:Number,vy:Number){
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
}
public function update(bitmapData:BitmapData):void{
this.x += vx;
this.y += vy;
bitmapData.setPixel(this.x,this.y,0xFFFFFF);
}
}
}[/code]
对于大多数情况而言,这种做法是十分符合逻辑的,而且也十分易读。和AS3书籍当中提倡的面向对象特性也十分吻合。下面就可以利用上面的代码,实现基本的粒子运动测试。
[code lang="as3"]package{
import com.particles.BitmapParticle2;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
[SWF(width="800",height="600")]
public class BitmapParticleTest1 extends Sprite{
private var bmp:Bitmap;
private var bmpd:BitmapData;
private var particles:Array;
public function BitmapParticleTest1(){
bmpd = new BitmapData(800,600,false,0x000000);
bmp = new Bitmap(bmpd);
addChild(bmp);
particles = new Array();
for(var i:int=0;i[/code]
效果实现:BitmapParticleTest1
对于以上代码在效率方面主要有以下问题:
1 BitmapData的rect属性并不是静态只读的
[code lang="as3"]bmpd.fillRect(bmpd.rect,0x000000);[/code]
这意味着每次查询BitmapData的rect属性,都需要重新实例化一个新的rect。而实例化在AVM2中是比较耗费资源的。
2 数组
用数组来作为保存和访问粒子的方式,可以说是代价作为昂贵的了
[code lang="as3"]particles = new Array();[/code]
将粒子保存到无类型的数组中是问题所在。当然可以用Vector对象来保存粒子,使用简单的数据结构,比如链表,可能是更合适的方式。这样修改我们的粒子类如下:
[code lang="as3"]package com.particles{
import flash.display.BitmapData;
final public class BitmapParticle3{
public var x:Number;
public var y:Number;
public var vx:Number;
public var vy:Number;
public var next:BitmapParticle3;
public function BitmapParticle3(x:Number,y:Number,vx:Number,vy:Number){
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
}
public function update(bitmapData:BitmapData):void{
this.x += vx;
this.y += vy;
bitmapData.setPixel(this.x,this.y,0xFFFFFF);
}
}
}[/code]
注意到我们只是在粒子类中增加了next属性。接下来修改粒子的测试类:
[code lang="as3"]package{
import com.particles.BitmapParticle2;
import com.particles.BitmapParticle3;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
[SWF(width="800",height="600")]
public class BitmapParticleTest2 extends Sprite{
private var bmp:Bitmap;
private var bmpd:BitmapData;
private var first:BitmapParticle3;
public function BitmapParticleTest2(){
bmpd = new BitmapData(800,600,false,0x000000);
bmp = new Bitmap(bmpd);
addChild(bmp);
var p:BitmapParticle3;
var previous:BitmapParticle3;
for(var i:int=0;i[/code]
使用链表代替数组后的效果:BitmapParticleTest2
3 final修饰符
使用final修饰符可以禁止粒子类被继承,虽然在Flash Player 10.1中性能的提高比之前的版本要少,但也是一处值得优化的地方。
4 inline
注意到测试类的主循环中,使用了面向对象方式来更新粒子的位置。在这里提到它,并不是由于使用面向对象的方式不正确,而是在主循环中调用的是一个函数,而函数调用在AVM2中的代价是昂贵的。而使用inline的方式来实现,由于编译时已经知道具体的代码,所以有性能上的优势。测试类修改如下:
[code lang="as3"]var p : Particle = first;
while (p) {
p.x += p.vx;
p.y += p.velY;
bitmapData.setPixel(p.x,p.y,0xFFFFFF);
p = p.next;
}[/code]
5 setPixel()方法
BitmapData的setPixel方法可能是最灵活的方法之一,但使用Vector对象的方式整体更新整张位图,可能是效率最高的方式了。将测试类修改如下:
[code lang="as3"]package{
import __AS3__.vec.Vector;
import com.particles.BitmapParticle4;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Rectangle;
[SWF(width="800",height="600")]
public class BitmapParticleTest3 extends Sprite{
private var bmp:Bitmap;
private var bmpd:BitmapData;
private var rect:Rectangle;
private var first:BitmapParticle4;
public function BitmapParticleTest3(){
bmpd = new BitmapData(800,600,false,0x000000);
rect = bmpd.rect;
bmp = new Bitmap(bmpd);
addChild(bmp);
var p:BitmapParticle4;
var previous:BitmapParticle4;
for(var i:int=0;i = bmpd.getVector(rect);
var w:int = rect.width;
var h:int = rect.height;
var p:BitmapParticle4 = first;
while(p){
p.x += p.vx;
p.y += p.vy;
if(p.x > w || p.x < 0){ p.x = p.startX; } if(p.y > h || p.y < 0){
p.y = p.startY;
}
vec[int(w*int(p.y)+int(p.x))] = 0xFFFFFFFF;
p = p.next;
}
bmpd.setVector(rect,vec);
bmpd.unlock();
}
}
}[/code]
40000个粒子的效果如下所示:
<img title="BitmapParticle2" src="http://www.everyinch.net/wp-content/uploads/2011/09/BitmapParticle2-300x224.jpg" alt="" width="300" height="224" />
BitmapData粒子的基本代码优化完毕。接下来增加两个花样。
首先,使用PerlinNoise生成纹理从而改变粒子的速度。
[code lang="as3"]package{
import __AS3__.vec.Vector;
import com.particles.BitmapParticle4;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Shader;
import flash.display.Sprite;
import flash.events.Event;
import flash.filters.ShaderFilter;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.utils.ByteArray;
[SWF(width="800",height="600")]
public class BitmapParticleTest6 extends Sprite{
private static const WIDTH:int = 800;
private static const HEIGHT:int = 600;
[Embed(source="assets/CentralDifference.pbj",mimeType="application/octet-stream")]
private static var velocityField:Class;
private var velocityVector:Vector.;
private var bmp:Bitmap;
private var bmpd:BitmapData;
private var rect:Rectangle;
private var first:BitmapParticle4;
public function BitmapParticleTest6(){
var perlinMap:BitmapData = new BitmapData(WIDTH,HEIGHT,false,0x000000);
perlinMap.perlinNoise(128,128,6,123456,true,false,7,true);
var shader:Shader = new Shader(new velocityField() as ByteArray);
var filter:ShaderFilter = new ShaderFilter(shader);
var velocityMap:BitmapData = perlinMap.clone();
velocityMap.applyFilter(perlinMap,perlinMap.rect,new Point(),filter);
velocityVector = velocityMap.getVector(velocityMap.rect);
perlinMap.dispose();
velocityMap.dispose();
bmpd = new BitmapData(WIDTH,HEIGHT,false,0x000000);
rect = bmpd.rect;
bmp = new Bitmap(bmpd);
addChild(bmp);
var p:BitmapParticle4;
var previous:BitmapParticle4;
for(var i:int=0;i = bmpd.getVector(rect);
var w:int = rect.width;
var h:int = rect.height;
var p:BitmapParticle4 = first;
var pos:int;
while(p){
p.x += p.vx;
p.y += p.vy;
if(p.x > w || p.x < 0){ p.x = p.startX; } if(p.y > h || p.y < 0){ p.y = p.startY; } pos = int(w*int(p.y)+int(p.x)); vec[pos] = 0xFFFFFFFF; var velocity:int = velocityVector[pos]; var vx:int = ((velocity & 0xFF00) >> 8) - 127.5;
var vy:int = (velocity & 0xFF) - 127.5;
p.vx += vx * 0.03;
p.vy += vy * 0.03;
p.vx *= 0.99;
p.vy *= 0.99;
p = p.next;
}
bmpd.setVector(rect,vec);
bmpd.unlock();
}
}
}[/code]
然后,使用一张位图的颜色值来改变粒子的速度,代码如下:
[code lang="as3"]package{
import __AS3__.vec.Vector;
import com.particles.BitmapParticle4;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Shader;
import flash.display.Sprite;
import flash.events.Event;
import flash.filters.ShaderFilter;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.utils.ByteArray;
[SWF(width="800",height="600",frameRate="31")]
public class BitmapParticleTest7 extends Sprite{
private static const WIDTH:int = 800;
private static const HEIGHT:int = 600;
[Embed(source="assets/image.jpg")]
private static var image:Class;
[Embed(source="assets/CentralDifference.pbj",mimeType="application/octet-stream")]
private static var velocityField:Class;
private var velocityVector:Vector.;
private var bmp:Bitmap;
private var bmpd:BitmapData;
private var rect:Rectangle;
private var first:BitmapParticle4;
public function BitmapParticleTest7(){
var imageMap:BitmapData = new image().bitmapData;
var shader:Shader = new Shader(new velocityField() as ByteArray);
var filter:ShaderFilter = new ShaderFilter(shader);
var velocityMap:BitmapData = imageMap.clone();
velocityMap.applyFilter(imageMap,imageMap.rect,new Point(),filter);
velocityVector = velocityMap.getVector(velocityMap.rect);
imageMap.dispose();
velocityMap.dispose();
bmpd = new BitmapData(WIDTH,HEIGHT,false,0x000000);
rect = bmpd.rect;
bmp = new Bitmap(bmpd);
addChild(bmp);
var p:BitmapParticle4;
var previous:BitmapParticle4;
for(var i:int=0;i = bmpd.getVector(rect);
var w:int = rect.width;
var h:int = rect.height;
var p:BitmapParticle4 = first;
var pos:int;
while(p){
p.x += p.vx;
p.y += p.vy;
if(p.x > w || p.x < 0){ p.x = p.startX; } if(p.y > h || p.y < 0){ p.y = p.startY; } pos = int(w*int(p.y)+int(p.x)); vec[pos] = 0xFFFFFFFF; var velocity:int = velocityVector[pos]; var vx:int = ((velocity & 0xFF00) >> 8) - 127.5;
var vy:int = (velocity & 0xFF) - 127.5;
p.vx += vx * 0.03;
p.vy += vy * 0.03;
p.vx *= 0.99;
p.vy *= 0.99;
p = p.next;
}
bmpd.setVector(rect,vec);
bmpd.unlock();
}
}
}[/code]
<img title="BitmapParticle3" src="http://www.everyinch.net/wp-content/uploads/2011/09/BitmapParticle3-300x224.jpg" alt="" width="300" height="224" />