stage3D 搭建2d图形引擎 (二) 四边形的大小,缩放和移动
前一篇文章简单地构建了一个四边形,当然这个四边形还不完善,接下来我们将继续完善它。
我们看到,1,这个四边形的尺寸并不是真正的视觉尺寸,2,还有无法通过改变四边形的坐标来移动它,3,也不能对他进行缩放。下面我们就从这三个方面来完善它。
首先是尺寸,我们希望在给四边形指定的宽高数值就是他在屏幕上显示的像素大小。我们知道我们在屏幕上看到的图像实际上是经过一个矩形变换过的,在QuadRender的setMatrix方法中:
1 var pm:PerspectiveMatrix3D = new PerspectiveMatrix3D(); 2 pm.perspectiveFieldOfViewLH(1,1,1,10000); 3 _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,pm,true);
我们创建了一个透视矩阵,并给他设定了一些数据。但实际上这是没有必要的,透视矩阵是在3D空间中才会发挥作用的东西,因为3D空间中有z轴方向这一自由度,但这一个自由度在2D空间中被固定死,所以我们认为这个自由度不存在,实际上我们需要的只是一个普通的正交投影矩阵,所谓正交投影,说白了其实就是忽略三维中的一维,取一个物体的某个剖面。我们将setMatrix方法修改如下:
1 public function setMatrix(sW:Number,sH:Number):void 2 { 3 var pm:Matrix3D = new Matrix3D(Vector.<Number>( 4 [ 5 2/sW,0,0,0, 6 0,-2/sH,0,0, 7 0,0,0,0, 8 1,-1,0,1 9 ])); 10 _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,pm,true); 11 }
其中sW,sH是通过外部传递的参数,代表舞台的宽和高,而我们知道一个宽高都为2(-1,1)的四边形在不经过任何变化之后投影到屏幕上时,应该是正好充满整个屏幕的,因此为了将其转换成2个像素的大小需要除以舞台的宽高。
至此矩形的尺寸问题就解决了。另外我们还调整了2D空间的y轴方向(-2/sH),以及舞台的原点(最后一行的1,-1)。这样渲染出来的显示对象同传统的flash显示对象就有了相同的特征了。
接下来,我们讨论三角形的移动问题。
我们知道能够影响显示对象的位置的有两个因素,一个是顶点数据另一个是变换矩阵,但是考虑到不同的显示对象可能具有不同的变换矩阵,所以无法通过统一的变换矩阵来处理移动问题。事实上,我们是通过变换矩阵来处理初始化给定的顶点数据最后形成新的顶点数据来达到改变显示对象的位置的目的的。同时矩阵不仅能处理位置,还能处理缩放,于是我们将Quad类修改如下:
1 package psw2d 2 { 3 import flash.geom.Matrix; 4 5 /** 6 * 0 - 1 7 * | / | 8 * 3 - 2 9 * @author Physwf 10 * 11 */ 12 public class Quad 13 { 14 private var _x:Number; 15 private var _y:Number; 16 private var _width:Number; 17 private var _height:Number; 18 private var _scaleX:Number = 1; 19 private var _scaleY:Number = 1; 20 private var _color:uint; 21 22 private var _modelMatrix:Matrix; 23 private var _isMatrixDirty:Boolean = false; 24 25 private var _vertexData:QuadVertex; 26 27 public function Quad(w:Number,h:Number,color:uint=0) 28 { 29 _x = 0; 30 _y = 0; 31 _width = w; 32 _height = h; 33 _color = color; 34 35 _vertexData = new QuadVertex(); 36 _vertexData.setPosition(0,0,0); 37 _vertexData.setPosition(1,_width,0); 38 _vertexData.setPosition(2,_width,_height); 39 _vertexData.setPosition(3,0,_height); 40 _vertexData.setColor(0,0xFF0000); 41 _vertexData.setColor(1,0x00FF00); 42 _vertexData.setColor(2,0x0000FF); 43 _vertexData.setColor(3,0xFF00FF); 44 45 _modelMatrix = new Matrix(); 46 } 47 48 public function set x(value:Number):void 49 { 50 if(_x==value) return; 51 _x = value; 52 _isMatrixDirty = true; 53 } 54 55 public function get x():Number 56 { 57 return _x; 58 } 59 60 public function get y():Number 61 { 62 return _y; 63 } 64 65 public function set y(value:Number):void 66 { 67 if(_y == value) return; 68 _y = value; 69 _isMatrixDirty = true; 70 } 71 72 public function get width():Number 73 { 74 return _width * _scaleX; 75 } 76 77 public function set width(value:Number):void 78 { 79 if(width == value) return; 80 _scaleX = value / _width; 81 _isMatrixDirty = true; 82 } 83 84 public function get height():Number 85 { 86 return _height * _scaleY; 87 } 88 89 public function set height(value:Number):void 90 { 91 if(height == value) return; 92 _scaleY = value / _height; 93 _isMatrixDirty = true; 94 } 95 96 public function get color():uint 97 { 98 return _color; 99 } 100 101 public function set color(value:uint):void 102 { 103 _color = value; 104 } 105 106 public function get scaleX():Number 107 { 108 return _scaleX; 109 } 110 111 public function set scaleX(value:Number):void 112 { 113 if(_scaleX == value) return; 114 _scaleX = value; 115 _isMatrixDirty = true; 116 } 117 118 public function get scaleY():Number 119 { 120 return _scaleY; 121 } 122 123 public function set scaleY(value:Number):void 124 { 125 if(_scaleY == value) return; 126 _scaleY = value; 127 _isMatrixDirty = true; 128 } 129 130 public function get modelMatrix():Matrix 131 { 132 if(_isMatrixDirty) 133 { 134 _modelMatrix.identity(); 135 _isMatrixDirty = false; 136 _modelMatrix.translate(_x,_y); 137 _modelMatrix.scale(_scaleX,_scaleY); 138 } 139 return _modelMatrix; 140 } 141 142 public function get vertexData():QuadVertex 143 { 144 return _vertexData; 145 } 146 } 147 }
新的类中我们增加了_modelMatrix和_vertexData两个字段,前者是显示对象的模型矩阵,所谓模型矩阵指的是依附于模型的,同模型的位置尺寸形状相关的一个矩阵。后一个字段是对顶点数据的封装,他是QuadVertex类型:
1 package psw2d 2 { 3 import flash.geom.Matrix; 4 5 public class QuadVertex 6 { 7 public static const ELEMENTS_PER_VERTEX:int = 6; 8 private var _rawData:Vector.<Number>; 9 10 public function QuadVertex() 11 { 12 _rawData = new Vector.<Number>(4*ELEMENTS_PER_VERTEX,true); 13 } 14 15 public function setPosition(index:uint,x:Number,y:uint):void 16 { 17 var offset:int = ELEMENTS_PER_VERTEX * index; 18 _rawData[offset] = x; 19 _rawData[offset+1] = y; 20 _rawData[offset+2] = 0; 21 } 22 23 public function setColor(index:uint,color:uint):void 24 { 25 var offset:int = ELEMENTS_PER_VERTEX * index; 26 27 var r:uint = (color&0xFF0000)>>16; 28 var g:uint = (color&0x00FF00)>>8; 29 var b:uint = (color&0x0000FF); 30 r/=0xFF; 31 g/=0xFF; 32 b/=0xFF; 33 _rawData[offset+3] = r; 34 _rawData[offset+4] = g; 35 _rawData[offset+5] = b; 36 } 37 38 public function get rawData():Vector.<Number> 39 { 40 return _rawData; 41 } 42 43 public function set rawData(value:Vector.<Number>):void 44 { 45 _rawData = value; 46 } 47 48 public function transformVertex(modelMatix:Matrix):void 49 { 50 var x:Number,y:Number; 51 for(var i:int=0; i<4; ++i) 52 { 53 x = _rawData[i*ELEMENTS_PER_VERTEX]; 54 y = _rawData[i*ELEMENTS_PER_VERTEX+1]; 55 _rawData[i*ELEMENTS_PER_VERTEX] = modelMatix.a * x + modelMatix.c * y + modelMatix.tx; 56 _rawData[i*ELEMENTS_PER_VERTEX+1] = modelMatix.b * x + modelMatix.d * y + modelMatix.ty; 57 } 58 } 59 60 public function copyTo(target:QuadVertex):void 61 { 62 for(var i:uint;i<_rawData.length;++i) 63 { 64 target.rawData[i] = _rawData[i]; 65 } 66 } 67 } 68 }
这个类目前封装不是很好,建议参考Starling中的VertexData类。
接下来需要修改QuadRender类:
1 package psw2d 2 { 3 import com.adobe.utils.AGALMiniAssembler; 4 import com.adobe.utils.PerspectiveMatrix3D; 5 6 import flash.display3D.Context3D; 7 import flash.display3D.Context3DProgramType; 8 import flash.display3D.Context3DVertexBufferFormat; 9 import flash.display3D.IndexBuffer3D; 10 import flash.display3D.Program3D; 11 import flash.display3D.VertexBuffer3D; 12 import flash.geom.Matrix3D; 13 14 public class QuadRender 15 { 16 private var _context3D:Context3D; 17 private var _quads:Vector.<Quad>; 18 19 private var _vertexBuffer:VertexBuffer3D; 20 private var _indexBuffer:IndexBuffer3D; 21 private var _vertexData:QuadVertex; 22 23 public function QuadRender(context3D:Context3D) 24 { 25 _context3D = context3D; 26 _quads = new Vector.<Quad>(); 27 } 28 29 public function addQuad(quad:Quad):Quad 30 { 31 _quads.push(quad); 32 rebuildBuffer(); 33 return quad; 34 } 35 36 public function rebuildBuffer():void 37 { 38 _vertexBuffer && _vertexBuffer.dispose(); 39 _indexBuffer && _indexBuffer.dispose(); 40 var numQuads:uint = _quads.length; 41 if(!numQuads) return; 42 var vertexData:Vector.<Number>=new Vector.<Number>(); 43 var indexData:Vector.<uint>=new Vector.<uint>(); 44 _vertexData = new QuadVertex(); 45 for(var i:int=0;i<numQuads;++i) 46 { 47 _quads[i].vertexData.copyTo(_vertexData) 48 _vertexData.transformVertex(_quads[i].modelMatrix); 49 trace(_quads[i].modelMatrix); 50 vertexData = vertexData.concat(_vertexData.rawData); 51 indexData.push(i*4+0,i*4+1,i*4+2,i*4+0,i*4+2,i*4+3); 52 } 53 _vertexBuffer = _context3D.createVertexBuffer(numQuads * 4,6); 54 _indexBuffer = _context3D.createIndexBuffer(numQuads * 6); 55 56 _vertexBuffer.uploadFromVector(vertexData,0,numQuads * 4); 57 _indexBuffer.uploadFromVector(indexData,0,indexData.length); 58 59 _context3D.setVertexBufferAt(0,_vertexBuffer,0,Context3DVertexBufferFormat.FLOAT_3); 60 _context3D.setVertexBufferAt(1,_vertexBuffer,3,Context3DVertexBufferFormat.FLOAT_3); 61 } 62 63 public function setMatrix(sW:Number,sH:Number):void 64 { 65 var pm:Matrix3D = new Matrix3D(Vector.<Number>( 66 [ 67 2/sW,0,0,0, 68 0,-2/sH,0,0, 69 0,0,0,0, 70 -1,1,0,1 71 ])); 72 _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,pm,true); 73 } 74 75 public function setProgram():void 76 { 77 var vertexSrc:String = "m44 op,va0,vc0 \n" + 78 "mov v0,va1"; 79 var fragmentSrc:String = "mov oc,v0"; 80 var vertexAssembler:AGALMiniAssembler = new AGALMiniAssembler(); 81 var fragmentAssembler:AGALMiniAssembler = new AGALMiniAssembler(); 82 vertexAssembler.assemble(Context3DProgramType.VERTEX,vertexSrc); 83 fragmentAssembler.assemble(Context3DProgramType.FRAGMENT,fragmentSrc); 84 var program:Program3D = _context3D.createProgram(); 85 program.upload(vertexAssembler.agalcode,fragmentAssembler.agalcode); 86 _context3D.setProgram(program); 87 } 88 89 public function render():void 90 { 91 rebuildBuffer(); 92 _context3D.clear(); 93 _context3D.drawTriangles(_indexBuffer); 94 _context3D.present(); 95 } 96 } 97 }
改变的地方已经标示出来。值得说明的是,由于需要及时的知道现实对象的信息,所以我们在帧循环里面重复的重建缓冲,重建的过程是,先拷贝原始的顶点数据在将这一份拷贝通过变换矩阵进行变换得到一份合成的顶点数据,然后将合成数据上传到顶点缓冲之中,绘制渲染。
接下来,在QuadTest的onEnterFrame()方法中增加一句代码:
1 private function onEnterFrame(e:Event):void 2 { 3 q.x++; 4 qRender.render(); 5 }
便可以让四边形动起来。