接下來我們該構建帶有貼圖的顯示對象了。
為了清楚的闡釋問題,我們還是從最基本的程序開始,在本博的第一篇文章中介紹了最基本的構建一個四邊形的程序,現在我們對其進行稍作修改即可讓其顯示貼圖:
1 package test 2 { 3 import com.adobe.utils.AGALMiniAssembler; 4 import com.adobe.utils.PerspectiveMatrix3D; 5 6 import flash.display.Bitmap; 7 import flash.display.Sprite; 8 import flash.display.Stage3D; 9 import flash.display3D.Context3D; 10 import flash.display3D.Context3DProgramType; 11 import flash.display3D.Context3DTextureFormat; 12 import flash.display3D.Context3DVertexBufferFormat; 13 import flash.display3D.IndexBuffer3D; 14 import flash.display3D.Program3D; 15 import flash.display3D.VertexBuffer3D; 16 import flash.display3D.textures.Texture; 17 import flash.events.Event; 18 import flash.geom.Matrix3D; 19 20 public class TexturePlane2DTest extends Sprite 21 { 22 private var stage3D:Stage3D; 23 private var context3D:Context3D; 24 private var vertexBuffer:VertexBuffer3D; 25 private var indexBuffer:IndexBuffer3D; 26 27 [Embed(source="../texture/flower.png")] 28 private var Flower:Class; 29 30 public function TexturePlane2DTest() 31 { 32 super(); 33 stage?onAddToStage(null): 34 35 addEventListener(Event.ADDED_TO_STAGE,onAddToStage); 36 } 37 38 private function onAddToStage(e:Event):void 39 { 40 stage3D = stage.stage3Ds[0]; 41 stage3D.addEventListener(Event.CONTEXT3D_CREATE,onContext3DCreated); 42 stage3D.requestContext3D(); 43 } 44 45 private function onContext3DCreated(e:Event):void 46 { 47 context3D = stage3D.context3D; 48 context3D.configureBackBuffer(stage.stageWidth,stage.stageHeight,2,true); 49 vertexBuffer = context3D.createVertexBuffer(4,5);//只要五個數據 50 indexBuffer = context3D.createIndexBuffer(6); 51 52 var vertexData:Vector.<Number>; 53 var indexData:Vector.<uint> ; 54 /** 1 - 2 55 * | / | 56 * 0 - 3 57 **/ 58 vertexBuffer.uploadFromVector(Vector.<Number>([ 59 -1,-1,5, 0,1, 60 -1,1, 5, 0,0, 61 1,1, 5, 1,0, 62 1,-1,5, 1,1 63 ]),0,4); 64 indexBuffer.uploadFromVector(Vector.<uint>([ 65 0,1,2,2,3,0 66 ]),0,6); 67 68 context3D.setVertexBufferAt(0,vertexBuffer,0,Context3DVertexBufferFormat.FLOAT_3); 69 context3D.setVertexBufferAt(1,vertexBuffer,3,Context3DVertexBufferFormat.FLOAT_2); 70 71 var flower:Bitmap = new Flower() as Bitmap; 72 var texture:Texture = context3D.createTexture(flower.width,flower.height,Context3DTextureFormat.BGRA,true); 73 texture.uploadFromBitmapData(flower.bitmapData,0); 74 context3D.setTextureAt(0,texture); 75 76 var vertexAssembler:AGALMiniAssembler = new AGALMiniAssembler(); 77 var fragmentAssembler:AGALMiniAssembler = new AGALMiniAssembler(); 78 var vertexSrc:String = "m44 op,va0,vc0 \n" + 79 "mov v0,va1"; 80 81 var fragmentSrc:String = "tex ft0,v0,fs0 <2d,nearest> \n" + 82 "mov oc,ft0"; 83 vertexAssembler.assemble(Context3DProgramType.VERTEX,vertexSrc); 84 fragmentAssembler.assemble(Context3DProgramType.FRAGMENT,fragmentSrc); 85 86 var program:Program3D = context3D.createProgram(); 87 program.upload(vertexAssembler.agalcode,fragmentAssembler.agalcode); 88 context3D.setProgram(program); 89 90 addEventListener(Event.ENTER_FRAME,onEnterFrame); 91 } 92 private var modelView:Matrix3D = new Matrix3D(); 93 94 private function onEnterFrame(e:Event):void 95 { 96 context3D.clear(0,0,0,1); 97 98 var pm:PerspectiveMatrix3D = new PerspectiveMatrix3D(); 99 pm.identity(); 100 pm.perspectiveFieldOfViewLH(1,stage.stageWidth/stage.stageHeight,1,10000); 101 context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,pm,true); 102 103 context3D.drawTriangles(indexBuffer); 104 context3D.present(); 105 } 106 } 107 }
變化的部分我已經突出顯示。可以看出與之前的程序不同的是,我們用紋理坐標(兩個數據)取代了原來的顏色值(三個數據),因此一個頂點最多需要5個數據。
另外一個關鍵的改變是在片段着色器中我們加入了一個新的運算:
1 "tex ft0,v0,fs0 <2d,nearest> \n" 2 "mov oc,ft0";
tex是紋理采樣運算,第一行的運算是將紋理采樣寄存器fs0中的數據采樣到臨時紋理寄存器ft0中,括號里的是一組flag,用來底層要如何采樣。更多。
顯示結果:
總體來說,這不算一件困難的事。但是當我們要將這些加入2d引擎中,就需要考慮的更多了。最關鍵的一點是紋理貼圖有其針對的片段着色器程序,這同普通的顏色填充的顯示對象是不能共享的,因此需要創建不同的program。
在starling2d中,靜態紋理貼圖對象Image被處理成Quad的子類,換句話說,Image只是一個貼了位圖數據的四邊形。這樣的處理有一個好處就是,他和普通的Quad對象公用一個頂點緩沖,因此只需一次上傳操作,從而避免了額外的效率開銷。
一下是我們簡單構建的Image類:
1 package psw2d.display 2 { 3 import psw2d.texture.Texture; 4 /** 5 * 6 * @author joe 7 * 8 */ 9 public class Image extends Quad 10 { 12 private var _texture:Texture;
14 public function Image(texture:Texture) 15 { 16 if(texture) 17 { 18 var w:Number = texture.width; 19 var h:Number = texture.height; 20 21 super(w,h,0xFFFF0000); 22 23 _vertexData.setTexCoords(0,0.0,0.0); 24 _vertexData.setTexCoords(1,1.0,0.0); 25 _vertexData.setTexCoords(2,1.0,1.0); 26 _vertexData.setTexCoords(3,0.0,1.0); 27 } 28 else 29 { 30 throw ArgumentError("參數不能為空!"); 31 } 34 _texture = texture; 35 } 36 37 public function get texture():Texture { return _texture; } 38 } 39 }
這里面我們省略了許多staling中的細節,為的是突出核心問題。與Image類相關的兩個類分別為QuadVertex和Texture。其中QuadVertex需要在原有的基礎之上修改:
1 package psw2d 2 { 3 import flash.geom.Matrix; 4 5 public class QuadVertex 6 { 7 public static const ELEMENTS_PER_VERTEX:int = 8;//每個頂點的數據個數 8 public static const POSSION_OFFSET:int =0;//位置坐標的偏移量 9 public static const COLOR_OFFSET:int = 2;//顏色的偏移量 10 public static const TEX_COORDS_OFFSET:int = 6;//紋理坐標的偏移量 11 12 private var _rawData:Vector.<Number>; 13 14 public function QuadVertex() 15 { 16 _rawData = new Vector.<Number>(4*ELEMENTS_PER_VERTEX,true); 17 } 18 19 public function setPosition(index:uint,x:Number,y:uint):void 20 { 21 var offset:int = ELEMENTS_PER_VERTEX * index + POSSION_OFFSET; 22 _rawData[offset] = x; 23 _rawData[offset+1] = y; 24 } 25 26 public function setTexCoords(index:uint,u:Number,v:uint):void 27 { 28 var offset:int = ELEMENTS_PER_VERTEX * index + TEX_COORDS_OFFSET; 29 _rawData[offset] = u; 30 _rawData[offset+1] = v; 31 } 32 33 public function setColor(index:uint,color:uint):void 34 { 35 var offset:int = ELEMENTS_PER_VERTEX * index + COLOR_OFFSET; 36 var alpha:Number = (color >> 24 ) & 0xFF; 37 var r:Number = (color >> 16) & 0xFF; 38 var g:Number = (color >> 8) & 0xFF; 39 var b:Number = (color & 0xFF); 40 41 r/=0xFF; 42 g/=0xFF; 43 b/=0xFF; 44 alpha/=0xFF; 45 46 _rawData[offset] = r; 47 _rawData[offset+1] = g; 48 _rawData[offset+2] = b; 49 _rawData[offset+3] = alpha; 50 } 51 52 public function get rawData():Vector.<Number> 53 { 54 return _rawData; 55 } 56 57 public function set rawData(value:Vector.<Number>):void 58 { 59 _rawData = value; 60 } 61 62 public function transformVertex(modelMatix:Matrix):void 63 { 64 var x:Number,y:Number; 65 for(var i:int=0; i<4; ++i) 66 { 67 x = _rawData[i*ELEMENTS_PER_VERTEX]; 68 y = _rawData[i*ELEMENTS_PER_VERTEX+1]; 69 _rawData[i*ELEMENTS_PER_VERTEX] = modelMatix.a * x + modelMatix.c * y + modelMatix.tx; 70 _rawData[i*ELEMENTS_PER_VERTEX+1] = modelMatix.b * x + modelMatix.d * y + modelMatix.ty; 71 } 72 } 73 74 public function copyTo(target:QuadVertex):void 75 { 76 for(var i:uint;i<_rawData.length;++i) 77 { 78 target.rawData[i] = _rawData[i]; 79 } 80 } 81 } 82 }
修改的部分已經突出顯示。首先我們定義了幾個常量,我們看到現在每個頂點的數據個數已經增加到了8個,前兩位為坐標x,y,接下來是顏色值r,g,b,a,最后是紋理坐標u,v。另外,增加了一個設置紋理坐標的方法:setTexCoords(index:uint,u:Number,v:Number):void。
至於Texture類則是一個新的類,它是一個抽象類(在starling中,這個類除了作為抽象基類之外,還作為創建紋理的工廠來使用):
1 package psw2d.texture 2 { 3 import flash.display3D.Context3DTextureFormat; 4 import flash.display3D.textures.Texture 5 6 public class Texture 7 { 8 private var isRepeat:Boolean; 9 10 public function Texture() 11 { 12 } 13 public function uploadData():void {} 14 15 public function get repeat():Boolean { return isRepeat; } 16 17 public function get width():Number { return 0; } 18 19 public function get height():Number { return 0; } 20 21 public function get format():String { return Context3DTextureFormat.BGRA; } 22 23 public function get base():flash.display3D.textures.Texture { return null; } 24 25 public function get mipMapping():Boolean { return false; } 26 } 27 }
可以看到,它很簡單,在本篇文章中,需要着重注意兩個方法一個是uploadData(),另一個則是base屬性。
針對於位圖數據我們構建了Texture的一個子類BitmapTexture:
1 package psw2d.texture 2 { 3 import flash.display.Bitmap; 4 import flash.display.BitmapData; 5 import flash.display3D.Context3DTextureFormat; 6 import flash.display3D.textures.Texture 7 8 public class BitmapTexture extends psw2d.texture.Texture 9 { 10 private var _data:BitmapData; 11 private var _mipMapping:Boolean; 12 private var _width:Number; 13 private var _height:Number; 14 private var _base:flash.display3D.textures.Texture; 15 16 public function BitmapTexture(base:flash.display3D.textures.Texture,data:*,mipMapping:Boolean) 17 { 18 if(data is Bitmap) 19 { 20 _data = (data as Bitmap).bitmapData; 21 } 22 else if(data is BitmapData) 23 { 24 _data = data as BitmapData; 25 } 26 else 27 { 28 throw "無效的位圖數據格式!"; 29 } 30 _base = base; 31 _mipMapping = mipMapping; 32 _width = _data.width; 33 _height = _data.height; 34 } 35 override public function uploadData():void 36 { 37 _base.uploadFromBitmapData(_data,0); 38 } 39 override public function get base():flash.display3D.textures.Texture { return _base; } 40 override public function get format():String { return Context3DTextureFormat.BGRA; } 41 override public function get width():Number { return _width; } 42 override public function get height():Number { return _height; } 43 override public function get mipMapping():Boolean { return _mipMapping; } 44 } 45 }
它接受一個原生的Texture對象和一個Bitmap或者BitmapData的數據做為參數。uploadData()方法實際是調用原生Texture的uploaFromBitmapData方法來講位圖數據上傳至顯卡。
至此紋理部分我們基本准備好了,雖然表面上我們做了很多,但是所有的這些依然都是圍繞一個渲染流程在走。而渲染紋理和渲染顏色填充的根本區別就是:顏色填充只是單純的將顏色搬到頂點輸出中(並計算中間差值,一般為線性),即:
1 “mov oc,v0”
而紋理渲染,則要多考慮一個因素,那就是紋理坐標,雖然也是將數據搬到oc中,但是它首先是根據紋理坐標在紋理數據上找到相應的位置,再把該位置的數據搬到輸出中:
"tex ft0,v1,fs0 <2d,nearest> \n" + "mov oc,ft0";
因此相對於之前的Quad渲染,Image的渲染要做的只是改變片段着色器程序(當然,相對於Quad,Image多了紋理和紋理坐標的數據,這也需要考慮進去),因此我們可以將QuadRender修改一下:
1 package psw2d 2 { 3 import com.adobe.utils.AGALMiniAssembler; 4 5 import flash.display3D.Context3D; 6 import flash.display3D.Context3DBlendFactor; 7 import flash.display3D.Context3DCompareMode; 8 import flash.display3D.Context3DProgramType; 9 import flash.display3D.Context3DVertexBufferFormat; 10 import flash.display3D.Program3D; 11 12 import psw2d.display.Image; 13 import psw2d.texture.Texture; 14 15 public class ImageRender extends QuadRender 16 { 17 private var texture:Texture; 18 19 public function ImageRender(context3D:Context3D) 20 { 21 super(context3D); 22 } 23 24 public function addImage(image:Image):Image 25 { 26 texture = image.texture; 27 texture.uploadData(); 28 return addQuad(image) as Image; 29 } 30 31 override public function rebuildBuffer():void 32 { 33 super.rebuildBuffer(); 34 _context3D.setTextureAt(0,texture.base); 35 _context3D.setVertexBufferAt(2,_vertexBuffer,QuadVertex.TEX_COORDS_OFFSET,Context3DVertexBufferFormat.FLOAT_2); 36 } 37 38 override public function setProgram():void 39 { 40 var vertexSrc:String = "m44 op,va0,vc0 \n" + 41 "mov v0,va1 \n" + 42 "mov v1,va2"; 43 var fragmentSrc:String = "tex ft0,v1,fs0 <2d,nearest> \n" + 44 "mov oc,ft0"; 45 var vertexAssembler:AGALMiniAssembler = new AGALMiniAssembler(); 46 var fragmentAssembler:AGALMiniAssembler = new AGALMiniAssembler(); 47 vertexAssembler.assemble(Context3DProgramType.VERTEX,vertexSrc); 48 fragmentAssembler.assemble(Context3DProgramType.FRAGMENT,fragmentSrc); 49 var program:Program3D = _context3D.createProgram(); 50 program.upload(vertexAssembler.agalcode,fragmentAssembler.agalcode); 51 _context3D.setProgram(program); 52 } 53 54 override public function render():void 55 { 56 setProgram(); 57 rebuildBuffer(); 58 _context3D.drawTriangles(_indexBuffer,0,_quads.length * 2); 59 60 _context3D.setTextureAt(0,null); 61 _context3D.setVertexBufferAt(0,null); 62 _context3D.setVertexBufferAt(1,null); 63 _context3D.setVertexBufferAt(2,null); 64 } 65 66 } 67 }
事實上我們是繼承了QuadRender來創建了ImageRender,在新的類中,我們增加了addImage方法,重寫了rebuildBuffer(),setProgram()和render()方法,特別需要注意的是render()方法,在重寫之后,我們去掉了context3D.clear()和context3D.present()(我們將它轉移到QuadTest中),並增加了一些清除數據的操作,這一點很重要。你會發現這里有很多的不合理,但現在我們的目的只是先讓Image顯示出來。
為了讓顏色填充的四邊形和貼圖的四邊形能夠同時顯示,我們也需要對QuadRender做同ImageRender類似的調整,這里就不把代碼貼出來了,可以從源碼中下載。
現在來看QuadTest類:
1 package test 2 { 3 import flash.display.Bitmap; 4 import flash.display.Sprite; 5 import flash.display.Stage3D; 6 import flash.display3D.Context3D; 7 import flash.display3D.Context3DTextureFormat; 8 import flash.events.Event; 9 10 import psw2d.ImageRender; 11 import psw2d.QuadRender; 12 import psw2d.display.Image; 13 import psw2d.display.Quad; 14 import psw2d.texture.BitmapTexture; 15 import psw2d.texture.Texture; 16 17 public class QuadTest extends Sprite 18 { 19 private var stage3D:Stage3D; 20 private var context3D:Context3D; 21 private var qRender:QuadRender; 22 private var imageRender:ImageRender; 23 24 [Embed(source="../texture/flower.png")] 25 private var Flower2:Class; 26 27 public function QuadTest() 28 { 29 stage?onAddToStage(null): 30 addEventListener(Event.ADDED_TO_STAGE,onAddToStage); 31 } 32 33 private function onAddToStage(e:Event):void 34 { 35 stage3D = stage.stage3Ds[0]; 36 stage3D.addEventListener(Event.CONTEXT3D_CREATE,onContext3DCreated); 37 stage3D.requestContext3D(); 38 } 39 private var q:Quad; 40 private var q2:Quad; 41 private var image:Image; 42 43 private function onContext3DCreated(e:Event):void 44 { 45 context3D = stage3D.context3D; 46 //關閉深度測試,,如果想要開啟,必須調用setDepthTest接口來開啟alpha混合 47 context3D.configureBackBuffer(stage.stageWidth,stage.stageHeight,2,true); 48 49 qRender = new QuadRender(context3D); 50 51 q = new Quad(100,100,0x7FFF0000); 52 q.x = 100; 53 q.y = 100; 54 qRender.addQuad(q); 55 q2 = new Quad(100,100,0x7F00FF00); 56 q2.x = 100; 57 q2.y = 100; 58 qRender.addQuad(q2); 59 60 var flower:Bitmap = new Flower2() as Bitmap; 61 imageRender = new ImageRender(context3D); 62 var tex:Texture = new BitmapTexture( 63 context3D.createTexture(flower.width,flower.height,Context3DTextureFormat.BGRA,false), 64 flower,false) as Texture; 65 image = new Image(tex); 66 image.x = 200; 67 image.y = 200; 68 imageRender.addImage(image); 69 imageRender.setMatrix(stage.stageWidth,stage.stageHeight); 70 71 qRender.setMatrix(stage.stageWidth,stage.stageHeight);//只需要執行一次設置正交投影矩陣 72 73 addEventListener(Event.ENTER_FRAME,onEnterFrame); 74 } 75 private function onEnterFrame(e:Event):void 76 { 77 context3D.clear(0,0,0,1); 78 79 q.x++; 80 q2.x+=0.5; 81 82 image.x++; 83 84 qRender.render(); 85 imageRender.render(); 86 87 context3D.present(); 88 } 89 } 90 }
在這個類中我們實例化了一個ImageRender和一個Image,並將后者添加到前者中,重點看onEnterFrame方法,將clear()方法和present()方法轉移到了這里,這也就是說QuadRender和ImageRender所做的其實只是在緩沖里面繪制,最后在所有的Render渲染結束之后,統一調用present()方法將緩沖數據上傳到顯示中。
最后給出一個Demo圖: