Version: 2019.2
稀疏纹理
异步纹理上传

CullingGroup API

CullingGroup 提供一种将系统集成到 Unity 剔除和 LOD 管线中的方法。这可用于许多目的;例如:

  • 模拟一群人,同时只为现在实际可见的角色提供完整的游戏对象
  • 构建由 Graphics.DrawProcedural 驱动的 GPU 粒子系统,但是跳过对墙背后的粒子系统的渲染
  • 跟踪不在摄像机视野范围内的生成点,以便在生成敌人时不让玩家看到他们“弹入”视图
  • 将角色从近处的全质量动画和 AI 计算切换到远处更低质量、更低成本的行为
  • 在场景中设置 10,000 个标记点,并在玩家进入其中任何标记点的 1m 范围内时有效发现这一状态

API 的工作原理是让您提供一系列包围球体。然后计算这些球体相对于特定摄像机的可见性,以及可视为 LOD 级别号的“距离带”值。

CullingGroup 入门

没有用于处理 CullingGroup 的组件或可视化工具;只能通过脚本访问它们。

可使用“new”运算符来构造 CullingGroup:

CullingGroup group = new CullingGroup();

要让 CullingGroup 执行可见性计算,请指定其应使用的摄像机:

group.targetCamera = Camera.main;

使用球体的位置和半径来创建并填充 BoundingSphere 结构数组,并将其与实际位于数组中的球体数一起传递给 SetBoundingSpheres。球体数量无需与数组的长度相同;事实上,我们建议创建一个足够大的数组来保存您一次拥有的最多球体(即使您最初在数组中实际拥有的球体数量非常少)。这样就可以避免在添加或删除球体时调整数组大小,这类操作的成本很高。

BoundingSphere[] spheres = new BoundingSphere[1000];
spheres[0] = new BoundingSphere(Vector3.zero, 1f);
group.SetBoundingSpheres(spheres);
group.SetBoundingSphereCount(1);

此时,CullingGroup 将开始计算每帧单个球体的可见性。

要清理 CullingGroup 并释放它使用的所有内存,请通过标准的 .NET IDisposable 机制来处置 CullingGroup:

group.Dispose();
group = null;

通过 onStateChanged 回调来接收结果

为响应球体而更改其可见性或距离状态的最有效方法是使用 onStateChanged 回调字段。将其设置为一个函数,该函数以 CullingGroupEvent 结构作为参数;对于已改变状态的每个球体,将在剔除完成后调用此函数。CullingGroupEvent 结构的成员会告诉您球体的先前状态和新状态。

group.onStateChanged = StateChangedMethod;

private void StateChangedMethod(CullingGroupEvent evt)
{
    if(evt.hasBecomeVisible)
        Debug.LogFormat("Sphere {0} has become visible!", evt.index);
    if(evt.hasBecomeInvisible)
        Debug.LogFormat("Sphere {0} has become invisible!", evt.index);
}

通过 CullingGroup 查询 API 来接收结果

除了 onStateChanged 委托之外,CullingGroup 还提供一个 API,用于检索包围球体数组中任何球体的最新可见性和距离结果。要检查单个球体的状态,请使用 IsVisible 和 GetDistance 方法:

bool sphereIsVisible = group.IsVisible(0);
int sphereDistanceBand = group.GetDistance(0);

要检查多个球体的状态,可使用 QueryIndices 方法。此方法将扫描连续范围的球体以查找与指定可见性或距离状态相匹配的球体。

// 分配一个数组来保存生成的球体索引 - 数组的大小决定每次调用检查的最大球体数
int[] resultIndices = new int[1000];
// 还要设置一个 int 来存储已放入数组的实际结果数
int numResults = 0;

// 查找所有可见的球体
numResults = group.QueryIndices(true, resultIndices, 0);
// 查找位于距离带 1 中的所有球体
numResults = group.QueryIndices(1, resultIndices, 0);
// 查找隐藏在距离带 2 中的所有球体,跳过前 100 个球体
numResults = group.QueryIndices(false, 2, resultIndices, 100);

请记住,仅在 CullingGroup 使用的摄像机实际执行剔除时,才更新查询 API 检索的信息。

CullingGroup API 最佳实践

在考虑如何将 CullingGroup 应用于项目时,请考虑 CullingGroup 设计的以下方面。

利用可见性

CullingGroup 为其计算可见性的所有体积都由包围球体定义;实际上,由位置(球体中心)和半径值定义。出于性能原因,不支持其他包围形状。在实践中,这意味着您将定义一个球体来完全包围希望剔除的对象。如果需要更紧密拟合,请考虑使用多个球体来覆盖对象的不同部分,并根据所有球体的可见性状态做出决定。

为了评估可见性,CullingGroup 需要知道应该从哪个摄像机可见性开始计算。目前,单个 CullingGroup 仅支持单个摄像机。如果需要评估多个摄像机的可见性,应为每个摄像机使用一个 CullingGroup 并合并结果。

CullingGroup 将仅基于视锥体剔除和静态遮挡剔除来计算可见性。它不会将动态对象视为潜在遮挡物。

利用距离

CullingGroup 能够计算某个参考点(例如,摄像机或玩家的位置)与每个球体上最近点之间的距离。此距离值不会直接提供给您,而是使用您提供的一组阈值来量化,以便计算离散的“距离带”整数结果。目的是将这些距离带解读为“近距离”、“中距离”、“远距离”等。

一个对象从一个区域移到另一个区域时,CullingGroup 将提供回调,让您有机会进行某些操作,例如将该对象的行为更改为 CPU 使用强度较低的操作。

超出最后一个距离的任何球体都将被视为不可见,这使您可以轻松构建一个剔除实现来完全停用非常远的对象。如果不想要此行为,只需将最终阈值设置为无限远的距离。

每个 CullingGroup 仅支持一个参考点。

性能和设计

CullingGroup API 不允许您对场景进行更改后立即请求包围球体的新可见性状态。出于性能原因,CullingGroup 仅在执行整个摄像机剔除期间计算新的可见性信息;此时,您可以通过回调或 CullingGroup 查询 API 来获取信息。实际上,这意味着您应该以异步方式处理 CullingGroup。

提供给 CullingGroup 的包围球体数组将由 CullingGroup 引用,而不是复制。这意味着您应该保留对传递给 SetBoundingSpheres 的数组的引用,并可修改此数组的内容,而无需再次调用 SetBoundingSpheres。如果需要多个 CullingGroup 来计算同一组球体的可见性和距离(例如,对于多个摄像机),那么让所有 CullingGroup 共享相同的包围球体数组实例会很高效。

稀疏纹理
异步纹理上传