吾知网

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 11228|回复: 6
打印 上一主题 下一主题

【Away3D代码解读】

  [复制链接]
跳转到指定楼层
楼主
发表于 2016-6-23 11:27:10 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
(一):主要类及说明

在深入解读Away3D的代码之前,需要对其有个大概的认识。本节主要列出Away3D中常用的类,并附上说明:

View3D:
Away3D的入口类,即创建该类就会初始化一个可以使用GPU呈现3D的对象,需要注意的是该类是继承自Sprite,所以可以将其添加到2D显示列表中,但是最终呈现3D是在Stage3D中进行的,所以实际的3D对象并非存在View3D这个2D容器中。View3D更像一个管理3D内容的管理器,并且每帧都需要调用其render方法来渲染其内部的所有3D对象。
Scene3D:
每个View3D对象创建时或运行时都可以指定一个Scene3D对象,不进行指定时其内部会实例化一个Scene3D对象,可以使用View3D的scene属性获取到Scene3D对象,该对象类似原生列表中的Stage对象,是所有3D显示对象的根容器,我们真正用于渲染的3D对象都应该添加到该对象中。
ObjectContainer3D:
3D容器对象,该对象支持3D对象的层级嵌套。
Camera3D:
类似于Scene3D对象,Camera3D对象也是作为View3D对象的一个属性存在的。
OrthographicLens:
正交镜头对象,可以设定到Camera3D对象中,控制3D渲染使用正交矩阵进行渲染。
PerspectiveLens:
透视镜头对象,可以设定到Camera3D对象中,控制3D渲染使用透视矩阵进行渲染。
Mesh:
网格对象,最终会使用GPU进行绘制的对象,Mesh对象主要包含Geometry、Material及Animator对象,这3个对象分别描述了一个3D对象的形状、材质及动作;内部包含多个SubMesh对象,与Geometry的SubGeometry对象一一对应。
Geometry:
几何对象,几何对象用来记录一个3D对象的顶点、法线、uv等信息。
Texture:
贴图对象,贴图对象记录程序会使用到的位图信息,但不会包含诸如光照或者滤镜信息。
Material:
纹理对象,纹理对象包含会贴图对象,同时可以设置光照或滤镜信息来产生特殊的效果。
animators包:
动画包,包含大量3D动画数据的解析和实现,主要包括骨骼动画、顶点动画、UV动画等。
DirectionalLight:
光源对象,该对象用来实现直线光源。
PointLight:
光源对象,该对象用来实现点光源。
loaders包:
加载及解析包,包含多种3D文件的加载及解析实现。

沙发
 楼主| 发表于 2016-6-23 11:29:16 | 只看该作者
关于AWAY3D的MESH、GEOMETRY的区别

Meshes和Geometry对象关联很紧密,Geometries提供了一个几何学上的3D对象,包括顶点、三角形、UV坐标、顶点法线给你。MESH对象给与GEOMETRY显示在空间中的资格,同时赋予它材质。为什么要把这两者区分开来呢,为什么不在MESH里固定死特定的基本几何对象呢。你想在不同的地方使用相同的几何体,比如在一个平面上显示多个同种类型的怪物,Geometry帮助你节约内存,以及降低GPU运算的数据量。

Mesh和Geometry通过包含彼此的sub对象的方式互相映射对方(SubMesh和SubGeometry),在一个Mesh的Geometry中,每一个SubMesh对象关联对应的SubGeometry。每一个SubGeometry提供顶点和三角形数据用来拼装成一个对象的整体。迷惑吗?看看下面的类图吧。之所以这样设计的原因是可以在一个独立的MESH中使用复合材质(不同的材质)。因为GPU发出绘图指令时,材质是不能指定到模型中一个独立三角形中的。所以为了解决这个问题,SubGeometries被用到了,每一个SubGeometry可以被渲染不同的材质。这也是SubMesh的功劳,因为只有SubMesh才能为SubGeometry贴图。另外,SubMesh的材质是可选的,如果你没有指定它,那么,它默认使用主体Mesh的材质。

一辆车的Mesh对象关联一个Geometry对象,这个Geometry包含多个SubGeometry,比如这辆车的车身,或者车窗,那么车身就可以用BitmapMaterial材质,车窗可以用ColorMaterial材质。



板凳
 楼主| 发表于 2016-6-23 11:34:32 | 只看该作者
Texture(纹理)和Material(材质)概念上的区别

我的理解是:纹理更偏向于“图”,而材质更偏向于“属性”。

打个比方说,对同一个立方体模型进行处理:

加纹理信息,可以认为是贴上图,比如木头的纹理图,大理石的纹理图。

加材质信息,可以认为是为这个立方体加上属性(这些属性主要是指反射系数、折射系数等),比如木头的属性或大理石的属性。

从另一个角度来看,加了纹理的模型是静态的和表面的,不会因为外界环境变化而变化(比如光照)。但是加了材质的模型是动态的和本质的,当外界环境变化的时候能做出相应的变化,所以更真实。

最简单的例子就是,我们可以做出有木头光泽的大理石模型,有大理石光泽的木头模型,有木头光泽的木头模型,有大理石光泽的大理石模型。在上面的描述中,有“什么光泽”的“什么”,这是材质信息;“什么模型”的“什么”,这是纹理信息。

说了那么多,具体的只能自己体会啦。这也只是我个人的理解,

一般在CG中,“材质”(Material)指的其实是颜色,准确的说,指的是物体表面对射到表面上的色光的RGB分量的反射率。通常材质都包括环境光、漫射光、镜面光和自发光等成分,指的就是对不同的光线,不同颜色分量的反射程度。
“纹理”(texture)指的是位图,把一张图贴到一个表面上去,实际是摹拟了自然事物的漫射材质。因为材质一般只对顶点指定,你不可能对这个平面上的每个像素都指定一种材质。纹理其实就是起这个作用,相当于对这个平面上的每个像素都指定了不同的漫射材质。
不知道我的解释你明白了没有?
其实说穿了,材质和纹理都只是“颜色”而已。

“物体表面的颜色”,这种说法似是而非。物体表面哪来的颜色?
我们看到树是绿的,花是红的,只不过是因为树叶表面反射G分量最强而RB分量被吸收;花表面反射R分量最强而GB分量被吸收而已。说到底,物体的颜色都是外界给的,颜色并不是物体本身的属性。对RGB分量的反射率才是物体本身的属性。
你当然可以说“石膏是白的”,但把石膏放在红色灯下,石膏也就成了红的。你只能说“石膏在白色光下是白的”——这不是一句废话吗?
纹理实际上是对漫反射率的一个摹拟,准确的说,纹理摹拟的是物体表面在白色漫射光照下,各点的反射率。而材质反映的是物体在环境光、漫射光、直射光甚至自发光照射下的反射率,仅此而已,没有什么内部外部,本质表象的。  
地板
 楼主| 发表于 2016-6-23 11:39:47 | 只看该作者
(二):渲染核心流程(简介、实体对象收集)我之前解析过Starling的核心渲染流程,相比Away3D而言Starling真的是足够简单,不过幸运的是两者的渲染流程是大体上相似的;Starling的渲染是每帧调用Starling类中的render方法,类似的Away3D的渲染是每帧调用View3D类中的render方法,那我们要了解Away3D的渲染就需要从这个方法入手了。

View3D的render方法源码:

View Code
在进入渲染代码的解读之前,我们应该需要大概的解读一下render方法实现的功能;


1 /**
2  * 渲染 View3D 对象.
3  */
4 public function render():void
5 {
6     //判断当前的 context3D 对象是否可以使用, 不能使用则取消本次渲染
7     if (!stage3DProxy.recoverFromDisposal()) {
8         _backBufferInvalid = true;
9         return;
10     }
11     
12     //如果 View3D 的尺寸改变则更新后台缓冲区的大小
13     if (_backBufferInvalid)
14         updateBackBuffer();
15     
16     //清除深度缓冲
17     if (_shareContext && _layeredView)
18         stage3DProxy.clearDepthBuffer();
19     
20     //如果父级不是 stage 对象则需要获取 View3D 对象的舞台坐标
21     if (!_parentIsStage) {
22         var globalPos:Point = parent.localToGlobal(_localPos);
23         if (_globalPos.x != globalPos.x || _globalPos.y != globalPos.y) {
24             _globalPos = globalPos;
25             _globalPosDirty = true;
26         }
27     }
28     
29     //更新舞台坐标
30     if (_globalPosDirty)
31         updateGlobalPos();
32     
33     //获取当前帧和上一帧之间的间隔时间
34     updateTime();
35     
36     //更新视口尺寸数据, 主要是更新当前摄像机的属性
37     updateViewSizeData();
38     
39     //清除实体收集器
40     _entityCollector.clear();
41     
42     //对当前渲染的场景进行实体收集, 收集到的对象会在后面进行渲染
43     _scene.traversePartitions(_entityCollector);
44     
45     //鼠标及触摸事件的处理
46     _mouse3DManager.updateCollider(this);
47     _touch3DManager.updateCollider();
48     
49     // ----- 渲染代码 begin -----
50     
51     if (_requireDepthRender)
52         renderSceneDepthToTexture(_entityCollector);
53     
54     // todo: perform depth prepass after light update and before final render
55     if (_depthPrepass)
56         renderDepthPrepass(_entityCollector);
57     
58     _renderer.clearOnRender = !_depthPrepass;
59     
60     if (_filter3DRenderer && _stage3DProxy._context3D) {
61         _renderer.render(_entityCollector, _filter3DRenderer.getMainInputTexture(_stage3DProxy), _rttBufferManager.renderToTextureRect);
62         _filter3DRenderer.render(_stage3DProxy, camera, _depthRender);
63     } else {
64         _renderer.shareContext = _shareContext;
65         if (_shareContext)
66             _renderer.render(_entityCollector, null, _scissorRect);
67         else
68             _renderer.render(_entityCollector);
69         
70     }
71     
72     // ----- 渲染代码  end  -----
73     
74     //不共享 context3D 对象就直接渲染, 共享需要手动调用 present 方法
75     if (!_shareContext) {
76         //呈现 3D 画面
77         stage3DProxy.present();
78         
79         //释放收集的鼠标和触摸事件
80         _mouse3DManager.fireMouseEvents();
81         _touch3DManager.fireTouchEvents();
82     }
83     
84     //清除实体收集器的数据
85     _entityCollector.cleanUp();
86     
87     //标记已经渲染完毕
88     stage3DProxy.bufferClear = false;
89 }



撇开诸如鼠标事件的处理,我们可以知道Away3D的核心渲染是分为两个步骤的:

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


收集需要渲染的实体:

我们知道在Starling中是直接采用深度优先遍历的方法来遍历显示列表中的所有显示对象,然后一一进行渲染,并没有分为收集和渲染两个步骤;那么在Away3D中3D显示列表也是树形结构,也可以采用Starling的方法来遍历绘制,特别的是Starling采用画家算法,所以需要得到谁先绘制谁后绘制的正确顺序,而Away3D使用的是ZBuffer算法,无论谁先绘制最终都会呈现一样的结果,那么是不是说Away3D的渲染就更加简单了呢?当然不是,Away3D由于是存在一个3D空间中,所以最终的绘制对象需要结合其摄像机的镜头对准的区域来决定,不在可视区域的对象就不进行绘制,即视锥剔除,可以大大的提高渲染效率;那么Away3D的实体收集其核心就是得到需要渲染的3D对象,去掉不需要渲染的3D对象的过程。

我们看看收集实体对象的代码:

1 // collect stuff to render
2 _scene.traversePartitions(_entityCollector);
查看这个方法:


1 public function traversePartitions(traverser:PartitionTraverser):void
2 {
3     var i:uint;
4     var len:uint = _partitions.length;
5     
6     traverser.scene = this;
7     
8     while (i < len)
9         _partitions[i++].traverse(traverser);
10 }

我们发现一个陌生的类型Partition3D,而几乎所有的实体收集都是由该类接手处理的,那么这个类究竟是什么呢?每一个Scene3D在初始化的时候都会创建一个_partitions:Vector.<Partition3D>。Partition3D是一个空间分区系统的核心,它用于将三维场景分级成多个互不重叠的子空间,从而形成一个树型数据结构。

接着查看traverse方法:


1 public function traverse(traverser:PartitionTraverser):void
2 {
3     if (_updatesMade)
4         updateEntities();
5     
6     ++PartitionTraverser._collectionMark;
7     
8     _rootNode.acceptTraverser(traverser);
9 }

我们发现了一个_rootNode对象,该对象是记录Partition3D包含的所有对象的树形结构的root,我们接下来看看acceptTraverser方法:


1 public function acceptTraverser(traverser:PartitionTraverser):void
2 {
3     if (_numEntities == 0 && !_debugPrimitive)
4         return;
5     
6     if (traverser.enterNode(this)) {
7         var i:uint;
8         while (i < _numChildNodes)
9             _childNodes[i++].acceptTraverser(traverser);
10         
11         if (_debugPrimitive)
12             traverser.applyRenderable(_debugPrimitive);
13     }
14 }

注意参数traverser就是我们的实体收集对象的实例_entityCollector,调用的方法enterNode即视锥剔除,会去掉不需要渲染的对象,而实际上添加需要渲染的对象是NodeBase的子类EntityNode的子类MeshNode,我们分别看看EntityNode和MeshNode的acceptTraverser方法:

EntityNode:

1 override public function acceptTraverser(traverser:PartitionTraverser):void
2 {
3     traverser.applyEntity(_entity);
4 }
MeshNode:


1 override public function acceptTraverser(traverser:PartitionTraverser):void
2 {
3     if (traverser.enterNode(this)) {
4         super.acceptTraverser(traverser);
5         var subs:Vector.<SubMesh> = _mesh.subMeshes;
6         var i:uint;
7         var len:uint = subs.length;
8         while (i < len)
9             traverser.applyRenderable(subs[i++]);
10     }
11 }

最终需要渲染的对象都会被收集,交给下一步的渲染代码进行渲染。



Partition3D将我们的3D空间切割为多个不重合的区域,那么如果一个实体对象移动到另一个Partition3D对象的区域,或改变尺寸跨越多个Partition3D对象时Away3D又是如何处理的呢?

我们看看实体类Entity的notifySceneBoundsInvalid方法,当我们的实体对象位置或尺寸改变时会调用该方法:

1 private function notifySceneBoundsInvalid():void
2 {
3     if (_scene)
4         _scene.invalidateEntityBounds(this);
5 }
这个方法会通知到我们的场景对象调用invalidateEntityBounds方法:

1 arcane function invalidateEntityBounds(entity:Entity):void
2 {
3     entity.implicitPartition.markForUpdate(entity);
4 }
markForUpdate方法会重新将我们的实体对象分配到对应的Partition3D对象中去。



另外有一个大神发现了实体回收的bug,链接贴出来:

Away3D 的实体收集器Bug


5#
 楼主| 发表于 2016-6-23 12:33:29 | 只看该作者
(三):渲染核心流程(渲染)
还是老样子,我们还是需要先简略的看一下View3D中render方法的渲染代码,已添加注释:

复制代码
1 //如果使用了 Filter3D 的话会判断是否需要渲染深度图, 如果需要的话会在实际渲染之前先渲染深度图
2 if (_requireDepthRender)
3     //深度图会被渲染到 _depthRender 这个贴图对象上
4     renderSceneDepthToTexture(_entityCollector);
5
6 // todo: perform depth prepass after light update and before final render
7 if (_depthPrepass)
8     renderDepthPrepass(_entityCollector);
9
10 _renderer.clearOnRender = !_depthPrepass;
11
12 //判断是否使用了滤镜
13 if (_filter3DRenderer && _stage3DProxy._context3D) {
14     //使用滤镜会进行特殊的渲染
15     _renderer.render(_entityCollector, _filter3DRenderer.getMainInputTexture(_stage3DProxy), _rttBufferManager.renderToTextureRect);
16     //滤镜渲染对象再渲染一次
17     _filter3DRenderer.render(_stage3DProxy, camera, _depthRender);
18 } else {
19     //如果没有使用滤镜则直接使用默认的渲染对象进行渲染
20     _renderer.shareContext = _shareContext;
21     if (_shareContext)
22         _renderer.render(_entityCollector, null, _scissorRect);
23     else
24         _renderer.render(_entityCollector);
25     
26 }
复制代码


添加filters包中的滤镜可以通过设置View3D的filters3d属性,设置方法和原生显示对象的filters属性一致,不过需要注意的是该属性是针对整个3D场景渲染的。

本笔记的渲染流程解读就暂时抛开滤镜的渲染,专门解读核心的渲染流程。



说到Away3D的渲染,那么away3d.core.render这个包就一定要看一下,我们打开这个包后会发现多个以Renderer结尾的类,这些类都是核心的渲染类,我们主要查看DefaultRenderer类即可,Away3D默认的渲染类就是该类。

渲染流程:

我们先看看DefaultRenderer类的render方法,位于其父类RendererBase中:

