Unity 八叉树场景管理
在网友四叉树场景管理的基础上,升级到支持八叉树,感谢网游的开源精神,下面我贴上修改后的代码
八叉树场景管理
- 八叉树是一种树形数据结构,用于在三维空间中划分和管理场景。
- 每个八叉树节点表示一个空间范围,通常是一个立方体或长方体。
- 八叉树将空间递归地划分为八个子节点,直到达到最大深度或其他停止条件。
- 插入对象时,根据对象的位置和大小将其分配到适当的八叉树节点中。
- 八叉树常用于碰撞检测、视锥体剔除和场景渲染等应用。
- 通过遍历八叉树,可以高效地查找和处理与特定空间范围相关的对象。
Unity 中使用八叉树进行场景管理通常用于优化游戏的性能。八叉树是一种树状数据结构,可以将三维空间分割成八个等分的立方体,每个立方体可以继续进行递归分割,直到达到某种条件为止。在游戏中,我们可以使用八叉树来管理场景中的物体,以便更高效地进行碰撞检测、视图裁剪和其他空间相关的操作。八叉树常用于场景管理的性能优化。还需要配合如批处理、静态/动态合并、LOD等技术来提高游戏的性能。
using UnityEngine;
public class ObjData//数据集合
{
public string uid;//id标识
public GameObject prefab;//预制体
public Vector3 pos;//坐标点
public Vector3 ang;//欧拉角度
public ObjData(GameObject prefab, Vector3 pos,Vector3 ang)
{
this.uid = System.Guid.NewGuid().ToString();
this.prefab = prefab;
this.pos = pos;
this.ang = ang;
}
}
using System.Collections.Generic;
using UnityEngine;
public class OctreeNode
{
public Bounds bound; // 包围盒
public int myDepth; // 当前层数
public Octree tree;
public List<ObjData> datas = new List<ObjData>(); // 集合列表
public OctreeNode[] childs;
// 分割点
public Vector3[] bit = new Vector3[]
{
new Vector3(-1, 1, 1),
new Vector3(1, 1, 1),
new Vector3(-1, -1, 1),
new Vector3(1, -1, 1),
new Vector3(-1, 1, -1),
new Vector3(1, 1, -1),
new Vector3(-1, -1, -1),
new Vector3(1, -1, -1)
};
public OctreeNode(Bounds bound, int myDepth, Octree tree)
{
this.bound = bound;
this.myDepth = myDepth;
this.tree = tree;
}
public void InstanceData(ObjData data)
{
if (myDepth < tree.maxDepth && childs == null) // 判定 小于树的最大深度 子集为空
{
CreatChild(); // 创建 子树
}
if (childs != null) // 判断子节点不为空
{
for (int i = 0; i < childs.Length; i++) // 遍历所有子节点
{
if (childs[i].bound.Contains(data.pos)) // 判断子节点的包围盒 是否在里面
{
childs[i].InstanceData(data); // 循环创建放入下一级
break; // 跳出
}
}
}
else // 判空就放入
{
datas.Add(data); // 放入集合列表中 等待空的包围盒
}
}
private void CreatChild()
{
childs = new OctreeNode[tree.maxChildCount]; // 创建八叉树
for (int i = 0; i < tree.maxChildCount; i++) // 循环遍历
{
// 计算分割点 包围盒中心点位置的点位坐标
Vector3 center = new Vector3(bit[i].x * bound.size.x / 4, bit[i].y * bound.size.y / 4, bit[i].z * bound.size.z / 4);
// 计算盒体尺寸大小 size.x 为盒体的宽度 size.y 为盒体的高度 size.z 为盒体的深度
Vector3 size = new Vector3(bound.size.x / 2, bound.size.y / 2, bound.size.z / 2);
// 创建包围盒 分割点+包围盒中心点 ,size该盒体的总大小
Bounds childbound = new Bounds(center + bound.center, size);
// 创建节点 一个节点对应一个子树
childs[i] = new OctreeNode(childbound, myDepth + 1, tree);
}
}
// 绘制盒
public void DrawBound()
{
if (datas.Count != 0) // 判断有东西 蓝盒
{
Gizmos.color = Color.blue;
Gizmos.DrawWireCube(bound.center, bound.size - Vector3.one * 0.1f);
}
else // 没东西 绿盒
{
Gizmos.color = Color.green;
Gizmos.DrawWireCube(bound.center, bound.size - Vector3.one * 0.1f);
}
if (childs != null) // 如果子节点不为空
{
for (int i = 0; i < childs.Length; i++)
{
childs[i].DrawBound(); // 递归调用
}
}
}
public void TriggerMove(Plane[] planes)//视锥体剔除
{
if (childs != null)//如果子节点不为空
{
for (int i = 0; i < childs.Length; i++)//便利所有子节点对象
{
childs[i].TriggerMove(planes);//递归下一节点计算
}
}
for (int i = 0; i < datas.Count; i++)//便利所有集合列表
{
datas[i].prefab.SetActive(GeometryUtility.TestPlanesAABB(planes, bound));//控制对象预制体开关
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Octree
{
public Bounds bound; // 包围盒
private OctreeNode root; // 根节点
public int maxDepth = 6; // 最大深度层级
public int maxChildCount = 8; // 最大子节点数
public Octree(Bounds bound) // 创建八叉树
{
this.bound = bound;
this.root = new OctreeNode(bound, 0, this); // 创建根节点
}
// 插入数据
public void Insert(ObjData data)
{
root.InstanceData(data); // 放到根节点
}
public void DrawBound()
{
root.DrawBound(); // 根节点调用绘制盒
}
public void TriggerMove(Plane[] planes)
{
root.TriggerMove(planes); // 根节点传递视锥体面
}
}
测试代码
using UnityEngine;
using Random = UnityEngine.Random;
public class CreatCube8 : MonoBehaviour
{
public GameObject cube;//对象
public Bounds mainBound;//包围盒
private Octree tree;//树
private bool startEnd = false;//控制结束
public Camera cam;//相机
private Plane[] _planes;//存储视锥体六个面
void Start()
{
_planes = new Plane[6];//初始化
Bounds bounds = new Bounds(transform.position, new Vector3(200, 200, 200));//生成包围盒
tree = new Octree(bounds);//初始化行为树
for (int x = -100; x < 100; x+=10)//随机生成对象放到树中
{
for (int z = -100; z < 100; z+=10)
{
for (int y = -100; y < 100; y+=10)
{
if (Random.Range(0, 20) < 1)
{
GameObject c = Instantiate(cube, transform);
c.transform.localScale = new Vector3(Random.Range(5, 25), Random.Range(5, 25), Random.Range(5, 25));
c.transform.position = new Vector3(x, y, z);
c.transform.eulerAngles = new Vector3(0, Random.Range(0, 360), 0);
tree.Insert(new ObjData(c, c.transform.position, c.transform.eulerAngles));
}
}
}
}
startEnd = true;
}
void Update()
{
if (startEnd)
{
//获取摄像机视锥体六个面
GeometryUtility.CalculateFrustumPlanes(cam, _planes);
// 更新_planes的数值
tree.TriggerMove(_planes);//传六个面
}
}
private void OnDrawGizmos()
{
if (startEnd)
{
tree.DrawBound();//开始绘制包围盒 用树组绘制盒
}
else
{
//初始化盒体
//使用 center 和 size 绘制一个线框盒体。
//mainBound.center中心节点 盒体大小
Gizmos.DrawWireCube(mainBound.center, mainBound.size);
}
}
}