我之前解析过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的核心渲染是分为两个步骤的:

  1. 收集需要渲染的实体;
  2. 根据收集到的实体开始进行真正的渲染;

收集需要渲染的实体:

我们知道在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代码解读】(二):渲染核心流程(简介、实体对象收集)的更多相关文章

  1. 【Away3D代码解读】(三):渲染核心流程(渲染)

    还是老样子,我们还是需要先简略的看一下View3D中render方法的渲染代码,已添加注释: //如果使用了 Filter3D 的话会判断是否需要渲染深度图, 如果需要的话会在实际渲染之前先渲染深度图 ...

  2. 2017.3.31 spring mvc教程(二)核心流程及配置详解

    学习的博客:http://elf8848.iteye.com/blog/875830/ 我项目中所用的版本:4.2.0.博客的时间比较早,11年的,学习的是Spring3 MVC.不知道版本上有没有变 ...

  3. 【Away3D代码解读】(四):主要模块简介

    数据模块: Away3D中最核心的数据类是Mesh类,我们先看看Mesh类的继承关系: NamedAssetBase:为对象提供id和name属性,是Away3D大部分类的基类: Object3D:3 ...

  4. 【Away3D代码解读】(五):动画模块及骨骼动画

    动画模块核心存放在away3d.animators包里: Away3D支持下面几种动画格式: VertexAnimator:顶点动画 SkeletonAnimator:骨骼动画 UVAnimator: ...

  5. 【Away3D代码解读】(一):主要类及说明

    在深入解读Away3D的代码之前,需要对其有个大概的认识.本节主要列出Away3D中常用的类,并附上说明: View3D: Away3D的入口类,即创建该类就会初始化一个可以使用GPU呈现3D的对象, ...

  6. Spring(二)核心容器 - 简介 、BeanFactory、ApplicationContext

    目录 前言 1.容器简介 2.容器的结构 2.1 BeanFactory 2.2 ApplicationContext 2.2.1 ConfigurableApplicationContext 2.2 ...

  7. 【Away3D代码解读】其它一些的记录(持续更新)

    查看当前正在使用的AGAL代码可以在程序开始时添加下面的代码,AGAL代码会被trace出来: Debug.active = true; 具体的输出是在MaterialPassBase类的update ...

  8. 从输入一个URL到页面渲染的流程简介

    首先说明以下是我参考网上答案和自己的思考,给出自己的想法,如果有问题,欢迎大家吐槽从用户在浏览器中输入一个URL,到整个页面渲染,这个过程中究竟发生了什么呢?今天先简单写下整个过程,后面再一点点完善. ...

  9. Jsoup代码解读之二-DOM相关对象

    Jsoup代码解读之二-DOM相关对象   之前在文章中说到,Jsoup使用了一套自己的DOM对象体系,和Java XML API互不兼容.这样做的好处是从XML的API里解脱出来,使得代码精炼了很多 ...

随机推荐

  1. WPF之外观模式

    名词解释: 外观模式:为子系统中的一组接口提供一个一致的界面,此模式定义一个高层接口,这个接口使得这一子系统更加容易使用. 必要元素: 一个外观类和多个子系统类(外观类中注入各个子系统类). 上例子: ...

  2. JVM运行机制

    JVM启动流程

  3. Python Requests库:HTTP for Humans

    Python标准库中用来处理HTTP的模块是urllib2,不过其中的API太零碎了,requests是更简单更人性化的第三方库. 用pip下载: pip install requests 或者git ...

  4. Yii cookie操作

    设置cookie: $cookie = new CHttpCookie('mycookie','this is my cookie'); $cookie->expire = time()+60* ...

  5. Linux C/C++ 编程练手 --- 大数相加和大数相乘

    最近写了一个大数相乘和相加的程序,结果看起来是对的.不过期间的效率可能不是最好的,有些地方也是临时为了解决问题而直接写出来的. 可以大概说一下相乘和相加的解决思路(当然,大数操作基本就是两个字符串的操 ...

  6. Linux内核与根文件系统的关系1

    Linux内核与根文件系统的关系开篇题外话:对于Linux初学者来说,这是一个很纠结的问题,但这也是一个很关键的问题!一语破天机: “尽管内核是 Linux 的核心,但文件却是用户与操作系统交互所采用 ...

  7. 锋利的JQuery-Jquery中的事件和动画

    有时候觉得这些内容都好简单,真想看看就算了. 事件绑定 bing(type [,data],fn) 第一个参数:事件类型包括:blur,focus,load,resize,scroll,unload, ...

  8. XSLT 处理程序是如何工作的

    与 JSP.PHP 和其他 Web 开发语言的比较 在本文中,Benoit Marchal 考察了 XSLT 处理程序的工作原理.为了说明他的观点,他编写了专门的样式表把处理中的某些方面凸显出来.他特 ...

  9. [转] android自动化之MonkeyRunner测试环境配置(一)

    Android自动化测试之MonkeyRunner 一.Android自动化测试之环境搭建 1.1  Android-sdk介绍 ¢ SDK(Software development kit)软件开发 ...

  10. c语言typedef关键字的理解

    1.typedef的定义 很多人认为typedef 是定义新的数据类型,这可能与这个关键字有关.本来嘛,type 是数据类型的意思:def(ine)是定义的意思,合起来就是定义数据类型啦. 不过很遗憾 ...