stage3D 搭建2d圖形引擎 (四)靜態文理貼圖


接下來我們該構建帶有貼圖的顯示對象了。

為了清楚的闡釋問題,我們還是從最基本的程序開始,在本博的第一篇文章中介紹了最基本的構建一個四邊形的程序,現在我們對其進行稍作修改即可讓其顯示貼圖:

  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圖:

源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM