Z-sorting (深度排列)
介绍下面三个方面:
a : 什么是Z-sorting(深度排序)
b : layering scene renders (分层渲染场景)
c : 用四叉树进行复杂的渲染的渲染
注:viewport层 就是ViewportLayer类的实例.它包含在viewport 内
Z-sorting的解释 :
在前面的章节中你一定碰到过Z-sorting(深度排序)的问题,你用的3D模型(model)越复杂,如果Z-sorting控制不准确,导致的问题也将会越来越明显 。
下面两幅图显示了两个茶壶,它清楚的显示了什么是Z-sorting(深度排序).
上面的图显示有一部分平面不可见,喷口后面的平面由于排在茶壶身体所在面的后面(即就是排在后面,我们不可见的一面),因而不可见,这就是利用了Z-sorting 。
The painter's algorithm (排序算法)
Z-sorting(深度排序)方法将决定每个平面在scene里的显示深度,其深度位置是按照scene里的平面(即3D对象的某一面)距camera的距离来决定的 。在排序时判断哪个平面应该在前面是非常耗cpu的(The process of determining which triangle should be in front is CPU intensive.)。默认的,papervision3d 用一种非常快速但不是很精确的算法来为scene里的每个平面排序。该算法就是The painter's algorithm ,它的主旨是:远处的物体应在近处的物体之前被显示出来(即先显示远处的物体,再显示近处的物体)
该算法的流程如下 :
第一:所有在camera视图里的平面(3D对象的某一面)是按照距camera的距离,由远到近进行排序的。
第二:所有的平面(3D对象的某一面)是按照已排好的顺序依次显示在scene上
即:最先开始显示最远处的的对象 然后由远及近依次显示各个对象。最近的对象最后显示。
上面 最左边的图里的山是离camera最远的,所以它最先显示出来,然后显示中间那张图里的草型区域,最后显示右图里的树林。
Sorting triangles ——对平面进行排序
为了更好的理解和深度排序有关的问题,我们需要更好的理解深度排序,下面是一幅标有注解的图 。注:triangle :平面 。
按照The painter's algorithm排序算法,则最先显示的是最远处的平面对象。所以先显示平面B,然后显示平面A 。
下面的两幅图,左边是按由近及远顺序显示的图(图Expected),右边是按照深度排序的方法显示的图(图painter's algorithm)
有一个很有效的方法去解决上面显示不明了的问题,即对B平面进行再分 。
下图是对平面细分的指导 :
上面的平面B被再细分成平面B和平面C 。渲染scene时,将会首先显示平面C然后是平面A, 最后是平面B 。
下图是再分后的结果:
如果换一个视角,将会导致重新排序,平面显示的顺序依旧是由远到近 ,只是此时各个平面距camera的距离发生了改变。如下图:
这时,平面A将会最先显示,然后是B 最后是C 。但是这里将会导致一个Z-sorting问题:即平面A将会和B重叠 。
Layering your renders :
为了增加3D对象的显示效果,可以增加多个三角形平面(即创建更多的segment),但它不是最好的解决方案。因为当多个三角形平面的顶点(vertex)投影到scene上时,三角形平面越多,投影到scene上的点也越多,因而消耗的cpu也越多 . 更好的解决办法是用viewport(视口) 层,这些viewport(视口)层被嵌套在viewport里(因为viewport可以多方位旋转,因而有多个viewport层)。 这些viewport(视口)层的概念与photoshop和flash里层的概念一样,他们的工作原理一样 ,最上层的总是显示在最前面 。在photoshop里,每层只是放一种元素或者一种混合元素。在Pv3d里也是一样,每层只放一种元素。例如:我们可以将椅子放在某一层,然后将桌子放在另一层。每层的排列顺序可以通过pv3d
里算法实现。<该段主要是讲pv3d层的概念:每个层只能放一种元素,该元素既可以是单一的,也可以是混合的>
为了演示上述方法,我们将重用在第八章介绍过了的animatedMill模型。下面将展示一些Z-sorting(深度排序)的问题 。下面的代码将作为本章的模板。
package {
import flash.events.Event;
import org.papervision3d.core.animation.clip.AnimationClip3D;
import org.papervision3d.events.FileLoadEvent;
import org.papervision3d.materials.ColorMaterial;
import org.papervision3d.materials.WireframeMaterial;
import org.papervision3d.materials.special.CompositeMaterial;
import org.papervision3d.objects.parsers.DAE;
import org.papervision3d.objects.primitives.Plane;
import org.papervision3d.view.BasicView;
private class ViewportLayersExample extends BasicView
{
private var mill:DAE;
private var floor:Plane;
private var rotX:Number = 0.1;
private var rotY:Number = 0.1;
private var camPitch:Number = 90;
private var camYaw:Number = 270;
private var easeOut:Number = 0.1;
public function ViewportLayersExample()
{
stage.frameRate = 40;
init();
}
private function init():void
{
mill = new DAE(true,null,true);
mill.addEventListener(FileLoadEvent.LOAD_COMPLETE, modelLoaded);
mill.load("assets/animatedMill.dae");
var colorMat:ColorMaterial = new ColorMaterial(0x006600);
var wireMat:WireframeMaterial = new WireframeMaterial();
var floorMat:CompositeMaterial = new CompositeMaterial();
floorMat.addMaterial(colorMat);
floorMat.addMaterial(wireMat);
floorMat.doubleSided = true;
floor = new Plane(floorMat,1000,1000,1,1);
floor.y = -410;
scene.addChild(floor);
floor.rotationX = 90;
}
private function modelLoaded(e:FileLoadEvent):void
{
scene.addChild(mill);
var animationLeft:AnimationClip3D = new AnimationClip3D ("right",0,6);
var animationRight:AnimationClip3D = new AnimationClip3D ("left",6,12);
mill.animation.addClip(animationRight);
mill.animation.addClip(animationLeft);
mill.play("right");
startRendering();
}
override protected function onRenderTick(e:Event=null):void
{
var xDist:Number = mouseX - stage.stageWidth * 0.5;
var yDist:Number = mouseY - stage.stageHeight * 0.5;
camPitch += ((yDist * rotX) - camPitch + 90) * easeOut;
camYaw += ((xDist * rotY) - camYaw + 270) * easeOut;
camera.orbit(camPitch, camYaw);
super.onRenderTick();
}
}
}
在init()方法里,我们加载了外部模型,并把它添加到了scene里。为地板floor对象添加了由WireframeMaterial和ColorMaterial组成的混合材质(floorMat)。
最后onRenderTick(e:Event=null)方法里的几段代码是创建鼠标的交互性。移动鼠标,Camera将会绕scene原点进行旋转 ,因而我们就能看到多个方位 。
测试代码,将会是下面这样的画面 ,下面的地板floor有一些Z-sorting(深度排序)问题。
因为从底部看,模型mill在floor的后面,我们看不到mill模型 。为了能让我们在floor后面看到mill模型,下面我们用层的概念分析一下该问题 :
为了解决该问题,我们在此提出上面讲述了很多的层问题。 用层的方法可以解决mill模型不可见的问题 。 因为从底部看,模型mill在floor的后面,即floor在最上层,而mill模型在floor的下一层,因而我们看不到mill模型。 为了使mill模型在从底部看可见,我们只需改变floor和mill模型 层的顺序的即可 。下面有一些改变层顺序的方法:
用useOwnContainer属性创建一个viewport层
设置3D对象的useOwnContainer为true(3dDisplayObject. useOwnContainer=true;),将会创建一个新的层,该层在所有层的上面(即该层在最上面)。并且该3d对象被画在了该层上。因而该3d对象将不会被遮住。
在上面的代码中为了使我们从底部看mill模型可见,只需在modelLoaded()方法里加上下面一句代码:
mill.useOwnContainer = true;
加上上句代码后再次测试,发现现在的效果更好,图片如下:
换多个方位看,我们会发现不存在Z-sorting问题了。其图片显示如下:右图是从较低的点的视口看的(即看它的底部)。
把useOwnContainer设为true后,mill模型始终在最上层,因而我们始终能看到mill模型。但这是不符合真实的,
并且该属性很耗cpu。下面我们将介绍另外两种方法来代替useOwnContainer方法。下面的两种方法更符合现实。
用getChildLayer方法创建viewport层并给viewport层排序
通过viewport3D类的getChildLayer方法得到ViewportLayer的实例。
var millLayer:ViewportLayer = viewport.getChildLayer(mill);
为了使它运行,我们还需要导入ViewportLayer类:
import org.papervision3d.view.layer.ViewportLayer;
getChildLayer()的三个参数:
参数 数据类型 默认值 用法描述
do3d DisplayObject3D ———— 获得的do3d的所在层
或者为do3d创建一个新的
层(创建新的层需要第二个参数)
createNew Boolean true 是否为do3d创建一个新的
viewport层(该值一般设
为true,这样才能创建
一个新的层,因为新层
在所有层的最上面)
Recurse Boolean true 是否将do3d里的子对象也
添加到该层
当你得到新的viewport层的实例,你也可以将其他的3D对象添加到同一个层中。例如我们可以将大量mill模型添加到同一层. 作为用法演示: 我们在先前的代码的基础上 ,将floor添加到millLayer 层里 。当然这是没有意义的,这和不用层得到的效果是一样的。因为添加floor以后,floor就又在最上面了。
我们在这里只是介绍用法:
millLayer.addDisplayObject3D(floor);
实例化ViewportLayer来创建 viewport 层
下面是用法 :
var millLayer:ViewportLayer = new ViewportLayer(viewport,null);
viewport.containerSprite.addLayer(millLayer);
millLayer.addDisplayObject3D(mill,true);
ViewportLayer类的构造函数的三个参数 :
参数 数据类型 默认值 用法描述
Viewport Viewport3D ———— Viewport3D实例对象
do3d DisplayObject3D ———— 被添加进层的3d对象
在getChildLayer()
方法里可以设置Recurse为
true,将3d对象的子对象全部添加到层中。但是在
ViewportLayer构造函数中并没有Recurse这个参数,因此
在上面的代码中将do3d属性设为null,然后用addDisplayObject3D() 方法将do3d对象添加到层,
addDisplayObject3D()方法的第二参数为Recurse ,故可以在这里进行设置
isDynamic Boolean false 是否要去除层在接
下来的渲染。这个参数时pv3d内部决定的,不能在外部修改,只能在源码处进行修改
addDisplayObject3D()的一个参数为do3d 第二个为Recurse 。
Sorting layers
前面的例子展示了使用viewport 层来解决Z-sorting(深度排序)问题,但是增加多个层也会带来处理层的问题。Papervision3D有三个内置的模式(mode)去处理这个问题 。用到的是
sortMode属性 。用法如下:
viewport.containerSprite.sortMode = ViewportLayerSortMode.Z_SORT;
Papervision3D支持viewport层的3种排序算法 是ViewportLayerSortMode类三个静态常量属性
ViewportLayerSortMode.Z_SORT
ViewportLayerSortMode.ORIGIN_SORT
ViewportLayerSortMode.INDEX_SORT
在使用上面的3个mode时, 必须导入类:
import org.papervision3d.view.layer.util.ViewportLayerSortMode;
用ViewportLayerSortMode.Z_SORT给viewport 层排序 :
默认的,这些viewport 层是按照Z-sorting深度排序的算法进行排序的.他们排序的方式是基于层上的每个点(vertices)的Z坐标距camera的平均值来决定(即层内所有的点在Z轴上距camera的距离/点的总数)。因而距camera最远的层将会最先显示在scene在舞台上,然后是较远的显示。
目前,我们只为mill模型创建了一个层,但我们要用层给mill模型和floor进行排序时。我们需要将floor也添加到viewport层中(一个新的viewport层)
var millLayer:ViewportLayer = viewport.getChildLayer(mill);
var floorLayer:ViewportLayer = viewport.getChildLayer(floor);
上面的代码创建了两个新层。当你测试,从顶部和从底部看你的3D模型和floor会发现不存在Z-sorting(深度排序)问题。但是,把camera 放在floor的上面一点点,会发现又出现了Z-sorting(深度排序问题),这时会发现整个floor对象显示在mill模型前面而不是单一的一个平面。
为此,我们可以强制为某个viewport 层设置深度(即距camera的距离),如果设置floor距camera的距离为2000,因而达到为floor排序的目的 。
下面是用法:
floorLayer.forceDepth = true;
floorLayer.screenDepth = 2000;
用上述方法可以达到为viewport重新排序的目的 (即强制设置他们的深度)。
用 ViewportLayerSortMode.ORIGIN_SORT给层排序
第二种算法是基于每个三D对象的原点进行的:当用默认的z-sorting(即:ViewportLayerSortMode.Z_SORT)排序无法满足精确的要求时。例如有一个模型,在某一面上有多个点(vertices),而在其他面上的点却很少。当按上述方法对它的深度(即距camera的距离)取平均值显然是不精确的 ,这将会导致失去平衡。为了解决这种情况,我们使用
ViewportLayerSortMode.ORIGIN_SORT 排序,它是按照3D对象的原点进行排序的。当一个层中包含很多3D对象时,他们的原点将会取平均值 。
用ViewportLayerSortMode.INDEX_SORT给viewport 层(ViewportLayer)排序
这种算法是基于层的layerIndex属性来实现的。我们可以控制设定它:
var millLayer:ViewportLayer = viewport.getChildLayer(mill);
var floorLayer:ViewportLayer = viewport.getChildLayer(floor);
millLayer.layerIndex = 1;
floorLayer.layerIndex = 2;
viewport.containerSprite.sortMode = ViewportLayerSortMode.INDEX_SORT;
我们这样设置millLayer.layerIndex = 1; floorLayer.layerIndex = 2; ,将会导致scene里先创建millLayer(即离camera较远) 然后再创建floorLayer层(即离camera较近),由于floorLayer层较后创建,因而floorLayer层在的最上面。尽管这样做对floorLayer层意义不大,但是我们是借此来介绍怎样使用
layerIndex 属性 。
目前我们已经有了几种可供选择的方法来给viewport层(ViewportLayer)进行排序。但是这些方法在我们的demo(即演示例子)里的使用都不能达到正确的效果。一个可供选择的方法是将它设为只有一面可见 即:matrial.oneSide=true; 但是对于这个demo,这并不是我们想要的。
当cameral在floor的上表面和下表面时,我们需要正确(即符合实际)的观察floor和mill模型
如果我们用ViewportLayerSortMode.INDEX_SORT给层排序,当cameral在floor的上表面和者下表面之间进行变动时,使用layerIndex属性来交换viewport层(ViewportLayer)的深度
将会产生怎么的结果呢?
在 onRenderTick()方法里来设定这些属性
//注意:下面的代码是demo的代码的基础上进行的
//下面的代码是放在onRenderTick()里面的
if(camera.y<-410)
{
viewport.getChildLayer(mill).layerIndex = 1;
viewport.getChildLayer(floor).layerIndex = 2;
}
else
{
viewport.getChildLayer(mill).layerIndex = 2;
viewport.getChildLayer(floor).layerIndex = 1;
}
为了使layerIndex属性正常运行
我们还需要这样设置:
viewport.containerSprite.sortMode = ViewportLayerSortMode.INDEX_SORT;
这行代码放在modelLoaded()方法里
上述方法成功解决了demo里存在的问题但这个方法并不是在每个工程中都适用 。
Creating and sorting sublayers
不仅viewport支持viewport层(ViewportLayer),viewport 层他们自己也可以嵌套层(即层中再包含层),因而我们可以在同一个scene里使用多种排序方法。
下面我们按照图示写出相关代码:
//Create viewport layer 1 structure
var viewportLayer1:ViewportLayer = new ViewportLayer(viewport,null);
var viewportLayer1_1:ViewportLayer = new ViewportLayer(viewport, chair);
var viewportLayer1_2:ViewportLayer = new ViewportLayer(viewport, table);
viewportLayer1.addLayer(viewportLayer1_1);
viewportLayer1.addLayer(viewportLayer1_2);
viewportLayer1.sortMode = ViewportLayerSortMode.INDEX_SORT;
//Create a regular viewport layer 2
var viewportLayer2:ViewportLayer = new ViewportLayer(viewport,floor);
//Create viewport layer 3 structure
var viewportLayer3:ViewportLayer = new ViewportLayer(viewport,null);
var viewportLayer3_1:ViewportLayer = new ViewportLayer(viewport,wall);
var viewportLayer3_2:ViewportLayer = new ViewportLayer(viewport,door);
viewportLayer3.addLayer(viewportLayer3_1);
viewportLayer3.addLayer(viewportLayer3_2);
viewportLayer3.sortMode = ViewportLayerSortMode.ORIGIN_SORT;
//Add parent layers to the viewport
viewport.containerSprite.addLayer(viewportLayer1);
viewport.containerSprite.addLayer(viewportLayer2);
viewport.containerSprite.addLayer(viewportLayer3);
viewport.containerSprite.sortMode = ViewportLayerSortMode.Z_SORT;
从上面的代码中可以看出嵌套的层在处理深度排序时 和在viewport里一样 。
四叉树渲染引擎(Quadtree rendering)
四叉树渲染引擎(Quadtree rendering)可以代替我们现在默认使用的的BasicRenderEngine 引擎。我们对四叉树渲染引擎(Quadtree rendering)的算法不会深入讲解。 我们只需要知道它可以解决大部分的深度排序(Z-sorting)问题。但是四叉树渲染引擎(Quadtree rendering)非常耗CPU。尽管这样,由于它的使用非常简单,下面我们来使用它:
首先我们要新建一个工程,该工程是在ViewportLayersExample文档类(本章前面的模板,第一个代码) 的基础上建立起来。
在该类的基础上,我们需要导入四叉树渲染引擎(Quadtree rendering)的包,
import org.papervision3d.render.QuadrantRenderEngine;
然后在init()方法里加上下面的代码:
renderer = new QuadrantRenderEngine();
上面的代码代替了我们默认的BasicRenderEngine引擎。BasicRenderEngine引擎是在我们继承BasicView类时自动创建的。
实例化QuadrantRenderEngine只需要一个参数 该参数指定你要选择哪种排序的方法。这些方法是QuadrantRenderEngine类的静态常量。
QuadrantRenderEngine.CORRECT_Z_FILTER: :为不交叉的平面进行正确的排序。
QuadrantRenderEngine.QUAD_SPLIT_FILTER: 为交叉的平面进行排序,引擎将会自动的分割交叉的平面,然后进行正确的排序。
QuadrantRenderEngine.ALL_FILTERS:这个是上面两种类型的组合。它是默认值(即不传参数时默认是它)
当我们测试代码,会发现出现的情况不令人满意。原因是我们用的模型是由少量的平面组成,这就限制了四叉树渲染引擎(Quadtree rendering)的正常发挥。
该问题得原因是因为每一个平面都被四叉树渲染引擎(Quadtree rendering)渲染了,但是并不是每个平面都需要用四叉树渲染引擎(Quadtree rendering)进行渲染,我们可以不让3D对象不被四叉树渲染引擎(Quadtree rendering)进行渲染,因而该3D对象将会被默认的BasicRenderEngine引擎进行渲染。 我们可以用设置3D对象的testQuad属性为false来实现。
我们例子的问题在于floor的深度排序问题。因而我们可以用四叉树渲染引擎(Quadtree rendering渲染floor对象,而对mill模型用默认的BasicRenderEngine引擎进行渲染。
mill.getChildByName("Blades",true).testQuad = false;
mill.getChildByName("Stand",true).testQuad = false;
mill.getChildByName("House",true).testQuad = false;
因为直接设置mill.testQuad = false;对子对对象不会产生任何影响.因而要像上述那样设置 。
测试代码,排序问题解决 。
总结(Summary)
Z-sorting 是一种给平面排序的方法,它决定平面在viewport显示的顺序(即决定他们被看见的先后顺序)。他是按painter's algorithm.算法实现的。即先创建离camera最远的平面,然后在创建较远的(由远到近的顺序创建平面显示在scene上)。
有几种方法可以改变3D对象的排列顺序:
我们知道viewport层(ViewportLayer)和phostoshop里层的概念相似。PV3D里的层可以按下面的三种方法进行排序:
ViewportLayerSortMode.Z_SORT
ViewportLayerSortMode.ORIGIN_SORT
ViewportLayerSortMode.INDEX_SORT
在mill模型和flloor 的例子里我们用ViewportLayerSortMode.INDEX_SORT方法,通过改变它们距camera的距离来解决排序问题。但是这种方法并不能解决所有的排序问题。
除了给viewport里的viewport层(ViewportLayer)排序外,我们也可以按上述方法给嵌套在viewport层(Viewportlayer)里的其他子viewport层(ViewportLayer)进行排序 。
在本章的最后,我们讲解了一下四叉树渲染引擎(Quadtree rendering),是解决深度排序问题(Z-sorting)较为先进的一种的算法。我们已经知道怎么样给交叉的和不交叉的平面进行排序。即对不交叉的平面用QuadrantRenderEngine.CORRECT_Z_FILTER方法,对交叉的用QuadrantRenderEngine.QUAD_SPLIT_FILTER,大多数情况是你需要结合这两种方法即用QuadrantRenderEngine.ALL_FILTERS方法,它是默认的 。
当然好的算法是要付出代价的,因为四叉树渲染引擎(Quadtree rendering)非常耗CPU.
当有大量的层或者很多的渲染工作需要处理这是可以用四叉树渲染引擎(Quadtree rendering)
将对象添加到viewport层(ViewportLayer)也能够解决深度排序问题(Z-sorting)
http://www.webdesign-cn.com
http://www.levs.cn