35.剔除系统
什么是剔除,我认为简单地说:就是不绘制不需要的对象。那么哪些对象是不需要的?情况非常复杂。在我的引擎中目前只实现了视锥体剔除(即如果对象位于相机视锥体之外就不绘制这个对象)和距离剔除(即对象位于指定距离之外就不绘制这个对象)。其中视锥体剔除的基本思路来自于2.5 检查对象是否可见,请先看懂这篇文章。由这篇文章可知,要实现视锥体剔除,需要的信息有相机视锥体的包围盒BoundingFrustum和对象的包围球,对象的包围球信息保存在了它的pose属性中了,因此只需要创建一个新的类CullingMethod.cs保存相机视锥体包围盒的信息,代码如下:
namespace StunEngine.Rendering { ////// 定义使用何种方法进行剔除。 /// public enum CullingMethod { ////// 不进行剔除计算。 /// NoCulling, ////// 视锥体剔除,所有在视锥体之外的对象将被剔除。 /// Frustum, ////// 这是默认的方法。类似于 FarPlaneFrustum, ///剔除,只不过这种剔除中,真实远裁平面( )被设置为一个很大的数,可以绘制非常远的对象。 /// 如果对象在视锥体之外或距离大于FarPlane则被剔除。通常如地形之类的对象不会被剔除,以防止当相机移动时,地形的几何体突然出现在视野中。 /// /// 场景对象由四叉树进行剔除,还未实现。 /// QuadTree, } ////// 几何体剔除的数据容器。 /// public class CullingInfo { ////// 创建一个新CullingInfo对象。 /// public CullingInfo() { this.CullingMethod = CullingMethod.NoCulling; this.FarPlane = 0; this.CameraPosition = Vector3.Zero; } ////// 创建一个新CullingInfo对象。 /// /// 剔除方法 /// 相机的真实视锥体,远裁平面距离很大 /// 相机的逻辑视锥体,这个值在创建相机时由用户指定 /// 当前相机位置 public CullingInfo(CullingMethod method, BoundingFrustum frustum, float farPlane, ref Vector3 cameraPosition) { this.CullingMethod = method; this.BoundingFrustum = frustum; this.FarPlane = farPlane; this.CameraPosition = cameraPosition; } ////// 当前RenderingPass使用的剔除方法。 /// public CullingMethod CullingMethod; ////// 相机的包围视锥体。它是真实包围视锥体,比逻辑视锥体大10倍。 /// public BoundingFrustum BoundingFrustum; ////// 相机的逻辑远裁平面。用来基于它进行基于距离的剔除,有些节点虽然在视锥体中,但距离太远,也应该将它剔除提高性能 /// public float FarPlane; ////// 相机的位置。 /// public Vector3 CameraPosition; } }
在这个类中还包含了一个剔除方法的枚举,解释请看注释。在CullingInfo类中除了包含BoundingFrustum信息,还包含了相机位置CameraPosition、逻辑远裁平面距离FarPlane的信息,这两个信息是用于基于距离的剔除的,将在后面讲到。这个类的数据是在Scene类中的Update()方法中使用sceneCamera.GetCulling(cullInfo)获取的。 具体的剔除操作位于新的类CullManager中,代码如下:
namespace StunEngine.SceneManagement { ////// 处理的节点的剔除。 /// 剔除包含两个过程:更新过程中的剔除和绘制过程中的剔除。 /// internal static class CullManager { ////// 创建一个需要更新的节点集合,在Update过程中被调用。 /// 此过程更新FrameBits中的UpdatePhaseFrustumChecked和UpdatePhaseFrustumDisjoint,在Draw过程中的剔除依赖于这些值。 /// /// 经过剔除的节点索引集合 /// 要更新的节点集合 /// 剔除信息 /// 当前节点的统计信息 public static void UpdatePhaseCulling(IndexList updateList, ListactiveNodeList, CullingInfo cullInfo, ref RenderStatistics currentStat) { //将updateList归零 updateList.Reset(); //遍历activeNodeList集合 for (int i = 0; i < activeNodeList.Count; i++) { // UI节点不需要进行剔除 if (activeNodeList[i] is UISceneNode) continue; Renderable3DSceneNode node = activeNodeList[i] as Renderable3DSceneNode; //--------------------------------------------------------------------- // 判断当前节点是否需要进行剔除?? // 要剔除的节点的Enabled位必须设置为1而DisableUpdateCulling位设置为0!!! //--------------------------------------------------------------------- if (((byte)node.frameBits & ((byte)StatusBits.Enabled | (byte)StatusBits.DisableUpdateCulling)) == (byte)StatusBits.Enabled) { currentStat.nodesEnabled++; //------------------------------------ // 如果需要进行剔除 // 则将UpdatePhaseFrustumChecked位设置为1 //------------------------------------ node.frameBits |= StatusBits.UpdatePhaseFrustumChecked; // 现在进行剔除检测 // 如果节点在视锥体内,则将节点的UpdatePhaseFrustumDisjoint位设置为0,并将此节点添加到updateList集合中 if (cullInfo.BoundingFrustum.Contains(node.pose.aaBB) != ContainmentType.Disjoint) { node.frameBits &= ~StatusBits.UpdatePhaseFrustumDisjoint; updateList.AddIndex(i); } //如果节点不在视锥体内,则将UpdatePhaseFrustumDisjoint位设置为1 else { node.frameBits |= StatusBits.UpdatePhaseFrustumDisjoint; } } else { //--------------------------------------- // 如果不需要进行剔除 // 则将UpdatePhaseFrustumChecked位设置为0 // 将UpdatePhaseFrustumDisjoint设置为0 //--------------------------------------- node.frameBits &= ~( StatusBits.UpdatePhaseFrustumChecked | StatusBits.UpdatePhaseFrustumDisjoint); if (((byte)node.frameBits & (byte)StatusBits.Enabled) == (byte)StatusBits.Enabled) { updateList.AddIndex(i); currentStat.nodesEnabled++; } } } } /// /// 创建一个需要被绘制的节点集合。在Draw过程中被调用。 /// /// 经过剔除的节点索引集合 /// 要更新的节点集合 /// 剔除信息 /// 当前节点统计信息 public static void DrawPhaseCulling(IndexList renderList, ListactiveNodeList, CullingInfo cullInfo, ref RenderStatistics currentStat) { //将renderList归零 renderList.Reset(); //遍历activeNodeList生成要绘制的节点集合 for (int i = 0; i < activeNodeList.Count; i++) { // UI节点不需要进行剔除 if (activeNodeList[i] is UISceneNode) continue; Renderable3DSceneNode node = activeNodeList[i] as Renderable3DSceneNode; if (node == null) continue; Renderable3DSceneNode snode = (Renderable3DSceneNode)node; // 如果节点位标志中的Visible设为1,即节点可见 if ((snode.frameBits & StatusBits.Visible) == StatusBits.Visible) { // 则更新相应的统计信息 currentStat.nodesVisible++; //-------------------------------------------------------------- // 如果关闭了剔除操作或这个节点不需要剔除 //-------------------------------------------------------------- if (cullInfo.CullingMethod == CullingMethod.NoCulling || (snode.frameBits & StatusBits.DisableCulling) == StatusBits.DisableCulling) { // 则将DrawPhaseCulled位设置为0 snode.frameBits &= ~StatusBits.DrawPhaseCulled; currentStat.nodesCullingDisabled++; renderList.AddIndex(i); currentStat.nodesRendered++; continue; } //-------------------------------------------------------------- // 检查Update过程是否计算了剔除 //-------------------------------------------------------------- if (((byte)snode.frameBits & (byte)StatusBits.UpdatePhaseFrustumChecked) == (byte)StatusBits.UpdatePhaseFrustumChecked) { //-------------------------------------------------------------- // 检查Update过程中是否已经将视锥体之外的节点剔除 //-------------------------------------------------------------- if (((byte)snode.frameBits & (byte)StatusBits.UpdatePhaseFrustumDisjoint) == (byte)StatusBits.UpdatePhaseFrustumDisjoint) { // 如果是,则将DrawPhaseCulled位设置为1 snode.frameBits |= StatusBits.DrawPhaseCulled; currentStat.nodesBoundingBoxCulled++; continue; } } else { //-------------------------------------------------------------- // 如果不是,则我们必须计算视锥体剔除 //-------------------------------------------------------------- if (cullInfo.BoundingFrustum.Contains(node.pose.aaBB) == ContainmentType.Disjoint) { // 将DrawPhaseCulled位设置为1 snode.frameBits |= StatusBits.DrawPhaseCulled; currentStat.nodesBoundingBoxCulled++; continue; } } //-------------------------------------------------------------- // 如果节点在真实相机视锥体中! // 检查它是否在远裁平面之内。 //-------------------------------------------------------------- if (cullInfo.CullingMethod == CullingMethod.FarPlaneFrustum) { //计算节点离开相机的距离 float distance = (node.Pose.Position - cullInfo.CameraPosition).Length() - node.pose.Radius; //如果此距离大于远裁平面乘以因子,说明节点在远裁平面之外需要剔除 if (distance > (cullInfo.FarPlane * node.FarPlaneCullingFactor)) { // 将DrawPhaseCulled设置为1 snode.frameBits |= StatusBits.DrawPhaseCulled; currentStat.nodesDistanceCulled++; continue; } } // 如果不属于前面的情况,则直接将节点添加到集合中 renderList.AddIndex(i); currentStat.nodesRendered++; // 将DrawPhaseCulled设置为0 snode.frameBits &= ~StatusBits.DrawPhaseCulled; } } } } }
剔除过程包含更新过程中的剔除和绘制过程中的剔除,分别在Scene类中的Update()和Draw()方法中调用,基于距离的剔除位于DrawPhaseCulling方法中,原理很简单,就是计算对象离开相机的距离,然后与cullInfo.FarPlane * node.FarPlaneCullingFactor作比较,如果大于说明对象距离太远没必要绘制,其中FarPlane叫做逻辑远裁平面,默认为1000,FarPlaneCullingFactor是一个因子默认为1,而相机的真实远裁平面默认是逻辑远裁平面的10倍即10000,远于10000时XNA会自动进行剔除,但在真实情况中这个距离还是太大,所以需要定义一个逻辑远裁平面,如果因子为1,则对象离开相机的距离超过1000时就会被剔除,你也可以改变FarPlaneCullingFactor的数值调整这个距离。为了方便地设置节点的剔除状态,使用了一个叫做frameBits的位标志,它的定义在StatusBits.cs中,代码如下:
namespace StunEngine.SceneManagement { ////// 为可绘制的节点定义的状态位标志 /// [System.Flags] public enum StatusBits : byte { ////// 如果设置为1,则节点是Enabled,在Update过程中会计算剔除。 /// Enabled = 1, ////// 如果设置为1,则在Update过程中不会剔除这个节点。只有Enabled设置为1时这个位才起作用。 /// DisableUpdateCulling = 2, ////// 如果在Update过程中需要对节点进行BoundingFrustum检测,则将这个位设置为1。 /// 如果一个节点是Enabled但它的DisableCulling属性为true,那么SCM就不会在更新过程中剔除它。 /// 如果这个位设置为0,那么在SCM的Draw过程中就需要检测视锥体是否包含这个节点。 /// UpdatePhaseFrustumChecked = 4, ////// 只有在UpdatePhaseFrustumChecked设置为1的情况下才会用到这个位。如果设置为1,则完全在Update过程中对这个节点进行剔除操作, /// 如果这个节点在相机视锥体之外,则它不可见。如果这个位设置为1,则Draw过程会略过对此节点的剔除操作。 /// UpdatePhaseFrustumDisjoint = 8, ////// 如果设置为0则节点不可见也不计算剔除。如果设置为1,则这个节点是可见的,在Draw过程中会根据其他允许剔除的位对这个节点进行剔除 /// Visible = 16, ////// 如果设置为1,则在Draw过程中不剔除这个节点,它会被添加到renderlist中 /// DisableCulling = 32, ////// 只有在Visible设置为1时才在Draw中被设置。如果设置为1,则当前节点If set to 1 the node is culled and wannt be rendered. /// DrawPhaseCulled = 64, } }
要理解这个枚举的作用,强烈建议先阅读XNA中的位标志。
有了剔除系统,作用是非常明显的,为了能看到实际效果,我还定义了一个统计信息的结构RenderStatistics可以显示剔除的信息和帧频等,比较简单就不写代码了,请看源程序。
当然要制作一个好的剔除系统不容易,以后再实现四叉树,八叉树、BSP树等方法吧。
文件下载(已下载 1640 次)发布时间:2010/5/10 下午3:57:52 阅读次数:6431