Unity——八叉树的原理与实现

八叉树原理

八叉树(Octree)是一种用于在三维空间中进行空间分割的数据结构。它将三维空间递归地划分为八个子空间,每个子空间对应于一个八叉树节点。这种分割方式可以有效地组织和管理场景中的对象,提高检索效率,特别是在进行空间查询时。

以下是八叉树的基本原理:

  1. 空间划分:

    • 初始状态:整个三维空间被表示为一个根节点,该节点包含所有的对象。
    • 递归划分:根节点被递归地划分为八个子节点,每个子节点对应于父节点的一个八分之一空间。这个过程会一直持续下去,直到达到预定义的停止条件,例如节点包含的对象数量小于某个阈值或达到最小节点大小。
  2. 节点结构:

    • 每个节点包含一个包围盒(Bounding Box)用于表示该节点所包含的空间范围。
    • 节点可能包含零个或多个对象,这些对象是该节点所表示空间范围内的物体。
    • 如果节点包含的对象数量达到某个限制,或者达到最小节点大小,不再继续划分,成为叶子节点。
  3. 添加对象:

    • 当需要向八叉树中添加对象时,从根节点开始递归地查找合适的叶子节点。
    • 将对象添加到叶子节点中。如果该节点包含的对象数量超过限制,可以考虑划分该节点。
  4. 查询操作:

    • 对于空间查询,从根节点开始,检查查询范围与每个节点的包围盒是否相交。
    • 如果相交,进一步检查该节点的子节点。
    • 递归地沿着相交的子节点继续查询,直到达到叶子节点。
    • 叶子节点包含在查询范围内的对象。
  5. 优点:

    • 空间局部性:八叉树有效地利用了物体在三维空间中的局部性,提高了检索效率。
    • 动态场景:适用于动态场景,因为对象的添加和删除只影响八叉树的局部结构。
  6. 应用领域:

    • 游戏开发:用于场景管理、碰撞检测等。
    • 计算机图形学:在光照、阴影等方面的优化中有广泛应用。
    • 计算机辅助设计(CAD):用于加速物体查询。

代码实现

1.创建项目

2.创建三个C#文件,命名为OctreeNode.cs、Octree.cs和CreateOctree.cs。

OctreeNode.cs具体代码

这个类实现了八叉树节点的功能,包括分割、添加游戏对象和绘制节点的包围盒等操作。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class OctreeNode
{
    Bounds nodeBounds; // 节点的包围盒
    float minSize; // 最小节点大小
    Bounds[] childBounds; // 子节点的包围盒数组
    OctreeNode[] children = null; // 子节点数组

    // 构造函数,接受一个包围盒和最小节点大小作为参数
    public OctreeNode(Bounds b, float minNodeSize)
    {
        nodeBounds = b;
        minSize = minNodeSize;

        float quarter = nodeBounds.size.y / 4.0f;
        float childLength = nodeBounds.size.y / 2;
        // 计算子节点的包围盒
        Vector3 childSize = new Vector3(childLength, childLength, childLength);
        childBounds = new Bounds[8];
        // 创建子节点的包围盒
        childBounds[0] = new Bounds(nodeBounds.center + new Vector3(-quarter, quarter, -quarter), childSize);
        childBounds[1] = new Bounds(nodeBounds.center + new Vector3(quarter, quarter, -quarter), childSize);
        childBounds[2] = new Bounds(nodeBounds.center + new Vector3(-quarter, quarter, quarter), childSize);
        childBounds[3] = new Bounds(nodeBounds.center + new Vector3(quarter, quarter, quarter), childSize);
        childBounds[4] = new Bounds(nodeBounds.center + new Vector3(-quarter, -quarter, -quarter), childSize);
        childBounds[5] = new Bounds(nodeBounds.center + new Vector3(quarter, -quarter, -quarter), childSize);
        childBounds[6] = new Bounds(nodeBounds.center + new Vector3(-quarter, -quarter, quarter), childSize);
        childBounds[7] = new Bounds(nodeBounds.center + new Vector3(quarter, -quarter, quarter), childSize);
    }

    // 将游戏对象添加到节点
    public void AddObject(GameObject go)
    {
        DivideAndAdd(go);
    }

    // 分割并添加游戏对象
    public void DivideAndAdd(GameObject go)
    {
        if (nodeBounds.size.y <= minSize)
        {
            return; // 如果节点大小小于等于最小节点大小,停止分割
        }

        if (children == null)
        {
            children = new OctreeNode[8];
        }

        bool dividing = false;

        for (int i = 0; i < 8; i++)
        {
            if (children[i] == null)
            {
                children[i] = new OctreeNode(childBounds[i], minSize);
            }

            // 如果游戏对象的包围盒与子节点的包围盒相交,进行分割
            if (childBounds[i].Intersects(go.GetComponent<Collider>().bounds))
            {
                dividing = true;
                children[i].DivideAndAdd(go);
            }
        }

        // 如果没有进行分割,将子节点数组设为null
        if (dividing == false)
        {
            children = null;
        }
    }

    // 绘制节点的包围盒
    public void Draw()
    {
        Gizmos.color = new Color(0, 1, 0);
        Gizmos.DrawWireCube(nodeBounds.center, nodeBounds.size);

        // 如果子节点不为空,递归绘制子节点
        if (children != null)
        {
            for (int i = 0; i < 8; i++)
            {
                if (children[i] != null)
                {
                    children[i].Draw(); // 递归调用
                }
            }
        }
    }
}

Octree.cs具体代码

这个类实现主要包括构造函数和将游戏对象添加到八叉树中的方法。八叉树的根节点存储在 rootNode 中,用于表示整个八叉树的结构。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Octree
{
    public OctreeNode rootNode; // 八叉树的根节点 

    // 构造函数,接受世界中的游戏对象数组和最小节点大小作为参数
    public Octree(GameObject[] worldObjects, float minNodeSize)
    {
        Bounds bounds = new Bounds(); // 用于计算包围盒的 Bounds 对象

        // 遍历所有游戏对象,计算包围盒以包含它们
        foreach (GameObject go in worldObjects)
        {
            bounds.Encapsulate(go.GetComponent<Collider>().bounds);
        }

        // 计算包围盒的最大边长
        float maxSize = Mathf.Max(new float[] { bounds.size.x, bounds.size.y, bounds.size.z });
        Vector3 sizeVector = new Vector3(maxSize, maxSize, maxSize) * 0.5f;

        // 将包围盒的最小和最大点调整为形成一个正方体
        bounds.SetMinMax(bounds.center - sizeVector, bounds.center + sizeVector);

        // 创建八叉树的根节点,传入包围盒和最小节点大小
        rootNode = new OctreeNode(bounds, minNodeSize);

        // 将世界中的游戏对象添加到八叉树中
        AddObjects(worldObjects);
    }

    // 将游戏对象添加到八叉树中
    public void AddObjects(GameObject[] worldObjects)
    {
        foreach (GameObject go in worldObjects)
        {
            rootNode.AddObject(go);
        }
    }
}

CreateOctree.cs具体代码

这个类主要用于在启动时创建八叉树对象,并在运行时通过 OnDrawGizmos 方法绘制八叉树的根节点的包围盒。在 Start 方法中,创建了一个 Octree 对象(otree),并通过传入的世界游戏对象数组和最小节点大小进行初始化。在 OnDrawGizmos 方法中,如果应用程序正在运行,则调用八叉树的根节点的 Draw 方法来绘制八叉树的结构。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CreateOctree : MonoBehaviour
{
    public GameObject[] worldObjects; // 存储世界中的游戏对象数组
    public int nodeMinsize = 5; // 八叉树的最小节点大小
    Octree otree; // 八叉树对象

    // 在启动时调用,用于初始化
    void Start()
    {
        otree = new Octree(worldObjects, nodeMinsize); // 创建八叉树对象并初始化
    }

    // 在每一帧更新时调用
    void OnDrawGizmos()
    {
        if (Application.isPlaying)
        {
            otree.rootNode.Draw(); // 在运行时绘制八叉树的根节点的包围盒
        }
    }
}

