BitmapData粒子的优化

二维粒子 everyinch 3691℃ 0评论

虽然使用BitmapData来处理粒子可以有比较高的效率,但也有进一步优化的余地。一个最简单的BitmapData粒子类如下所示:

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;
		}
	}
}

代码是十分简单,只包含了使粒子运动的属性。为了实现更好的面向对象,在BitmapData粒子中实现update方法,调用它使粒子运动。

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);
		}
	}
}

对于大多数情况而言,这种做法是十分符合逻辑的,而且也十分易读。和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

效果实现:BitmapParticleTest1
对于以上代码在效率方面主要有以下问题:
1 BitmapData的rect属性并不是静态只读的

bmpd.fillRect(bmpd.rect,0x000000);

这意味着每次查询BitmapData的rect属性,都需要重新实例化一个新的rect。而实例化在AVM2中是比较耗费资源的。
2 数组
用数组来作为保存和访问粒子的方式,可以说是代价作为昂贵的了

particles = new Array();

将粒子保存到无类型的数组中是问题所在。当然可以用Vector对象来保存粒子,使用简单的数据结构,比如链表,可能是更合适的方式。这样修改我们的粒子类如下:

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);
		}
	}
}

注意到我们只是在粒子类中增加了next属性。接下来修改粒子的测试类:

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

使用链表代替数组后的效果:BitmapParticleTest2
3 final修饰符
使用final修饰符可以禁止粒子类被继承,虽然在Flash Player 10.1中性能的提高比之前的版本要少,但也是一处值得优化的地方。
4 inline
注意到测试类的主循环中,使用了面向对象方式来更新粒子的位置。在这里提到它,并不是由于使用面向对象的方式不正确,而是在主循环中调用的是一个函数,而函数调用在AVM2中的代价是昂贵的。而使用inline的方式来实现,由于编译时已经知道具体的代码,所以有性能上的优势。测试类修改如下:

var p : Particle = first;
while (p) {
     p.x += p.vx;
     p.y += p.velY;
     bitmapData.setPixel(p.x,p.y,0xFFFFFF);
     p = p.next;
}

5 setPixel()方法
BitmapData的setPixel方法可能是最灵活的方法之一,但使用Vector对象的方式整体更新整张位图,可能是效率最高的方式了。将测试类修改如下:

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();
		}
	}
}

40000个粒子的效果如下所示:

BitmapData粒子的基本代码优化完毕。接下来增加两个花样。
首先,使用PerlinNoise生成纹理从而改变粒子的速度。

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();
		}
	}
}

然后,使用一张位图的颜色值来改变粒子的速度,代码如下:

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();
		}
	}
}

分享&收藏

转载请注明:陈童的博客 » BitmapData粒子的优化

喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
'; } if( dopt('d_footcode_b') ) echo dopt('d_footcode'); ?>