stage3D 搭建2d图形引擎 (六) 显示列表
显示列表的概念
Flash传统的显示列表实质上是一种树形数据结构,可以用下面的图来表示:
这一数据结构的实现是通过两个类:DisplayObject和DisplayObjectContainer,其中前者为后者的父类(介于阅读的方便,我省略了这两个类的一些几何属性和方法,更完整的内容可以参考后面的源码资源):
DisplayObject:
1 package psw2d.display 2 { 3 import flash.geom.Matrix; 4 5 import psw2d.render.RenderMaster; 6 7 public class DisplayObject 8 { 9 private var _alpha:Number; 10 protected var _tined:Boolean; 11 private var _visible:Boolean; 12 private var _blendMode:String; 13 private var _transformationMatrix:Matrix; 14 private var _parent:DisplayObjectContainer; 15 private var _isOrientationChanged:Boolean;//显示对象的方位参数是否改变过 16 17 private var _root:DisplayObjectContainer; 18 private var _stage:Stage; 19 20 public function DisplayObject() 21 { 22 _alpha = 1.0; 23 _blendMode = BlendMode.AUTO; 24 _visible = true; 25 _transformationMatrix = new Matrix(); 26 _isOrientationChanged = false; 27 } 28 29 public function get alpha():Number 30 { 31 return _alpha; 32 } 33 34 public function set alpha(value:Number):void 35 { 36 _alpha = value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value); 37 } 38 39 public function get tined():Boolean 40 { 41 return _tined; 42 } 43 44 public function get visible():Boolean 45 { 46 return _visible; 47 } 48 49 public function set visible(value:Boolean):void 50 { 51 _visible = value; 52 } 53 54 public function get blendMode():String 55 { 56 return _blendMode; 57 } 58 59 public function set blendMode(value:String):void 60 { 61 _blendMode = value; 62 } 63 64 public function get transformationMatrix():Matrix 65 { 66 } 67 68 public function set transformationMatrix(value:Matrix):void 69 { 70 } 71 72 public function get parent():DisplayObjectContainer 73 { 74 return _parent; 75 } 76 77 public function render(renderMaster:RenderMaster,parentAlpha:Number=1.0):void 78 { 79 80 } 81 82 internal function get hasVisibleArea():Boolean 83 { 84 return _alpha != 0 && _visible && _scaleX != 0 && _scaleY != 0; 85 } 86 87 public function get root():DisplayObjectContainer 88 { 89 return _root; 90 } 91 92 public function get stage():Stage 93 { 94 return _stage; 95 } 96 97 98 } 99 }
DisplayObjectContainer:
1 package psw2d.display 2 { 3 import psw2d.render.RenderMaster; 4 5 public class DisplayObjectContainer extends DisplayObject 6 { 7 private var _children:Vector.<DisplayObject>; 8 private var _numChildren:int; 9 10 public function DisplayObjectContainer() 11 { 12 super(); 13 _children = new <DisplayObject>[]; 14 } 15 16 public function addChild(child:DisplayObject):DisplayObject 17 { 18 _children.push(child); 19 _numChildren ++; 20 return child 21 } 22 23 public function removeChild(child:DisplayObject):DisplayObject 24 { 25 var index:int = _children.indexOf(child); 26 if(index>-1) return _children.splice(index,1)[0]; 27 } 28 29 override public function render(renderMaster:RenderMaster,parentAlpha:Number=1.0):void 30 { 31 } 32 } 33 }
DisplayObjectContainer中我们只实现了少数的接口,更丰富的内容可以参考相关资料,或者由你们自己实现。
通过这一数据结构我们要实现如下的三个特性:
1.层级关系
2.位置关系
3.颜色混合
所谓层级关系是指“谁在谁的上面,或者谁遮挡住了谁”,而位置关系则是坐标系的变换问题,最后颜色混合是在层级关系的基础之上实现的像素操作。
显示列表的实现
这三个方面中,层级关系和颜色混合是需要同时考虑的。
层级关系
层级关系的实现是通过遵循所谓的画家算法来实现的,如果你仔细研究了我们之前的一些程序,你会发现,后添加的显示对象会遮盖住先添加的。之所以会这样是因为,先添加的显示对象的数据位于数据缓冲的前端,因此会被较早绘制,从而会被后面绘制的对象遮盖住。
因此,我们需要实现的是:越是底层的显示对象就越先被绘制。通过一个显示列表的结构图我们就能更直观的了解:
图中的阿拉伯数字代表着绘制顺序,可以很明显的看出这是一个树的递归。实现递归的核心是在DisplayObjectContainer的render()方法中遍历children列表:
1 override public function render(renderMaster:RenderMaster,parentAlpha:Number=1.0):void 2 { 3 var child:DisplayObject; 4 5 for(var i:int=0;i<_numChildren;++i) 6 { 7 child = _children[i]; 8 if(child.hasVisibleArea)//忽略不可见的对象 9 { 10 child.render(renderMaster,alpha); 11 } 12 } 13 }
颜色混合
上面实现的render()方法中并没有考虑颜色混合,但已经保证了层级关系。为了加入颜色混合,需要我们首先搞清楚颜色混合究竟是啥?其实前面讲alpha混合的时候已经有所提及,在计算机内部,为了实现颜色混合,用到了两块存储区域(虽然实际情况可能更多,但是并没有改变颜色混合的实质),根据他们的功能我们可以形象的称他们为前缓冲区,和后缓冲区。前缓冲区里的颜色数据时屏幕上实际显示的颜色,而后缓冲区的作用是保存我们每次绘制(在stage3D中就是drawTriangles())之后计算出来的数据,而颜色混合就是发生在后缓冲区中的像素操作。颜色混合的实质是将当前绘制出来的颜色数据(即所谓的源,此时这些数据还在处理器中)同之前绘制的已经在缓冲区(存储器,内存)中的颜色数据(即所谓的目标),按照一定规则来计算出一个最终颜色并放入到缓冲区中。这里所谓的规则指的是一个计算公式,可以用alpha混合为例。
清楚了这一原理之后,我们再回过头来看显示列表。如果所有的显示对象都是平级的,即他们没有包含于被包含的关系,那么只需要我们在绘制的时候调用一次Context3D::setBlendFactors()设置一下参数即可,但是当有了父子的层级关系之后,一个问题就出现了即倘若子对象和父对象都设定了混合模式,那么该如何处理呢?
我们的解决方案是,如果子对象设置了混合模式,那么优先使用子对象的设定,否则子对象继承父对象的混合模式。如下的BlendMode类中我们定义了几个常量:
1 package psw2d.display 2 { 3 public class BlendMode 4 { 5 public static const AUTO:String = "auto"; 6 public static const NONE:String = "none"; 7 public static const NORMALL:String = "normal"; 8 public static const ADD:String = "add"; 9 public static const MULTIPLY:String = "multiply"; 10 public static const SREEN:String = "screen"; 11 public static const ERASE:String = "erase"; 12 } 13 }
用来表示不同的混合模式,其中第一个"auto"的作用就是让设定为该值的显示对象继承父级的混合模式。如果设定为其他的值,那么就要忽略父级显示对象的设定。
接着我们修改DisplayObjectContainer的render()方法:
1 override public function render(renderMaster:RenderMaster,parentAlpha:Number=1.0):void 2 { 3 var alpha:Number = parentAlpha * this.alpha; 4 var child:DisplayObject; 5 6 for(var i:int=0;i<_numChildren;++i) 7 { 8 child = _children[i]; 9 if(child.hasVisibleArea) 10 { 11 var blendMode:String = child.blendMode; 12 var blendModeChange:Boolean = blendMode != BlendMode.AUTO; 13 if(blendModeChange) 14 { 15 renderMaster.pushBlendMode(); 16 renderMaster.blendMode = blendMode; 17 }
20 child.render(renderMaster,alpha); 22 if(blendModeChange) 23 { 24 renderMaster.popBlendMode(); 25 } 26 } 27 } 28 }
在渲染子对象之前我们先判断子对象是否有自己的混合模式设定,如果有,我先将当前的混合模式压入栈内,并重新设定当前混合模式,接着渲染子对象,渲染完之后,再将栈中的渲染模式弹出。这里的RenderMaster对象我们暂时可以将它看成一个用来全局存储渲染模式的对象,通过render方法,它被一级一级的传入子对象的内部。
特别要注意,考虑到子对象要继承父对象的透明度,所以我们用上一级对象的透明度同当前级的透明度相乘,并将这个值传递给子对象进行渲染。
位置关系
对一个子对象,它要“继承”父对象的空间属性,或者说,子对向是在父对象的几何属性基础之上发挥自身的几何属性。而这种几何属性的继承是通过对子对象运用父对象的变换矩阵来实现的:
1 override public function render(renderMaster:RenderMaster,parentAlpha:Number=1.0):void 2 { 3 var alpha:Number = parentAlpha * this.alpha; 4 var child:DisplayObject; 5 6 for(var i:int=0;i<_numChildren;++i) 7 { 8 child = _children[i]; 9 if(child.hasVisibleArea) 10 { 11 var blendMode:String = child.blendMode; 12 var blendModeChange:Boolean = blendMode != BlendMode.AUTO; 13 if(blendModeChange) 14 { 15 renderMaster.pushBlendMode(); 16 renderMaster.blendMode = blendMode; 17 } 18 renderMaster.pushMatrix(); 19 renderMaster.transformMatrix(child); 20 child.render(renderMaster,alpha); 21 renderMaster.popMatrix(); 22 if(blendModeChange) 23 { 24 renderMaster.popBlendMode(); 25 } 26 } 27 } 28 }
类似于颜色混合,在变换一个子对向之前,我们先将父对象变换矩阵的一个副本的一个副本压入栈内,然后用原始矩阵同子对向的矩阵相乘的到子对向的合成变换矩阵并对子对象应用该矩阵,变换完毕之后再将之前压入栈内的副本弹出,接着继续下一个子对象的变换,如此反复。
最后我们来看一下前面一直用到的RenderMaster类,这个类被设计用来管理所有的Render对象:
1 package psw2d.render 2 { 3 import flash.geom.Matrix; 4 import flash.utils.Dictionary; 5 import flash.utils.getDefinitionByName; 6 import flash.utils.getQualifiedClassName; 7 8 import psw2d.display.BlendMode; 9 import psw2d.display.DisplayObject; 10 import psw2d.display.Image; 11 import psw2d.display.Quad; 12 import psw2d.display.SimpleImagePlayer; 13 import psw2d.texture.Texture; 14 15 public class RenderMaster 16 { 17 private var _renderMap:Dictionary; 18 19 private var _renders:Vector.<RenderBase>; 20 private var _currentRenderID:int; 21 22 private var _modelMatrix:Matrix; 23 24 private var _matrixStack:Vector.<Matrix>; 25 private var _matrixStackSize:int; 26 27 private var _blendMode:String; 28 private var _blendModeStack:Vector.<String>; 29 30 private var _drawCount:int; 31 32 private var _helperMatrix:Matrix; 33 34 public function RenderMaster() 35 { 36 _renders = new <RenderBase>[]; 37 _currentRenderID = -1; 38 _modelMatrix = new Matrix(); 39 _matrixStack = new <Matrix>[]; 40 41 _blendMode = BlendMode.AUTO; 42 _blendModeStack = new <String>[]; 43 44 _matrixStackSize = 0; 45 _drawCount = 0; 46 47 _helperMatrix = new Matrix(); 48 49 mapRenders(); 50 } 51 52 private function mapRenders():void 53 { 54 _renderMap = new Dictionary(); 55 _renderMap[Quad] = QuadRender; 56 _renderMap[Image] = ImageRender; 57 _renderMap[SimpleImagePlayer] = ImageRender; 58 } 59 60 public function pushMatrix():void 61 { 62 if(_matrixStack.length<_matrixStackSize+1) 63 _matrixStack.push(new Matrix()); 64 65 _matrixStack[_matrixStackSize++].copyFrom(_modelMatrix); 66 } 67 68 public function transformMatrix(object:DisplayObject):void 69 { 70 transformMatrixFormObject(_modelMatrix,object); 71 } 72 73 public function transformMatrixFormObject(matrix:Matrix,object:DisplayObject):void 74 { 75 _helperMatrix.copyFrom(object.transformationMatrix); 76 _helperMatrix.concat(matrix); 77 matrix.copyFrom(_helperMatrix); 78 } 79 80 public function popMatrix():void 81 { 82 _modelMatrix.copyFrom(_matrixStack[--_matrixStackSize]); 83 } 84 85 public function pushBlendMode():void 86 { 87 _blendModeStack.push(_blendMode); 88 } 89 90 public function popBlendMode():void 91 { 92 _blendMode = _blendModeStack.pop(); 93 } 94 95 public function nextFrame():void 96 { 97 98 } 99 100 public function bacthDisplay(display:DisplayObject,parentAlpha:Number,texture:Texture=null,smooting:String=null):void 101 { 102 var render:RenderBase = matchRender(display,texture); 103 if(render.isStateChange(parentAlpha,texture,smooting,_blendMode,display.tined)) 104 { 105 finishRender(display,texture); 106 } 107 _renders[_currentRenderID].addDisplay(display,parentAlpha,texture,smooting,_modelMatrix,_blendMode); 108 } 109 110 public function finishRender(display:DisplayObject,texture:Texture=null):void 111 { 112 var curRender:RenderBase = _renders[_currentRenderID]; 113 if(curRender.numDisplays > 0) 114 { 115 curRender.render(); 116 curRender.reset(); 117 118 ++ _currentRenderID; 119 ++ _drawCount; 120 121 if(_renders.length <= _currentRenderID) 122 { 123 var type:Class = getDefinitionByName(getQualifiedClassName(display)) as Class; 124 _renders.push(new _renderMap[type](texture) as RenderBase); 125 } 126 } 127 } 128 129 public function get blendMode():String 130 { 131 return _blendMode; 132 } 133 134 public function set blendMode(value:String):void 135 { 136 if(value != BlendMode.AUTO) _blendMode = value; 137 } 138 139 private function matchRender(display:DisplayObject,texture:Texture=null):RenderBase 140 { 141 var type:Class = getDefinitionByName(getQualifiedClassName(display)) as Class; 142 if(_currentRenderID<0) 143 { 144 _currentRenderID ++; 145 _renders.push(new _renderMap[type](texture) as RenderBase); 146 } 147 return _renders[_currentRenderID]; 148 } 149 } 150 }
如果一个显示对象时可以被批处理的,那么其render方法的实现是通过调用RenderMaster对象的bacthDisplay方法将其传入RenderMaster对象内部,并选择合适的render对象来批处理它。