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