Metaball(元球)效果學習

來源:互聯網
上載者:User

幾年前就在網上曾看見過這種效果,但一直不知道叫什麼名字

 

前一陣無意在9ria(天地會)論壇上看到了一篇專門講這個的文章:AS3 元球(Metaball),不過有點遺憾的是那篇文章上的代碼直接複製下來都不能調試,花了點時間整理了一下,終於調試通過了,貼在這裡分享一下:

Metaball的公式:

其中,x、y是舞台上的任意一個點,x0、y0是metaball的位置,R為半徑。從公式上看,可以理解為萬有引力的變種(即引力與距離成反比,與半徑與正比)

先定義一個Metaball類(註:相對於原文而言,增加了vx,vy速度變數,用於後面示範運動的效果):

package {public class Metaball {public var radius:Number;public var x:int;public var y:int;public var original_radius:Number;public var vx:Number;//x軸速度public var vy:Number;//y軸速度public function Metaball(x:Number,y:Number,radius:Number) {this.x=x;this.y=y;this.original_radius = radius;//儲存原始半徑this.radius=radius*radius;//設定為radius的二次方,有助於後面最佳化效能  }public function equation(tx:Number,ty:Number):Number {//Metaball公式的體現: return radius/((x-tx)*(x-tx)+(y-ty)*(y-ty));//因為之前Radius已經是平方值了,這裡就不再用平方根處理(就是上面提到的最佳化效能的解釋)}}}

接下來是如何運用:

var sW:Number=stage.stageWidth;//舞台寬度var sH:Number=stage.stageHeight;//舞台高度var canvas:BitmapData=new BitmapData(sW,sH,false,0xff000000);//預設產生一個黑背景的BitmapDatavar rect:Rectangle=canvas.rect;//canvas的矩形地區var pt:Point=new Point(rect.left,rect.top);//rect的左上頂點var blurFilter:BlurFilter=new BlurFilter(10,10);//定義一個模糊濾鏡var metaballs:Array = new Array();//用於儲存n個metaball的數組var ballNumber:uint=5;//小球數目var i:uint=0;//迴圈變數var minThreshold:int=0x000009;//最小閾值var maxThreshold:int=0x000020;//最大閾值var bitMap:Bitmap = new Bitmap();//最終用來顯示的位元影像對象var isHollow:Boolean=false;//是否空心圖形function init() {for (i=0; i<ballNumber; i++) {var b:Metaball=new Metaball(Math.random()*sW,Math.random()*sH,20 + Math.random()*80);if (b.x>sW-b.original_radius) {b.x=sW-b.original_radius;} else if (b.x < b.original_radius) {b.x=b.original_radius;}if (b.y>sH-b.original_radius) {b.y=sH-b.original_radius;} else if (b.y<b.original_radius) {b.y=b.original_radius;}b.vx = (Math.random()*2-1)*2;b.vy = (Math.random()*2-1)*2;metaballs.push(b);}addChild(bitMap);addEventListener(Event.ENTER_FRAME,enterFrameHandler);}function enterFrameHandler(e:Event):void {for (i=0; i<ballNumber; i++) {var b:Metaball=metaballs[i];b.x+=b.vx;b.y+=b.vy;if (b.x>=sW-b.original_radius) {b.x=sW-b.original_radius;b.vx*=-1;} else if (b.x<b.original_radius) {b.x=b.original_radius;b.vx*=-1;}if (b.y>=sH-b.original_radius) {b.y=sH-b.original_radius;b.vy*=-1;} else if (b.y<b.original_radius) {b.y=b.original_radius;b.vy*=-1;}}canvas.dispose();canvas = new BitmapData(sW,sH,false,0xff000000);canvas.lock();canvas.floodFill(0,0,0);var sum:Number=0;for (var ty:int = 0; ty < stage.stageHeight; ty++) {for (var tx:int = 0; tx < stage.stageWidth; tx++) {sum=0;for (var i:int = 0; i < metaballs.length; i++) {sum+=metaballs[i].equation(tx,ty);}if (! isHollow) {if (sum>=minThreshold) {canvas.setPixel(tx, ty, 0xFFFFFF);}} else {if (sum>=minThreshold&&sum<=maxThreshold) {canvas.setPixel(tx, ty, 0xFFFFFF);}}}}canvas.applyFilter(canvas,rect,pt,blurFilter);canvas.unlock();bitMap.bitmapData=canvas;}init();

 

大概原理就是根據公式遍曆舞台上的每個像素點,得到一個計算值,如果該值在指定的閾值之間,就設定為白色。

空心Metaball:


線上示範

實心Metaball:


線上示範

正如大家所看到的,效果雖然不錯,但是運行效率也是極低的,因為要逐像素處理。

如何提高效能?這個就得藉助上一篇裡剛學過的PixelBender,原文的作者已經幫我們寫了一個PixdelBender的指令碼:

<languageVersion : 1.0;>kernel Metaballs<   namespace : "com.rocketmandevelopment";vendor : "Rocketman Development";version : 1;description : "Fast Metaballs";>{parameter float minThreshold<minValue:float(0.0);maxValue:float(2.0);defaultValue:float(0.9);description: "minThreshold";>;//no max threshold because I want these balls completely filledparameter float3 ball1<//this is where it gets odd. Pixel bender with flash doesn't support loops, so you must pass in each ball individually. This limits the amount of balls to the amount defined in the filterminValue:float3(0.0,0.0,0.0); //storing the data is also odd. We'll use a float3, which is like an array of 3 float values.maxValue:float3(640.0,480.0,50.0); //this first is the x, second is y, and the third is radius.defaultValue:float3(50.0,50.0,20.0);description: "ball1, params, x,y,radius";>;parameter float3 ball2<minValue:float3(0.0,0.0,0.0);maxValue:float3(640.0,480.0,50.0);//this example only supports two ballsdefaultValue:float3(100.0,100.0,20.0);description: "ball2, params, x,y,radius";>;input image4 src;output pixel4 dst;voidevaluatePixel(){dst = sampleNearest(src,outCoord());dst.rbg = float3(0,0,0);//sets the current pixel to black so the image is cleared before redrawingfloat2 coord = outCoord(); //get the coordinate of the pixelfloat sum = 0.0; //get the sum and set it to 0sum += (ball1.z)/((ball1.x-coord.x)*(ball1.x-coord.x)+(ball1.y-coord.y)*(ball1.y-coord.y)); //add to the sum using the formula from the first examplesum += (ball2.z)/((ball2.x-coord.x)*(ball2.x-coord.x)+(ball2.y-coord.y)*(ball2.y-coord.y));if(sum >= minThreshold){dst.rgb = float3(255,255,255); //set it to black if its within the threshold}}}

藉助於PixelBender Toolkit可以將它匯出為flash所需要的二進位檔案metall.pbj,然後在Flash中測試一把:

package {import flash.display.Sprite;import flash.events.Event;import flash.filters.BlurFilter;import flash.filters.ShaderFilter;import flash.utils.ByteArray;import flash.display.Bitmap;import flash.display.BitmapData;import flash.display.Shader;import flash.display.ShaderPrecision;[SWF(height=300,width=300)]public class PixelBenderMetaballs extends Sprite {[Embed(source='metaball.pbj',mimeType='application/octet-stream')]private static const MetaballsFilter:Class;private const canvas:BitmapData=new BitmapData(300,300,false,0);private const blur:BlurFilter=new BlurFilter(10,10,1);private var metaballsFilter:ShaderFilter;private var b1:Metaball;private var b2:Metaball;private var b:Bitmap;public function PixelBenderMetaballs() {b=new Bitmap(canvas);b.smoothing=true;addChild(b);var ba:ByteArray = new MetaballsFilter() as ByteArray;var s:Shader=new Shader(ba);metaballsFilter=new ShaderFilter(s);metaballsFilter.shader.data.src.image=canvas;b1=new Metaball(100,100,Math.random()*20 + 30);b2=new Metaball(150,150,Math.random()*20 + 30);b1.vx = (Math.random()*2-1)*5;b1.vy = (Math.random()*2-1)*5;b2.vx = (Math.random()*2-1)*5;b2.vy = (Math.random()*2-1)*5;metaballsFilter.shader.data.ball1.value=[b1.x,b1.y,b1.radius];metaballsFilter.shader.data.ball2.value=[b2.x,b2.y,b2.radius];metaballsFilter.shader.precisionHint=ShaderPrecision.FAST;b.filters=[metaballsFilter];addEventListener(Event.ENTER_FRAME, enterFrameHandler);}private function enterFrameHandler(e:Event):void {b1.x+=b1.vx;b1.y+=b1.vy;checkWalls(b1);b2.x += b2.vx;b2.y += b2.vy;checkWalls(b2);metaballsFilter.shader.data.ball1.value=[b1.x,b1.y,b1.radius];metaballsFilter.shader.data.ball2.value=[b2.x,b2.y,b2.radius];b.filters=[metaballsFilter,blur];}private function checkWalls(ball:Metaball):void {var sw:Number=stage.stageWidth;var sh:Number=stage.stageHeight;var adjust:uint=15;if (ball.x>sw-ball.original_radius-adjust) {ball.x=sw-ball.original_radius-adjust;ball.vx*=-1;} else if (ball.x < ball.original_radius + adjust) {ball.x=ball.original_radius+adjust;ball.vx*=-1;}if (ball.y>sh-ball.original_radius-adjust) {ball.y=sh-ball.original_radius-adjust;ball.vy*=-1;} else if (ball.y<ball.original_radius+adjust) {ball.y=ball.original_radius+adjust;ball.vy*=-1;}}}}


線上示範

很明顯,現在看上去流暢多了。

上面提到的都是極其精確的標準做法,如果要求不高,其實這種效果可以直接用Bitmap + 模糊濾鏡來模似(不過看上去效果有點假),大概原理就直接把二個圓形進行重疊,然後把最終的(並集)圖形邊緣模糊處理。(該方法是從一位老外的部落格上看到的)

var ballNum:uint=5;var balls:Array = new Array();var sW:Number=stage.stageWidth;var sH:Number=stage.stageHeight;var container:Sprite = new Sprite();var bmd:BitmapData=new BitmapData(sW,sH,false,0x00000000);var bitmap:Bitmap;var i:uint=0;var rect:Rectangle=new Rectangle(0,0,sW,sH);var pt:Point=new Point(0,0);var filter:BlurFilter=new BlurFilter(10,10);function init() {for (i=0; i<ballNum; i++) {var b:Ball=new Ball(15+Math.random()*20,0xffffff);balls.push(b);b.x = (sW - b.width)*Math.random() + b.radius;b.y = (sH - b.width)*Math.random() + b.radius;b.vx=(Math.random()*2-1)*1;b.vy=(Math.random()*2-1)*1;container.addChild(b);}bmd.draw(container);bmd.applyFilter(bmd, rect, pt, filter);bitmap=new Bitmap(bmd);addChild(bitmap);addEventListener(Event.ENTER_FRAME,enterFrameHandler);}function enterFrameHandler(e:Event):void {for (i=0; i<ballNum; i++) {var b:Ball=balls[i];b.x+=b.vx;b.y+=b.vy;var adjust:uint=5;if (b.x>=sW-b.radius-adjust) {b.x=sW-b.radius-adjust;b.vx*=-1;} else if (b.x<b.radius+adjust) {b.x=b.radius+adjust;b.vx*=-1;}if (b.y>=sH-b.radius-adjust) {b.y=sH-b.radius-adjust;b.vy*=-1;} else if (b.y<b.radius+adjust) {b.y=b.radius+adjust;b.vy*=-1;}}bmd.dispose();bmd=new BitmapData(sW,sH,false,0x00000000);bmd.draw(container);bmd.applyFilter(bmd, rect, pt, filter);bitmap.bitmapData=bmd;}init();


線上示範

文中所用原始碼下載:http://cid-2959920b8267aaca.office.live.com/self.aspx/Flash/metaball.rar

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.