复制代码
1 arcane function render(entityCollector:EntityCollector, target:TextureBase = null, scissorRect:Rectangle = null, surfaceSelector:int = 0):void
2 {
3     if (!_stage3DProxy || !_context)
4         return;
5     
6     //更新摄像机的投影矩阵
7     _rttViewProjectionMatrix.copyFrom(entityCollector.camera.viewProjection);
8     _rttViewProjectionMatrix.appendScale(_textureRatioX, _textureRatioY, 1);
9     
10     //执行渲染的方法
11     executeRender(entityCollector, target, scissorRect, surfaceSelector);
12     
13     // clear buffers
14     for (var i:uint = 0; i < 8; ++i) {
15         _context.setVertexBufferAt(i, null);
16         _context.setTextureAt(i, null);
17     }
18 }
复制代码
我们发现实际是执行executeRender这段代码,先看DefaultRenderer的部分:

复制代码
1 protected override function executeRender(entityCollector:EntityCollector, target:TextureBase = null, scissorRect:Rectangle = null, surfaceSelector:int = 0):void
2 {
3     //更新光照数据
4     updateLights(entityCollector);
5
6     //供 filters3d 使用, 我们可以不看
7     // otherwise RTT will interfere with other RTTs
8     if (target) {
9         drawRenderables(entityCollector.opaqueRenderableHead, entityCollector, RTT_PASSES);
10         drawRenderables(entityCollector.blendedRenderableHead, entityCollector, RTT_PASSES);
11     }
12
13     //父级方法
14     super.executeRender(entityCollector, target, scissorRect, surfaceSelector);
15 }
复制代码
再看RendererBase的部分:

复制代码
1 protected function executeRender(entityCollector:EntityCollector, target:TextureBase = null, scissorRect:Rectangle = null, surfaceSelector:int = 0):void
2 {
3     _renderTarget = target;
4     _renderTargetSurface = surfaceSelector;
5     
6     //对收集到的实体对象进行排序
7     if (_renderableSorter)
8         _renderableSorter.sort(entityCollector);
9     
10     //渲染到一个纹理, DefaultRenderer 中始终为 false, 略过即可
11     if (_renderToTexture)
12         executeRenderToTexturePass(entityCollector);
13     
14     //如果 target 存在则渲染到 target 对象, 否则渲染到后台缓冲区
15     _stage3DProxy.setRenderTarget(target, true, surfaceSelector);
16     
17     //清除 3D 场景
18     if ((target || !_shareContext) && _clearOnRender)
19         _context.clear(_backgroundR, _backgroundG, _backgroundB, _backgroundAlpha, 1, 0);
20     //设置深度测试
21     _context.setDepthTest(false, Context3DCompareMode.ALWAYS);
22     //设置裁剪区域
23     _stage3DProxy.scissorRect = scissorRect;
24     //背景渲染, 这个可以在底部绘制一个图像, 具体可以参考 BackgroundImageRenderer 类
25     if (_backgroundImageRenderer)
26         _backgroundImageRenderer.render();
27     
28     //调用绘制方法
29     draw(entityCollector, target);
30     
31     //和 Starling 集成时需要还原深度测试
32     //line required for correct rendering when using away3d with starling. DO NOT REMOVE UNLESS STARLING INTEGRATION IS RETESTED!
33     _context.setDepthTest(false, Context3DCompareMode.LESS_EQUAL);
34     
35     //是否需要截屏到 _snapshotBitmapData 对象中
36     if (!_shareContext) {
37         if (_snapshotRequired && _snapshotBitmapData) {
38             _context.drawToBitmapData(_snapshotBitmapData);
39             _snapshotRequired = false;
40         }
41     }
42     //清空裁剪区域
43     _stage3DProxy.scissorRect = null;
44 }
复制代码
我们先看看对实体对象进行排序的类,位于away3d.core.sort包中(包含接口IEntitySorter和类RenderableMergeSort),我们大概了解一下排序类的功能:通过排序获得更好的渲染效率。实体对象会先根据其使用的纹理和与摄像机之间的距离进行排序,不透明的物体会按从前到后的顺序排序,有混合模式的物体也会进行排序以确保得到正确的效果。

我们发现主要的渲染是通过调用自身的draw方法进行的,我们看下DefaultRenderer的draw方法:

复制代码
1 override protected function draw(entityCollector:EntityCollector, target:TextureBase):void
2 {
3     //设置混合因子
4     _context.setBlendFactors(Context3DBlendFactor.ONE, Context3DBlendFactor.ZERO);
5
6     //如果存在天空盒就先绘制天空盒
7     if (entityCollector.skyBox) {
8         if (_activeMaterial)
9             _activeMaterial.deactivate(_stage3DProxy);
10         _activeMaterial = null;
11
12         _context.setDepthTest(false, Context3DCompareMode.ALWAYS);
13         drawSkyBox(entityCollector);
14     }
15
16     //设置深度测试
17     _context.setDepthTest(true, Context3DCompareMode.LESS_EQUAL);
18
19     var which:int = target? SCREEN_PASSES : ALL_PASSES;
20     //渲染不透明的实体对象
21     drawRenderables(entityCollector.opaqueRenderableHead, entityCollector, which);
22     //渲染带有混合模式的实体对象
23     drawRenderables(entityCollector.blendedRenderableHead, entityCollector, which);
24
25     //设置深度测试
26     _context.setDepthTest(false, Context3DCompareMode.LESS_EQUAL);
27
28     //销毁 _activeMaterial 对象
29     if (_activeMaterial)
30         _activeMaterial.deactivate(_stage3DProxy);
31
32     _activeMaterial = null;
33 }
复制代码
我们来看看drawRenderables方法实现了什么功能:

复制代码
1 private function drawRenderables(item:RenderableListItem, entityCollector:EntityCollector, which:int):void
2 {
3     var numPasses:uint;
4     var j:uint;
5     var camera:Camera3D = entityCollector.camera;
6     var item2:RenderableListItem;
7
8     //item 是类似于链表的结构, 通过 item 的 next 属性遍历所有需要渲染的实体对象
9     while (item) {
10         //获取纹理对象
11         _activeMaterial = item.renderable.material;
12         _activeMaterial.updateMaterial(_context);
13
14         //获取纹理的 Pass 数量, 每个纹理可以附带多个 Pass 对象, Pass 对象进行最终的绘制操作
15         numPasses = _activeMaterial.numPasses;
16         j = 0;
17
18         //下面是遍历纹理中的所有 Pass 对象
19         do {
20             item2 = item;
21
22             var rttMask:int = _activeMaterial.passRendersToTexture(j)? 1 : 2;
23
24             //这个判断还是判断是否需要渲染到纹理
25             if ((rttMask & which) != 0) {
26                 //激活 Pass 对象
27                 _activeMaterial.activatePass(j, _stage3DProxy, camera);
28                 //渲染纹理相同的后续实体对象
29                 do {
30                     //使用 Pass 对象进行渲染
31                     _activeMaterial.renderPass(j, item2.renderable, _stage3DProxy, entityCollector, _rttViewProjectionMatrix);
32                     item2 = item2.next;
33                 } while (item2 && item2.renderable.material == _activeMaterial);
34                 //取消 Pass 对象的激活
35                 _activeMaterial.deactivatePass(j, _stage3DProxy);
36             } else {
37                 //跳过纹理相同的后续实体对象
38                 do
39                     item2 = item2.next;
40                 while (item2 && item2.renderable.material == _activeMaterial);
41             }
42
43         } while (++j < numPasses);
44
45         item = item2;
46     }
47 }
复制代码
其实到这里我们可以看到,Away3D中的渲染实际是遍历所有的SubMesh对象,通过调用SubMesh所指定的Material的renderPass方法来进行实际的渲染,而renderPass方法的第一个参数表示使用第几个Pass进行渲染,通过改变该参数到达遍历所有Pass对象进行渲染的目的。



接下来我们的解读暂停一下,看看新出现的Pass和Method对象:

每一类纹理对应一个或多个Pass,其提供主要的渲染控制,每种纹理对应不同的渲染方式,具体点说就是主要提供AGAL的代码。
每一个Pass可以添加多个Method,每一个Method对应一段追加的渲染脚本,本意就是在现有的渲染流程中提供一定的变数,比如颜色转换,纹理模糊、阴影等。
可以理解为Pass提供核心的渲染代码,而Method可以提供类似原生显示列表中的滤镜的功能,每当给一个纹理添加一个Method之后,对应的Method会对该Pass的片段着色器添加多行实现代码,同时也会提供部分可变参数作为常量提交到GPU。

具体的把Method的AGAL代码合并到Pass中是在ShaderCompiler的compileMethods方法中实现的。



下面我们继续解读渲染代码,我们看看MaterialBase类的renderPass方法:

复制代码
1 arcane function renderPass(index:uint, renderable:IRenderable, stage3DProxy:Stage3DProxy, entityCollector:EntityCollector, viewProjection:Matrix3D):void
2 {
3     //如果存在光源则处理光源
4     if (_lightPicker)
5         _lightPicker.collectLights(renderable, entityCollector);
6     
7     //获取对应的 pass 对象
8     var pass:MaterialPassBase = _passes[index];
9     
10     //存在动画则更新动画状态
11     if (renderable.animator)
12         pass.updateAnimationState(renderable, stage3DProxy, entityCollector.camera);
13     
14     //使用 pass 进行渲染
15     pass.render(renderable, stage3DProxy, entityCollector.camera, viewProjection);
16 }
复制代码
我跟着Basic_View示例进行调试时进入了CompiledPass类的render方法,当前随着不同的Pass对象,实际进行渲染的代码会不一样:

View Code
代码较多,也没有加上注释,大体上是对光源、动画、Method的处理,最后调用了Context3D的drawTriangles方法,对我们当前的Mesh对象进行了最终的绘制。

天道酬勤,功不唐捐!


6#
 楼主| 发表于 2016-6-23 13:03:37 | 只看该作者
查看当前正在使用的AGAL代码可以在程序开始时添加下面的代码,AGAL代码会被trace出来:
1 Debug.active = true;
具体的输出是在MaterialPassBase类的updateProgram方法中。
-----
使用stereo包(立体包)渲染的图像可以配合红蓝眼立体镜来查看立体效果。
-----
SkyBox是不可到达的对象,会优先被渲染(所以总是处于最后方),一般一个场景只会包含一个SkyBox。
(四):主要模块简介
数据模块:
Away3D中最核心的数据类是Mesh类,我们先看看Mesh类的继承关系:
NamedAssetBase:为对象提供id和name属性,是Away3D大部分类的基类;
Object3D:3D对象基类,提供方便操作3D对象本地转换矩阵的功能,提供坐标、旋转、缩放等属性和较多的实用方法,如lookAt、moveLeft等,注意Object3D对象并不是可渲染对象;
ObjectContainer3D:作为可存放3D对象的容器,是构成显示列表树形结构的核心,提供sceneTransform属性可直接获取当前容器位于场景中的转换矩阵;
Entity:实体对象是所有可以渲染对象的抽象基类,其可以被Partition3D对象分隔从而被EntityCollector类收集;
Mesh:网格对象,是Away3D中核心的3D网格渲染单元,我们下面会详细的讨论他;
我们再看看away3d.entities包中的其他类:
SegmentSet:线段集合,该类配合LineSegment类可以再3D空间中绘制线段;
Sprite3D:始终面向摄像机的面片对象,Billboard系统;
TextureProjector:这个类配合ProjectiveTextureMethod类可以得到类似贴花的效果,即可以把一个贴图添加到一个已经存在纹理贴图的模型上,但和贴花不同的是,我们的贴图不会跟随模型的缩放、位移或旋转等一同改变;具体可以设想一下,一个人物站在一个窗口,这个窗口有一束光射入到人身上,同时这个窗口上是存在图像的,那么这个图像就应该会映射到人物的身上,但这个图像不应该一直跟随这人物,当人物移动时,射入的图案应该还是在原理的位置;具体代码示例可以查看这个,大家跑一下示例就清楚了;

其实Mash对象包含了渲染需要使用的所有数据:包括顶点数据、纹理数据、动画数据以及转换矩阵等等;
但是我们这篇笔记只关心模型的数据。

Geometry
我们知道Mesh对象其实包含了渲染需要的所有对象,那么模型的顶点数据是存放在哪个对象中呢?答案是Geometry对象。
一个Mesh对象带有多个SubMesh对象,同时带有一个Geometry对象,Geometry对象又带有多个ISubGeometry对象,SubMesh对象与ISubGeometry对象一一对应。
Geometry类型的对象包含了一个模型的顶点数据;
  • SubMesh是渲染的最基础单元。
  • SubGeometry是最基础的顶点数据单元,渲染时负责数据往显存的提交。
  • Material是最基础的材质单元,渲染时负责材质数据往显存的提交。

纹理模块
Material类包含了具体渲染的逻辑,包括灯光和各种Method的渲染;
  • 每一类纹理对应一个Pass提供主要的渲染控制,每种纹理对应不同的渲染方式。
  • 每一个Method对应一段追加的渲染脚本,Method本意就是在现有的渲染流程中提供一定的变数,比如颜色转换,纹理模糊、阴影等。
贴图
Texture对象不同于纹理对象,Texture对象专门用来解析和保存纹理使用的位图数据,同时负责上传数据到GPU,而Material除了可以包含Texture外还可以同环境发生交互,如光源和添加特效等。

灯光模块
灯光是一个实体对象,在Away3D中存在两种光源类型,分别是直线光(DirectionalLight)和点光(PointLight)。
渲染
与Mesh类似,参与scene3D的裁剪过滤,enityCollector.XXXlights记录当前光照渲染;
在DefaultRenderer类的模型渲染(drawRenderable)之前,UpdateLights-->renderDepthMap,shadowMapper.renderDepthMap(stage3D, entityCollector, _distanceRenderer),渲染流程和Mesh类似。

摄像机模块
Camera3D也是继承自实体类,表示当前场景中的摄像机对象,目前Away3D一个场景(Scene3D)中只支持一个摄像机;
镜头
摄像机支持设置镜头类型,比较常用的镜头类型有两种:
  • OrthographicLens:正交镜头。
  • PerspectiveLens:透视镜头。
Controller
根据相对运动原理,控制观察者相机,也就等于控制了世界范围的整体显示。CameraController通过提供适用于不同场景的控制接口,更改相机坐标,提供用户更容易使用和理解的场景控制接口。
所有摄像机控制类存放在away3d.controllers包中;
LookAtController:
  • 固定点观察控制 表现特征,目光视角跟随物体移动;
  • 只观察方向,不修改其他 ;
HoverController:
  • 围绕某个观察事物进行旋转观察的控制器,事物展示适用,街景等;
  • 可以设置lookatPosition来设置观察点;
  • 提供设置围绕对象旋转的角度控制;
  • 提供distance设置距离对象的距离;
FollowController:
  • 跟随视角控制,绕当前目标物的四周环绕观察,类似极品飞车后跟随视角;
  • 修改lookatObject物体角度,视角动态更新水平旋转以跟随物体变化;
  • 其他同HoverController;
  • 注:因为水平角度动态跟随lookatObject,则设置panAngle无效;
FirstPersonController:
  • 第一人称视角控制属性;
  • incrementWalk:来调整前后属性;
  • incrementStrafe:调整左右属性;
  • fly:标记在修改的时候是否根据视角来动态修改视角高度。设置为true可以看到一个忽高忽低的飞行效果;
  • panAngle,tiltAngle:设置前后左右旋转角度 ;
SpringController:
  • 胡克定律运动,定义一个相机在两点之间运动的动画渐进效果 ;

拾取模块
拾取是处理鼠标事件的基础,即解决鼠标下面是什么的问题。
位于away3d.core.pick包中;
RaycastPicker拾取原理:通过点、相机获得射线,计算射线与Mesh三角面的相交情况;
注意:AS3_BEST_HIT 与AS3_FIRST_ENCOUNTERED的区别在于是否对相交的面做排序并找出第一个相交的面及焦点坐标

动画模块
动画模块会另外作为一个重点详解;

加载模块
负责模型加载解析,功能主要分为三块:
  • 文件资源读取;
  • 模型三角面片/对应纹理解析;
  • 资源标记;