3.创建一个空物体(Create empty),命名为Octree,添加CreateOctree组件。

4.创建对象,调整位置和缩放比例等。(本实验创建了4个立方体,可根据情况,自由创建其他对象,注:对象一定要有碰撞器 Collier,不如会报错:NullReferenceException: Object reference not set to an instance of an object)

5.把对象添加到 world Objects中。

6.运行项目,效果如下:

参考链接

最浅显易懂的 Unity 八叉树场景管理(保姆级)-CSDN博客

Unity 八叉树空间分割的简单实现,part1, Dividing 3D Space into an Octree_哔哩哔哩_bilibili

Unity 八叉树空间分割的简单实现,part2,Dividing 3D Space into an Octree_哔哩哔哩_bilibili

Unity 八叉树空间分割的简单实现,part3,Dividing 3D Space into an Octree_哔哩哔哩_bilibili

### Unity八叉树实现应用 #### 八叉树的基本概念 八叉树是一种用于三维空间划分的数据结构,通过递归地将空间划分为八个子区域来表示复杂的空间分布。这种数据结构常被用来优化碰撞检测、可见性计算以及大规模场景管理等问题。 在 Unity 的开发环境中,八叉树可以显著提升性能效率,尤其是在处理大量游戏对象时。其核心思想是利用边界框(Bounds 或 AABB)定义一个初始的大立方体区域,并逐步将其细分成更小的子立方体,直到达到预设条件为止[^1]。 #### 统一初始化过程 为了构建有效的八叉树,在 Unity 场景中通常会先创建一个覆盖整个目标范围的最大 Bounds 区域作为根节点。随后按照一定规则对该区域进行细分操作: - **切割逻辑**:每次都将当前父级节点对应的立方体均匀切分成为边长相等的八个部分; - **停止准则**:当某个特定条件下不再继续分裂时结束递归流程,比如最大层数限制或者最小物体数量阈值达成等情况; 这些原则有助于控制最终形成的层次深度并减少不必要的资源消耗[^2]。 #### 应用实例分析 ##### 1. 碰撞检测优化 在一个密集填充着多个刚体模型的游戏世界里,如果采用传统的逐一对比方法来进行物理交互判断,则随着实体数目增加而呈指数增长的时间成本将会变得难以承受。然而借助于预先建立起来的八叉索引体系之后,我们只需要关注那些真正可能相互影响的小范围内的成员即可完成快速筛选工作。 ```csharp // 假设有两个物体需要测试是否发生接触事件 if (octreeNode.IsIntersecting(objectA, objectB)) { Debug.Log("Collision detected!"); } ``` 上述伪代码片段展示了如何调用已有的八叉树节点函数去高效评估两者之间是否存在潜在冲突关系。 ##### 2. 可见性裁剪技术 对于远距离视锥外的对象不必渲染绘制出来这一常识性的做法同样适用于基于八叉组织形式下的隐藏面消除策略之中。通过对摄像机视角方向上的各级分支逐一排查剔除掉不可达的部分从而降低GPU负载压力。 ```csharp foreach(var node in visibleNodes){ if(!Camera.main.FrustumCull(node.Bound)){ continue; } RenderObjectsInNode(node); } ``` 这里列举了一个循环迭代所有可视范围内符合条件的叶子结点集合的例子,并且只针对其中未超出屏幕界限之外的内容安排进一步的具体表现动作。 #### 总结说明 综上所述,Unity 平台下运用八叉树算法不仅可以极大地改善大型项目的运行流畅度而且还能简化许多复杂的几何运算任务。不过值得注意的是实际项目部署过程中还需要综合考虑诸如内存占用率等因素的影响以便做出合理取舍调整参数配置获得最佳效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值