【Away3D代码解读】(二):渲染核心流程(简介、实体对象收集)
来源:互联网 发布:如何在七天网络查分 编辑:程序博客网 时间:2024/05/18 08:00
我之前解析过Starling的核心渲染流程,相比Away3D而言Starling真的是足够简单,不过幸运的是两者的渲染流程是大体上相似的;Starling的渲染是每帧调用Starling类中的render方法,类似的Away3D的渲染是每帧调用View3D类中的render方法,那我们要了解Away3D的渲染就需要从这个方法入手了。
View3D的render方法源码:
/** * Renders the view. */public function render():void{ //if context3D has Disposed by the OS,don't render at this frame if (!stage3DProxy.recoverFromDisposal()) { _backBufferInvalid = true; return; } // reset or update render settings if (_backBufferInvalid) updateBackBuffer(); if (_shareContext && _layeredView) stage3DProxy.clearDepthBuffer(); if (!_parentIsStage) { var globalPos:Point = parent.localToGlobal(_localPos); if (_globalPos.x != globalPos.x || _globalPos.y != globalPos.y) { _globalPos = globalPos; _globalPosDirty = true; } } if (_globalPosDirty) updateGlobalPos(); updateTime(); updateViewSizeData(); _entityCollector.clear(); // collect stuff to render _scene.traversePartitions(_entityCollector); // update picking _mouse3DManager.updateCollider(this); _touch3DManager.updateCollider(); if (_requireDepthRender) renderSceneDepthToTexture(_entityCollector); // todo: perform depth prepass after light update and before final render if (_depthPrepass) renderDepthPrepass(_entityCollector); _renderer.clearOnRender = !_depthPrepass; if (_filter3DRenderer && _stage3DProxy._context3D) { _renderer.render(_entityCollector, _filter3DRenderer.getMainInputTexture(_stage3DProxy), _rttBufferManager.renderToTextureRect); _filter3DRenderer.render(_stage3DProxy, camera, _depthRender); } else { _renderer.shareContext = _shareContext; if (_shareContext) _renderer.render(_entityCollector, null, _scissorRect); else _renderer.render(_entityCollector); } if (!_shareContext) { stage3DProxy.present(); // fire collected mouse events _mouse3DManager.fireMouseEvents(); _touch3DManager.fireTouchEvents(); } // clean up data for this render _entityCollector.cleanUp(); // register that a view has been rendered stage3DProxy.bufferClear = false;}在进入渲染代码的解读之前,我们应该需要大概的解读一下render方法实现的功能;
/** * 渲染 View3D 对象. */public function render():void{ //判断当前的 context3D 对象是否可以使用, 不能使用则取消本次渲染 if (!stage3DProxy.recoverFromDisposal()) { _backBufferInvalid = true; return; } //如果 View3D 的尺寸改变则更新后台缓冲区的大小 if (_backBufferInvalid) updateBackBuffer(); //清除深度缓冲 if (_shareContext && _layeredView) stage3DProxy.clearDepthBuffer(); //如果父级不是 stage 对象则需要获取 View3D 对象的舞台坐标 if (!_parentIsStage) { var globalPos:Point = parent.localToGlobal(_localPos); if (_globalPos.x != globalPos.x || _globalPos.y != globalPos.y) { _globalPos = globalPos; _globalPosDirty = true; } } //更新舞台坐标 if (_globalPosDirty) updateGlobalPos(); //获取当前帧和上一帧之间的间隔时间 updateTime(); //更新视口尺寸数据, 主要是更新当前摄像机的属性 updateViewSizeData(); //清除实体收集器 _entityCollector.clear(); //对当前渲染的场景进行实体收集, 收集到的对象会在后面进行渲染 _scene.traversePartitions(_entityCollector); //鼠标及触摸事件的处理 _mouse3DManager.updateCollider(this); _touch3DManager.updateCollider(); // ----- 渲染代码 begin ----- if (_requireDepthRender) renderSceneDepthToTexture(_entityCollector); // todo: perform depth prepass after light update and before final render if (_depthPrepass) renderDepthPrepass(_entityCollector); _renderer.clearOnRender = !_depthPrepass; if (_filter3DRenderer && _stage3DProxy._context3D) { _renderer.render(_entityCollector, _filter3DRenderer.getMainInputTexture(_stage3DProxy), _rttBufferManager.renderToTextureRect); _filter3DRenderer.render(_stage3DProxy, camera, _depthRender); } else { _renderer.shareContext = _shareContext; if (_shareContext) _renderer.render(_entityCollector, null, _scissorRect); else _renderer.render(_entityCollector); } // ----- 渲染代码 end ----- //不共享 context3D 对象就直接渲染, 共享需要手动调用 present 方法 if (!_shareContext) { //呈现 3D 画面 stage3DProxy.present(); //释放收集的鼠标和触摸事件 _mouse3DManager.fireMouseEvents(); _touch3DManager.fireTouchEvents(); } //清除实体收集器的数据 _entityCollector.cleanUp(); //标记已经渲染完毕 stage3DProxy.bufferClear = false;}
撇开诸如鼠标事件的处理,我们可以知道Away3D的核心渲染是分为两个步骤的:
- 收集需要渲染的实体;
- 根据收集到的实体开始进行真正的渲染;
收集需要渲染的实体:
我们知道在Starling中是直接采用深度优先遍历的方法来遍历显示列表中的所有显示对象,然后一一进行渲染,并没有分为收集和渲染两个步骤;那么在Away3D中3D显示列表也是树形结构,也可以采用Starling的方法来遍历绘制,特别的是Starling采用画家算法,所以需要得到谁先绘制谁后绘制的正确顺序,而Away3D使用的是ZBuffer算法,无论谁先绘制最终都会呈现一样的结果,那么是不是说Away3D的渲染就更加简单了呢?当然不是,Away3D由于是存在一个3D空间中,所以最终的绘制对象需要结合其摄像机的镜头对准的区域来决定,不在可视区域的对象就不进行绘制,即视锥剔除,可以大大的提高渲染效率;那么Away3D的实体收集其核心就是得到需要渲染的3D对象,去掉不需要渲染的3D对象的过程。
我们看看收集实体对象的代码:
// collect stuff to render _scene.traversePartitions(_entityCollector);查看这个方法:
public function traversePartitions(traverser:PartitionTraverser):void{ var i:uint; var len:uint = _partitions.length; traverser.scene = this; while (i < len) _partitions[i++].traverse(traverser);}
我们发现一个陌生的类型Partition3D,而几乎所有的实体收集都是由该类接手处理的,那么这个类究竟是什么呢?每一个Scene3D在初始化的时候都会创建一个_partitions:Vector.<Partition3D>。Partition3D是一个空间分区系统的核心,它用于将三维场景分级成多个互不重叠的子空间,从而形成一个树型数据结构。
接着查看traverse方法:
public function traverse(traverser:PartitionTraverser):void{ if (_updatesMade) updateEntities(); ++PartitionTraverser._collectionMark; _rootNode.acceptTraverser(traverser);}我们发现了一个_rootNode对象,该对象是记录Partition3D包含的所有对象的树形结构的root,我们接下来看看acceptTraverser方法:
public function acceptTraverser(traverser:PartitionTraverser):void{ if (_numEntities == 0 && !_debugPrimitive) return; if (traverser.enterNode(this)) { var i:uint; while (i < _numChildNodes) _childNodes[i++].acceptTraverser(traverser); if (_debugPrimitive) traverser.applyRenderable(_debugPrimitive); }}
注意参数traverser就是我们的实体收集对象的实例_entityCollector,调用的方法enterNode即视锥剔除,会去掉不需要渲染的对象,而实际上添加需要渲染的对象是NodeBase的子类EntityNode的子类MeshNode,我们分别看看EntityNode和MeshNode的acceptTraverser方法:
EntityNode:
override public function acceptTraverser(traverser:PartitionTraverser):void { traverser.applyEntity(_entity); }
MeshNode:
override public function acceptTraverser(traverser:PartitionTraverser):void{ if (traverser.enterNode(this)) { super.acceptTraverser(traverser); var subs:Vector.<SubMesh> = _mesh.subMeshes; var i:uint; var len:uint = subs.length; while (i < len) traverser.applyRenderable(subs[i++]); }}
最终需要渲染的对象都会被收集,交给下一步的渲染代码进行渲染。
Partition3D将我们的3D空间切割为多个不重合的区域,那么如果一个实体对象移动到另一个Partition3D对象的区域,或改变尺寸跨越多个Partition3D对象时Away3D又是如何处理的呢?
我们看看实体类Entity的notifySceneBoundsInvalid方法,当我们的实体对象位置或尺寸改变时会调用该方法:
private function notifySceneBoundsInvalid():void{ if (_scene) _scene.invalidateEntityBounds(this);}
这个方法会通知到我们的场景对象调用invalidateEntityBounds方法:
arcane function invalidateEntityBounds(entity:Entity):void{ entity.implicitPartition.markForUpdate(entity);}
markForUpdate方法会重新将我们的实体对象分配到对应的Partition3D对象中去。
另外有一个大神发现了实体回收的bug,链接贴出来:
Away3D 的实体收集器Bug
- 【Away3D代码解读】(二):渲染核心流程(简介、实体对象收集)
- 【Away3D代码解读】(三):渲染核心流程(渲染)
- 【Away3D代码解读】(四):主要模块简介
- 【Away3D代码解读】(一):主要类及说明
- 【Away3D代码解读】(五):动画模块及骨骼动画
- (原创)OGRE主要渲染流程简介
- 《代码大全》解读(二)
- 【Hibernate】(二)核心对象
- 【Hibernate】(二)核心对象
- (转)3ds Max 和 Away3D工作流程
- Away3D-------------------------------------------Material的渲染
- js面向对象收集(二)
- Intent对象(二)简介
- Java核心代码(二)Class loader
- Java核心代码(二)ServiceLoader用例
- J2SE代码例子收集(IO)二
- Activiti——工作流程-核心API(二)
- Cesium学习笔记(二):添加实体对象
- localStorage的过期时间设置的方法?
- python数据挖掘笔记(2)—模型建立
- 时钟周期,机器周期,指令周期的区别
- Spring事务管理中@Transactional的propagation参数
- 记录一些以后要拜读的java书籍
- 【Away3D代码解读】(二):渲染核心流程(简介、实体对象收集)
- qtableview代理类,不需要双击就能显示效果
- 为什么我们创业失败了和选择创业公司的思考
- Linux时间子系统
- MFC界面库BCGControlBar v25.3新版亮点:Dialogs和Forms
- IT 面试总结
- es6 iterator(十四)
- POJ
- Android 用ViewPager实现加载两个webview的可滑动Tab页