Loader功能类职责
AssetLoader
资源加载控制类,负责针对不同URL或者二进制数据进行解析,对模型所附带的资源列表(uv,纹理图片)进行逐个加载解析。内含对AssetLoaderContext中设置属性的使用,对parse()的调用,对resourceDependency的逐个调用或者直接接卸(retrieveAsRawData);
ResourceDependency
记录资源URL(或ID)与解析文件的对应关系。
SingleFileLoader
实质性的文件加载类,解析调度类。内含URLLoader引用负责加载数据,根据对数据类型的判断(或用户设置)来获取对应数据解析器parser对数据做最终的解析。
Parser
定义了当前支持的几种类型的数据解析器,使用AssetLoder. enableParsers 可以设置SingleFileLoader当前支持的解析类型;
ParserBase
解析器基类,各具体类型的解析器继承自此。负责解析过程的调用,异步逐帧加载,事件的分发等。
各个XXXParser
具体的解析类,很直观,不同类型的模型文件对应不同的具体解析器的内容转换成3D可显示的模型并封装在自身容器中。
AssetLibrary
AssetLibraryBundle 这两个类提供静态方法实现模型加载和全局的模型资源管理。
AssetLoaderContext
负责资源加载过程的的一些全局控制属性,主要集中在对于资源URL的重定向控制,已加载的二进制资源命名管理等。一般情况下不用;
AssetLoaderToken
内含一个AssetLoader引用,负责资源加过程中的事件控制;
Loader3D
继承自ObjectContainer3D,内含一个AssetLibraryBundle或AssetLoader实例(通过属性_useAssetLib属性来控制选择使用全局资源管理还是单独构建),可以直接将加载解析;


7#
 楼主| 发表于 2016-6-23 13:07:22 | 只看该作者
(五):动画模块及骨骼动画
动画模块核心存放在away3d.animators包里;
Away3D支持下面几种动画格式:
  • VertexAnimator:顶点动画
  • SkeletonAnimator:骨骼动画
  • UVAnimator:UV动画
  • SpriteSheetAnimator:二维切换动画
  • ParticleAnimator:粒子动画
  • PathAnimator:路径动画
这几种动画都有各自的特点及应用场景,一般而在3D游戏中应用得最广泛的是骨骼动画,因为骨骼动画是人物动画的核心,我们下半段会专门详解这个动画;

动画简介
核心类
IAnimator
提供控制动画播放的接口,具体的动画类型对应具体的实现类,如骨骼动画使用SkeletonAnimator类;
将该对象赋值到对应的Mesh的animator属性既可以控制该Mesh对象播放动画;
对于播放的所有动画都应该存放到IAnimationSet对象中;
IAnimationSet
提供管理多个动画的容器,多个动画如奔跑或攻击都需要添加到对应的Set容器后才能进行播放;
data包
动画所需的数据对象元变化(帧)信息;
nodes包
一串变化组成的一个独立动画节点;
states包
动画当前状态,从Nodes提取以供当前显示的动画元(帧)信息;

渲染流程
Away3D中的动画渲染是双线更新渲染的,一个更新位于AnimatorBase类的方法update中,负责更新动画数据,一个更新位于MaterialBase类的renderPass方法中,负责渲染绘制;
具体的更新都是更具当前时间来更新对应的State对象,更新具体流程可以具体的打断点查看,这里就不深入解读了。

骨骼动画
骨骼动画在网络上有大量的资料,大家可以去网上搜搜看,这里主要说一下Away3D中是如何处理骨骼动画的数据的;
SkeletonJoint
由于多个关节之间是可以相互链接的(父关节变化时会影响到子关节),该对象用来保存关节的链接信息;
Skeleton
骨骼对象包含了所有关节的链接信息(SkeletonJoint);
JointPose
保存一个关节的信息,包括3个数据:名称、四元数(用于旋转)和转换信息;
SkeletonPose
记录一个关键帧下所有的关节的信息(每个关节的位置及旋转数据),包含所有的关节对象(JointPose)列表;
SkeletonClipNode
包含了一个骨骼动画的所有关键帧数据(SkeletonPose列表),同时还可以为每个关键帧指定一定的间隔;
SkeletonClipState
更新特定时间点的骨骼数据,骨骼动画的插值运算;
SkeletonAnimationSet
存放多个骨骼动画(SkeletonClipNode)的容器,使骨骼动画(SkeletonClipNode)和其name属性关联起来,配合SkeletonAnimator的play方法可以指定播放哪个骨骼动画(SkeletonClipNode);
SkeletonAnimator
骨骼动画的播放类,可以设定到指定Mesh上,需要指定一个骨骼容器(SkeletonAnimationSet)和对应的骨骼对象(Skeleton);

Away3D物理引擎的简介与使用
首先我们要了解的是AwayPhysics这个物理引擎并不是重头开始写的新物理引擎,而是使用Flascc技术把一个已经很成熟的Bullet物理引擎引入到了Flash中,同时为了让as3可以使用这个C++开发的物理引擎,AwayPhysics库编写了一些必要的AS3类使我们可以方便的使用它。

而为了方便区分和使用AwayPhysics里的所有类都使用了AWP作为类名前缀。
创建物理世界
要模拟一个物理世界,首先需要创建一个AWPDynamicsWorld的对象,该对象类似于Away3D中创建3D世界的View3D对象,同时每帧也需要类似View3D对象调用render方法一样调用AWPDynamicsWorld对象的step方法;
创建方法:

1 //获取物理世界
2 _physicsWorld = AWPDynamicsWorld.getInstance();
3 //初始化, 主要包括初始化重力等
4 _physicsWorld.initWithDbvtBroadphase();
step方法一般使用下面的参数:

1 var _timeStep:Number = 1 / 60;//60表示帧率
2 _physicsWorld.step(_timeStep, 1, _timeStep);
关于该方法的更多信息可以参考这里;

而正常来说应该是先调用step方法模拟了物理世界之后在调用View3D的render方法来渲染3D世界;

该类有添加和移除各种刚体的方法,可以方便的添加需要使用物理引擎模拟的对象;


输出调试图形

如果我们需要查看调试的物件信息(可以查看到添加到物理世界的刚体的坐标系和形状),可以使用AWPDebugDraw类:

复制代码
1 private var _debugDraw:AWPDebugDraw;
2
3 //构造函数中创建
4 _debugDraw = new AWPDebugDraw(_view, _physicsWorld);
5 _debugDraw.debugMode |= AWPDebugDraw.DBG_DrawTransform;
6
7 //帧事件方法中调用绘制
8 _debugDraw.debugDrawWorld();
复制代码
需要注意的一点是,debugDrawWorld这个方法会严重拉低运行的帧率,当然作为调试工具,我们不需要高效的运行效率,需要调试时别忘了这个类哦。


