35.剔除系统

什么是剔除,我认为简单地说:就是不绘制不需要的对象。那么哪些对象是不需要的?情况非常复杂。在我的引擎中目前只实现了视锥体剔除(即如果对象位于相机视锥体之外就不绘制这个对象)和距离剔除(即对象位于指定距离之外就不绘制这个对象)。其中视锥体剔除的基本思路来自于2.5 检查对象是否可见,请先看懂这篇文章。由这篇文章可知,要实现视锥体剔除,需要的信息有相机视锥体的包围盒BoundingFrustum和对象的包围球,对象的包围球信息保存在了它的pose属性中了,因此只需要创建一个新的类CullingMethod.cs保存相机视锥体包围盒的信息,代码如下:

namespace StunEngine.Rendering
{
    /// 
    /// 定义使用何种方法进行剔除。
    /// 
    public enum CullingMethod
    {
        /// 
        /// 不进行剔除计算。
        /// 
        NoCulling,

        /// 
        /// 视锥体剔除,所有在视锥体之外的对象将被剔除。
        /// 
        Frustum,

        /// 
        /// 这是默认的方法。类似于剔除,只不过这种剔除中,真实远裁平面()被设置为一个很大的数,可以绘制非常远的对象。
        /// 如果对象在视锥体之外或距离大于FarPlane则被剔除。通常如地形之类的对象不会被剔除,以防止当相机移动时,地形的几何体突然出现在视野中。
        /// 
        FarPlaneFrustum,

        /// 
        /// 场景对象由四叉树进行剔除,还未实现。
        /// 
        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, List activeNodeList, 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, List activeNodeList, 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

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号