stage3D 搭建2d图形引擎 (一) 从四边形开始
在上一篇文章中我们看到,为了绘制一个四边形,我们从最基本的API开始,按部就班,虽然最终达到了目的,但是很难想象如何将其应用于更加复杂的表现中。
但是即使再复杂的表现,回过头一想,也逃不出上一篇文章中的基本步骤:
创建环境-> 创建数据缓冲和矩阵数据-> 上传顶点数据和矩阵数据-> 编写渲染器(shader)->绘制呈现
在这一过程中,真正影响表现的丰富程度的就是顶点数据和矩阵数据这一块(现阶段暂不考虑渲染器),为了能够拥有丰富的表现能力,我们需要将数据这一块分离出去,然他们能够灵活变化。当然需要考虑的是如何让底层API及时的知道他们的变化,并且尽可能低降程序运行的成本,提高运行效率,但需要明白的是表现的丰富程度是和效率之间本身是有矛盾的,需要我们具体去权衡。
为了能够把问题阐释清楚,我们还是从最基本的四边形开始。
在上一篇文章中,我们在指定四边形的数据时,是手工输入的,基本上是同纯数字打交道,而事实上我们完全可以用面向对象的思想来考虑这一问题,即构建一个四边形对象:
1 package psw2d 2 { 3 /** 4 * 1 - 2 5 * | / | 6 * 0 - 3 7 * @author Physwf 8 * 9 */ 10 public class Quad 11 { 12 private var _x:Number; 13 private var _y:Number; 14 private var _width:Number; 15 private var _height:Number; 16 private var _color:uint; 17 18 public function Quad(w:Number,h:Number,color:uint=0) 19 { 20 _x = 0; 21 _y = 0; 22 _width = w; 23 _height = h; 24 _color = color; 25 } 26 27 public function set x(value:Number):void 28 { 29 _x = value; 30 } 31 32 public function get x():Number 33 { 34 return _x; 35 } 36 37 public function get y():Number 38 { 39 return _y; 40 } 41 42 public function set y(value:Number):void 43 { 44 _y = value; 45 } 46 47 public function get width():Number 48 { 49 return _width; 50 } 51 52 public function set width(value:Number):void 53 { 54 _width = value; 55 } 56 57 public function get height():Number 58 { 59 return _height; 60 } 61 62 public function set height(value:Number):void 63 { 64 _height = value; 65 } 66 67 public function get color():uint 68 { 69 return _color; 70 } 71 72 public function set color(value:uint):void 73 { 74 _color = value; 75 } 76 77 78 } 79 }
在上面的代码中,我们给四边简单地形规定了几个属性:位置(x,y)尺寸(width,height)和颜色color。
通过这个类,我们很容易就构建一个四边形,但是问题是如何将这个四边形对象转换成我们最终将要传入的顶点缓冲和索引缓冲之中。解决这一问题有两个方面,一个是将对象数据转换为基本API能够识别的顶点数据的算法,另一个方面是在哪里转换它。
首先来说算法:对一个单一的四边形来说这并不困难,只要按照我们规定的顶点顺序,依次的将四边形的四个点的坐标计算出来即可。
其次再说在哪里转换它:其实这是一个面向对象程序的设计问题,有两种选择:第一是将这种转换算法封装在四边形内部,另一种是放在四边形之外,如果只是绘制一个四边形那么随便哪一种都可以,但是如果绘制大量的四边形,因为我们并不会一个一个地按照顺序来绘制四边形,而是要通过一定的算法将众多的四边形数据简化成一个多边形数据,所以第一种方法就不可取了。因此我们将转换四边形数据的算法放在四边形之外:
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 13 public class QuadRender 14 { 15 private var _context3D:Context3D; 16 private var _quads:Vector.<Quad>; 17 18 private var _vertexBuffer:VertexBuffer3D; 19 private var _indexBuffer:IndexBuffer3D; 20 21 public function QuadRender(context3D:Context3D) 22 { 23 _context3D = context3D; 24 _quads = new Vector.<Quad>(); 25 } 26 27 public function addQuad(quad:Quad):Quad 28 { 29 _quads.push(quad); 30 rebuildBuffer(); 31 return quad; 32 } 33 34 public function rebuildBuffer():void 35 { 36 _vertexBuffer && _vertexBuffer.dispose(); 37 _indexBuffer && _indexBuffer.dispose(); 38 var numQuads:uint = _quads.length; 39 if(!numQuads) return; 40 var vertexData:Vector.<Number>=new Vector.<Number>(); 41 var indexData:Vector.<uint>=new Vector.<uint>(); 42 var x0:Number,y0:Number; 43 var x1:Number,y1:Number; 44 var x2:Number,y2:Number; 45 var x3:Number,y3:Number; 46 var z:Number=1; 47 var color:uint; 48 var r:uint; 49 var g:uint; 50 var b:uint; 51 for(var i:int=0;i<numQuads;++i) 52 { 53 x0 = _quads[i].x; 54 y0 = _quads[i].y + _quads[i].height; 55 x1 = _quads[i].x; 56 y1 = _quads[i].y; 57 x2 = _quads[i].x + _quads[i].width; 58 y2 = _quads[i].y; 59 x3 = _quads[i].x + _quads[i].width; 60 y3 = _quads[i].y + _quads[i].height; 61 color = _quads[i].color; 62 r = (color&0xFF0000)>>16; 63 g = (color&0x00FF00)>>8; 64 b = (color&0x0000FF); 65 r/=0xFF; 66 g/=0xFF; 67 b/=0xFF; 68 vertexData.push( 69 x0,y0,z,r,g,b, 70 x1,y1,z,r,g,b, 71 x2,y2,z,r,g,b, 72 x3,y3,z,r,g,b); 73 indexData.push(i*4+0,i*4+1,i*4+2,i*4+0,i*4+2,i*4+3); 74 75 } 76 _vertexBuffer = _context3D.createVertexBuffer(numQuads * 4,6); 77 _indexBuffer = _context3D.createIndexBuffer(numQuads * 6); 78 79 _vertexBuffer.uploadFromVector(vertexData,0,numQuads * 4); 80 _indexBuffer.uploadFromVector(indexData,0,indexData.length); 81 82 _context3D.setVertexBufferAt(0,_vertexBuffer,0,Context3DVertexBufferFormat.FLOAT_3); 83 _context3D.setVertexBufferAt(1,_vertexBuffer,3,Context3DVertexBufferFormat.FLOAT_3); 84 } 85 86 public function setMatrix():void 87 { 88 var pm:PerspectiveMatrix3D = new PerspectiveMatrix3D(); 89 pm.perspectiveFieldOfViewLH(1,1,1,10000); 90 _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,pm,true); 91 } 92 93 public function setProgram():void 94 { 95 var vertexSrc:String = "m44 op,va0,vc0 \n" + 96 "mov v0,va1"; 97 var fragmentSrc:String = "mov oc,v0"; 98 var vertexAssembler:AGALMiniAssembler = new AGALMiniAssembler(); 99 var fragmentAssembler:AGALMiniAssembler = new AGALMiniAssembler(); 100 vertexAssembler.assemble(Context3DProgramType.VERTEX,vertexSrc); 101 fragmentAssembler.assemble(Context3DProgramType.FRAGMENT,fragmentSrc); 102 var program:Program3D = _context3D.createProgram(); 103 program.upload(vertexAssembler.agalcode,fragmentAssembler.agalcode); 104 _context3D.setProgram(program); 105 } 106 107 public function render():void 108 { 109 _context3D.clear(); 110 _context3D.drawTriangles(_indexBuffer); 111 _context3D.present(); 112 } 113 } 114 }
对QuadRender这个类,重点在addQuad方法这个方法封装了将四边形对象转换成顶点数据的算法,至于其他的方法大家都是知道的。
然后建一个测试类:
1 package test 2 { 3 import flash.display.Sprite; 4 import flash.display.Stage3D; 5 import flash.display3D.Context3D; 6 import flash.events.Event; 7 8 import psw2d.Quad; 9 import psw2d.QuadRender; 10 11 public class QuadTest extends Sprite 12 { 13 private var stage3D:Stage3D; 14 private var context3D:Context3D; 15 private var qRender:QuadRender; 16 17 public function QuadTest() 18 { 19 stage?onAddToStage(null): 20 21 addEventListener(Event.ADDED_TO_STAGE,onAddToStage); 22 } 23 24 private function onAddToStage(e:Event):void 25 { 26 stage3D = stage.stage3Ds[0]; 27 stage3D.addEventListener(Event.CONTEXT3D_CREATE,onContext3DCreated); 28 stage3D.requestContext3D(); 29 } 30 31 private function onContext3DCreated(e:Event):void 32 { 33 context3D = stage3D.context3D; 34 context3D.configureBackBuffer(stage.stageWidth,stage.stageHeight,2,true); 35 qRender = new QuadRender(context3D); 36 var q:Quad = new Quad(1,1,0xFF0000); 37 q.x = -.5; 38 q.y = .5; 39 qRender.addQuad(q); 40 qRender.setMatrix(); 41 qRender.setProgram(); 42 addEventListener(Event.ENTER_FRAME,onEnterFrame); 43 } 44 45 private function onEnterFrame(e:Event):void 46 { 47 qRender.render(); 48 } 49 } 50 }
我们将看到,至少绘制一个四边形是没有问题的。
当然我们这几个类还很简陋,但是重要的是体会到了数据的分离。后面我们会追加更多了特性,逐渐构成一个基本的2d图形引擎。