基础使用简介

我们搭建好了物理世界后就可以向物理世界中添加对象了:

首先,AwayPhysics提供了可以用来进行碰撞检测的多种3D形状,存放在awayphysics.collision.shapes包中,我们可以根据需要创建适合的碰撞形状;
然后,我们需要添加一个刚体(AWPRigidBody),将我们的碰撞形状和3D显示对象进行绑定;
最后,我们可以调整刚体的各种属性(如果调整刚体的坐标旋转等信息会实时同步到该刚体绑定的3D对象中),添加到物理世界中即可,当我们的刚体添加到物理世界后他的格子状态就都由物理引擎来模拟了;
示例代码:

复制代码
1 //创建地面 Mesh
2 var material:ColorMaterial = new ColorMaterial(0x252525);
3 material.lightPicker = lightPicker;
4 var mesh:Mesh = new Mesh(new PlaneGeometry(50000, 50000), material);
5 mesh.mouseEnabled = true;
6 mesh.addEventListener(MouseEvent3D.MOUSE_UP, onMouseUp);
7 _view.scene.addChild(mesh);
8
9 //创建地面形状和刚体
10 var groundShape:AWPStaticPlaneShape = new AWPStaticPlaneShape(new Vector3D(0, 1, 0));
11 var groundRigidbody:AWPRigidBody = new AWPRigidBody(groundShape, mesh, 0);
12 //添加到物理世界
13 _physicsWorld.addRigidBody(groundRigidbody);
复制代码

碰撞检测

要接收碰撞检测事件需要开启一下标志,这样就可以接收事件了:

1 //开启碰撞侦听, AWPEvent.COLLISION_ADDED 事件被启用
2 _physicsWorld.collisionCallbackOn = true;
碰撞对象:

要使用碰撞检测我们就不用刚体对象(AWPRigidBody)了,要使用碰撞对象(AWPCollisionObject);

同时添加到物理世界时也要使用addCollisionObject方法;

碰撞对象可以添加多个射线,用于射线碰撞检测;

碰撞事件:

碰撞事件都被封装好了,有两个AWPEvent.COLLISION_ADDED和AWPEvent.RAY_CAST:

COLLISION_ADDED:当有另一个碰撞物体和自己发生碰撞时会抛出该事件,注意并没有COLLISION_REMOVED等表示碰撞结束的事件,这是由于每次调用step方法模拟时都会重新运算碰撞,所以如果存在碰撞就会一直抛出该事件。
RAY_CAST:如果添加过射线,那么射线和其它碰撞对象碰撞时也会受到该事件。
游戏应用
我们通过上面的代码可以模拟真实的物理世界,但这是不够的,更多的时候我们需要使用物理引擎来帮助我们实现游戏中的逻辑,而常见的需求是赛车游戏(车辆和地面与其它物体的碰撞)和全3D的RPG游戏(人物和高低不平的地面的碰撞)。
赛车:
AwayPhysics提供了两个关于赛车的Demo,点击查看源码:Demo1Demo2
由于AwayPhysics已经帮我们封装好了专门用来控制车辆的类,所以创建一个小赛车的游戏是十分简单的,下面说说会使用到的类:
车辆控制类:
  • AWPCompoundShape:这个类可以添加多个基础的形状来达到组合成一个复杂形状的目的,小车使用该类组合了两个长方体;
  • AWPVehicleTuning:这个类用来设置车辆的调节属性;
  • AWPRaycastVehicle:将AWPVehicleTuning和小车刚体进行绑定的类,并且最终会调用_physicsWorld.addVehicle方法添加到物理世界中;
  • AWPRaycastVehicle.addWheel:该方法可以对指定的轮子对象进行绑定;
  • AWPRaycastVehicle.getWheelInfo:该方法可以微调每个轮子的属性;
  • AWPRaycastVehicle.applyEngineForce:该方法可以控制指定的轮子的引擎力,即踩油门,设置为负值可以倒车;
  • AWPRaycastVehicle.setBrake:该方法可以控制指定的轮子的刹车;
  • AWPRaycastVehicle.setSteeringValue:该方法可以控制指定的轮子的转向,即转弯;
地形生成类(直接加载模型作为地形):
  • AWPBvhTriangleMeshShape:我们将加载好的Mesh的geometry属性赋值到该对象后,就创建了一个和Mesh对象一致的刚体形状,再将该对象添加到刚体中即可作为自定义的地形使用。
地形生成类(使用高度图生成地形):
  • AWPTerrain:该对象是一个可视对象,是由一个高度位图创建的Mesh对象。
  • AWPHeightfieldTerrainShape:将我们的AWPTerrain对象赋值到该对象后,就创建了一个和Mesh对象一致的刚体形状,再将该对象添加到刚体中即可作为自定义的地形使用。
RPG:
AwayPhysics提供了一个关于行走的Demo,点击查看源码:Demo
地形生成同上,我们看看不一样的地方:
  • AWPGhostObject:该类用来连接一个形状对象和一个显示对象;
  • AWPKinematicCharacterController:角色控制类,可以设置一个AWPGhostObject对象作为其控制的对象;
  • AWPKinematicCharacterController.setWalkDirection:该方法接收一个3D向量作为移动的距离;
  • AWPKinematicCharacterController.jump:该方法可以用来进行跳跃;

补充
除了我们谈到的几点外,大家还可以查看示例程序了解更多的使用技巧:


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|吾知网 ( 粤ICP备13013563号-1 )

GMT+8, 2024-12-22 20:37 , Processed in 1.140625 second(s), 7 queries , Memcache On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表