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 阅读次数:6969
