Unity - 撸一个简单版本的 四叉树 + 视锥cascaded,用于场景剔除


环境

Unity : 2019.4.0f1


这个内容时前端时间写的


整体效果

  • 蓝色空心矩形 就是被 剔除的 cube 集合
  • 红色实心矩形 就是在四叉树 + 视锥cascaded 内的 cube 集合

在这里插入图片描述


更加精准

在这里插入图片描述

在这里插入图片描述


Code


QuadTree.cs

#define __ENABLE_COMPLETE_CONTAINS_BRANCH_HANDLE__  // 启用完全包含 枝干 优化的处理
#define __ENABLE_QT_PROFILER__                      // 启用 Profiler

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

#if __ENABLE_QT_PROFILER__
using UnityEngine.Profiling;
#endif

// jave.lin : 下面的 ListPool<T> 类可以单独放到另一个通用的工具类下管理
// 便于外部所有地方可以使用,但是如果这么做的话,最好声明为 static 静态类

/// <summary>
/// date    : 2020/11/11
/// author  : jave.lin
/// 外部大量的 new List<T> 也是导致大量 GC.Collect 频繁触发的原因
/// 可以使用 ListPool<T>.FromPool, ToPool 来专门替代外部的 new List<T> 的临时变量
/// 可大大降低:GC.Collect 的触发周期
/// List<T> 的对象池管理,专用于 C# 层的处理,因为 lua 做不了 C# 编译时决定的泛型
/// </summary>
/// <typeparam name="T">元素类型</typeparam>
public class ListPool<T> : IDisposable
{
    private Stack<List<T>> _list_pool = new Stack<List<T>>();
    public List<T> FromPool()
    {
        return _list_pool.Count > 0 ? _list_pool.Pop() : new List<T>();
    }

    public void ToPool(List<T> list)
    {
        list.Clear();
        _list_pool.Push(list);
    }

    public void Clear()
    {
        _list_pool.Clear();
    }

    public void Dispose()
    {
        if (_list_pool != null)
        {
            _list_pool.Clear();
            _list_pool = null;
        }
    }
}

[Serializable]
/// <summary>
/// QuadTree 使用的 AABB(后续完善其实可以尝试支持 OOB)
/// date    : 2021/02/18
/// author  : jave.lin
/// </summary>
public struct QTAABB : IEquatable<QTAABB>
{
    public static readonly QTAABB Zero = new QTAABB();

    /// <summary>
    /// 获取 cam frustum 的 aabb 的垂直方向的 分层级别,默认是 3 个级别
    /// </summary>
    public const int DEFAULT_GET_CAM_FRUSTUM_TO_AABB_LEVEL = 3;
    /// <summary>
    /// 获取 cam frustum 的 aabb 的水平方向的 padding,默认是 unity 的 0 个 units 的大小
    /// </summary>
    public const float DEFAULT_GET_CAM_FRUSTUM_AABB_H_PADDING = 0;

    public float x;
    public float y;
    public float w;
    public float h;
    
    public float left       { get => x; set => x = value; }
    public float top        { get => y; set => y = value; }
    public float right      { get => x + w; set => w = value - x; }
    public float bottom     { get => y + h; set => h = value - y; }

    public float centerX    { get => x + w * 0.5f; set => x = value - (w * 0.5f); }
    public float centerY    { get => y + h * 0.5f; set=> y = value - (h * 0.5f); }
    public Vector2 center   { get => new Vector2(centerX, centerY); set { centerX = value.x; centerY = value.y; } }

    public float extentX    { get => w * 0.5f; set => w = value * 2; }
    public float extentY    { get => y * 0.5f; set => h = value * 2; }
    public Vector2 extent   { get => new Vector2(extentX, extentY); set { extentX = value.x; extentY = value.y; } }

    public Vector2 min { get => new Vector2(left, top); set { left = value.x; top = value.y; } }
    public Vector2 max { get => new Vector2(right, bottom); set { right = value.x; bottom = value.y; } }

    public Vector2 top_left { get => new Vector2(left, top); }
    public Vector2 top_right { get => new Vector2(right, top); }
    public Vector2 bottom_left { get => new Vector2(left, bottom); }
    public Vector2 bottom_right { get => new Vector2(right, bottom); }

    public bool IsZero()
    {
        return left == right || top == bottom;
    }
    public void Set(float x, float y, float w, float h)
    {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }
    /// <summary>
    /// 当前 AABB 与 other 的 AABB 是否有交集,并返回交集的 AABB
    /// </summary>
    /// <param name="other">其他的 AABB</param>
    /// <param name="outAABB">返回交集的 AABB</param>
    /// <returns>如果当前 AABB 与 other 的 AABB 是否有交集,则返回 true</returns>
    public bool IsIntersect(ref QTAABB other, out QTAABB outAABB)
    {
        outAABB         = new QTAABB();
        outAABB.x       = Mathf.Max(left, other.left);
        outAABB.right   = Mathf.Min(right, other.right);
        outAABB.y       = Mathf.Max(top, other.top);
        outAABB.bottom  = Mathf.Min(bottom, other.bottom);
        return !outAABB.IsZero();
    }
    /// <summary>
    /// 当前 AABB 与 other 的 AABB 是否有交集
    /// </summary>
    /// <param name="other">其他的 AABB</param>
    /// <returns>如果当前 AABB 与 other 的 AABB 是否有交集,则返回 true</returns>
    public bool IsIntersect(ref QTAABB other)
    {
        return x < other.right && y < other.bottom && right > other.x && bottom > other.y;
    }
    public bool IsIntersect(QTAABB other)
    {
        return IsIntersect(ref other);
    }
    /// <summary>
    /// 是否完整包含另一个 AABB(做优化用,一般如果整个 AABB 都被另一个 AABB 包含就不用精确检测了)
    /// </summary>
    /// <param name="other">另一个 AABB</param>
    /// <returns>如果完整包含另一个 AABB,则返回 true</returns>
    public bool Contains(ref QTAABB other)
    {
        return other.x >= x && other.y >= y && other.right <= right && other.bottom <= bottom;
    }
    public bool Contains(QTAABB other)
    {
        return Contains(ref other);
    }
    /// <summary>
    /// 是否包含一个 2D 点
    /// </summary>
    /// <param name="x">2D 点 x</param>
    /// <param name="y">2D 点 x</param>
    /// <returns>如果包含 2D 点,则返回 true</returns>
    public bool Contains(float x, float y)
    {
        return x >= left && x < right && y >= top && y < bottom;
    }
    /// <summary>
    /// 是否包含一个 2D 点
    /// </summary>
    /// <param name="pos">2D 点</param>
    /// <returns>如果包含 2D 点,则返回 true</returns>
    public bool Contains(Vector2 pos)
    {
        return Contains(ref pos);
    }
    public bool Contains(ref Vector2 pos)
    {
        return Contains(pos.x, pos.y);
    }
    public void Union(QTAABB aabb)
    {
        Union(ref aabb);
    }
    /// <summary>
    /// 并集一个 AABB
    /// </summary>
    /// <param name="aabb">需要与之并集的 AABB</param>
    public void Union(ref QTAABB aabb)
    {
        Union(aabb.min);
        Union(aabb.max);
    }
    /// <summary>
    /// 并集一个 点
    /// </summary>
    /// <param name="pos"></param>
    public void Union(Vector2 pos)
    {
        Union(ref pos);
    }
    public void Union(ref Vector2 pos)
    {
        Union(pos.x, pos.y);
    }
    public void Union(Vector3 pos)
    {
        Union(ref pos);
    }
    public void Union(ref Vector3 pos)
    {
        Union(pos.x, pos.z);
    }

    public void Union(float _x, float _z)
    {
        var src_x = x;
        var src_y = y;
        var src_r = right;
        var src_b = bottom;

        x = Mathf.Min(src_x,        _x);
        x = Mathf.Min(x,            src_r);

        y = Mathf.Min(src_y,        _z);
        y = Mathf.Min(y,            src_b);

        right = Mathf.Max(src_x,    _x);
        right = Mathf.Max(right,    src_r);

        bottom = Mathf.Max(src_y,   _z);
        bottom = Mathf.Max(bottom,  src_b);
    }

    /// <summary>
    /// 与多个 aabbs 是否有任意的并集
    /// </summary>
    /// <param name="aabbs">多个 aabbs</param>
    /// <returns>如果有任意的并集,返回 true</returns>
    public bool AnyIntersect(List<QTAABB> aabbs)
    {
        foreach (var aabb in aabbs)
        {
            if (IsIntersect(aabb))
            {
                return true;
            }
        }
        return false;
    }
    /// <summary>
    /// 是否被 多个 aabbs 中的其中一个全包含
    /// </summary>
    /// <param name="aabbs">多个 aabbs</param>
    /// <returns>如果被其中一个全包含,返回 true</returns>
    public bool AnyContainsBy(List<QTAABB> aabbs)
    {
        foreach (var aabb in aabbs)
        {
            if (aabb.Contains(this))
            {
                return true;
            }
        }
        return false;
    }
    public bool Equals(QTAABB other)
    {
        return x == other.x && y == other.y && w == other.w && h == other.h;
    }
    /// <summary>
    /// jave.lin : 让x,y为最小值,right,bottom 为最大值
    /// 因为部分 w, h 可以为负数,那么再部分计算就不太方便,所以可以统一转换成 x,y < w,h 的格式
    /// </summary>
    public void Reorder()
    {
        var src_x = x;
        var src_y = y;
        var src_r = right;
        var src_b = bottom;

        x = Mathf.Min(src_x, src_r);
        y = Mathf.Min(src_y, src_b);

        x = Mathf.Max(src_x, src_r);
        y = Mathf.Max(src_y, src_b);
    }
    /// <summary>
    /// 获取 Camera 分层 level 的多个 aabb,如果 Camera 是一个正交投影,那么会无视 level 数值,直接返回一个 aabb
    /// </summary>
    /// <param name="cam">要获取多个 aabb 的 Camera</param>
    /// <param name="ret">结果</param>
    /// <param name="level">将 frustum 分解的层级数量</param>
    /// <param name="h_padding">添加水平边界间隔</param>
    public static void GetCameraAABBs(Camera cam, List<QTAABB> ret, 
        int level = DEFAULT_GET_CAM_FRUSTUM_TO_AABB_LEVEL, float h_padding = DEFAULT_GET_CAM_FRUSTUM_AABB_H_PADDING)
    {
        ret.Clear();
        if (cam.orthographic)
        {
            var aabb = new QTAABB();
            GetOrthorCameraAABB(cam, ref aabb, h_padding);
            ret.Add(aabb);
        }
        else
        {
            GetFrustumCameraAABBs(cam, ret, level, h_padding);
        }
    }
    public static void GetOrthorCameraAABB(Camera cam, ref QTAABB aabb, float h_padding = DEFAULT_GET_CAM_FRUSTUM_AABB_H_PADDING)
    {
        System.Diagnostics.Debug.Assert(cam.orthographic == true);
        var far             = cam.farClipPlane;
        var near            = cam.nearClipPlane;
        var delta_fn        = far - near;
        var half_height     = cam.orthographicSize;
        var half_with       = cam.aspect * half_height;
        var forward         = cam.transform.forward;
        var right           = cam.transform.right;
        var up              = cam.transform.up;
        var start_pos       = cam.transform.position + forward * near;
        var top_left        = start_pos + forward * delta_fn + (-right * half_with) + (up * half_height);
        var top_right       = top_left + (right * (2 * half_with));
        var bottom_right    = top_right + (-up * (2 * half_height));
        var bottom_left     = bottom_right + (-right * (2 * half_with));

        var h_padding_vec   = right * h_padding;

        top_left            -= h_padding_vec;
        top_right           += h_padding_vec;
        bottom_right        += h_padding_vec;
        bottom_left         -= h_padding_vec;

        // 重置
        aabb.w = aabb.h = 0;
        aabb.x = start_pos.x;
        aabb.y = start_pos.z;

        // 并集其他点
        aabb.Union(ref top_left);
        aabb.Union(ref top_right);
        aabb.Union(ref bottom_right);
        aabb.Union(ref bottom_left);
    }
    public static void GetFrustumCameraAABBs(Camera cam, List<QTAABB> aabbs, int level = DEFAULT_GET_CAM_FRUSTUM_TO_AABB_LEVEL, float h_padding = DEFAULT_GET_CAM_FRUSTUM_AABB_H_PADDING)
    {
        // 计算椎体分段包围盒
        System.Diagnostics.Debug.Assert(cam.orthographic == false);
        System.Diagnostics.Debug.Assert(level > 0);
        // 相机的 frustum 如果构建,可以参考我以前的一篇文章:https://blog.csdn.net/linjf520/article/details/104761121#OnRenderImage_98
        var far                     = cam.farClipPlane;
        var near                    = cam.nearClipPlane;
        var tan                     = Mathf.Tan(cam.fieldOfView * 0.5f * Mathf.Deg2Rad);
        var far_plane_half_height   = tan * far;
        var far_plane_half_with     = cam.aspect * far_plane_half_height;
        var near_plane_half_height  = tan * near;
        var near_plane_half_with    = cam.aspect * near_plane_half_height;

        var forward                 = cam.transform.forward;
        var right                   = cam.transform.right;
        var up                      = cam.transform.up;

        var far_top_left            = cam.transform.position + forward * far + (-right * far_plane_half_with) + (up * far_plane_half_height);
        var far_top_right           = far_top_left + (right * (2 * far_plane_half_with));
        var far_bottom_right        = far_top_right + (-up * (2 * far_plane_half_height));
        var far_bottom_left         = far_bottom_right + (-right * (2 * far_plane_half_with));

        var near_top_left           = cam.transform.position + forward * near + (-right * near_plane_half_with) + (up * near_plane_half_height);
        var near_top_right          = near_top_left + (right * (2 * near_plane_half_with));
        var near_bottom_right       = near_top_right + (-up * (2 * near_plane_half_height));
        var near_bottom_left        = near_bottom_right + (-right * (2 * near_plane_half_with));

        var n2f_top_left_vec        = far_top_left - near_top_left;
        var n2f_top_right_vec       = far_top_right - near_top_right;
        var n2f_bottom_right_vec    = far_bottom_right - near_bottom_right;
        var n2f_bottom_left_vec     = far_bottom_left - near_bottom_left;

        var h_padding_vec           = right * h_padding;

        for (int i = 0; i < level; i++)
        {
            var rate_start          = (float)i / level;
            var rate_end            = (float)(i + 1) / level;

            // near plane 四个角点
            var top_left_start      = near_top_left     + n2f_top_left_vec      * rate_start;
            var top_right_start     = near_top_right    + n2f_top_right_vec     * rate_start;
            var bottom_right_start  = near_bottom_right + n2f_bottom_right_vec  * rate_start;
            var bottom_left_start   = near_bottom_left  + n2f_bottom_left_vec   * rate_start;

            // 水平 padding
            top_left_start          -= h_padding_vec;
            top_right_start         += h_padding_vec;
            bottom_right_start      += h_padding_vec;
            bottom_left_start       -= h_padding_vec;

            // far plane 四个角点
            var top_left_end        = near_top_left     + n2f_top_left_vec      * rate_end;
            var top_right_end       = near_top_right    + n2f_top_right_vec     * rate_end;
            var bottom_right_end    = near_bottom_right + n2f_bottom_right_vec  * rate_end;
            var bottom_left_end     = near_bottom_left  + n2f_bottom_left_vec   * rate_end;

            // 水平 padding
            top_left_end            -= h_padding_vec;
            top_right_end           += h_padding_vec;
            bottom_right_end        += h_padding_vec;
            bottom_left_end         -= h_padding_vec;

            var aabb = new QTAABB();
            aabb.Set(top_left_start.x, top_left_start.z, 0, 0);

            // 并集其他点
            aabb.Union(ref top_left_start);
            aabb.Union(ref top_right_start);
            aabb.Union(ref bottom_right_start);
            aabb.Union(ref bottom_left_start);
            aabb.Union(ref top_left_end);
            aabb.Union(ref top_right_end);
            aabb.Union(ref bottom_right_end);
            aabb.Union(ref bottom_left_end);
            aabbs.Add(aabb);
        }
    }
    /// <summary>
    /// Unity 的 Bounds 隐式转为 我们自己定义的 QTAABB 便于外部书写
    /// </summary>
    /// <param name="v">Unity 的 Bounds</param>
    public static implicit operator QTAABB(Bounds v)
    {
        var b_min = v.min;
        var b_max = v.max;
        return new QTAABB 
        {
            min = new Vector2(b_min.x, b_min.z),
            max = new Vector2(b_max.x, b_max.z),
        };
    }
    /// <summary>
    /// 便于 VS 中断点调式的简要显示 title 信息
    /// </summary>
    /// <returns>返回:便于 VS 中断点调式的简要显示 title 信息</returns>
    public override string ToString()
    {
        return base.ToString() + $", x:{x}, y:{y}, w:{w}, h:{h}, right:{right}, bottom:{bottom}";
    }
}

/// <summary>
/// QuadTree 类
/// (
///     目前封装的写法适合静态构建四叉树的写法,
///     如果需要动态调整,可能还需要四叉树刷新机制,或是重构树的机制,
///     简单粗暴的重构树,也可以先 Clear 再逐个 Insert,但是会导致 Insert 消耗增加
/// )
/// 参考另一个:https://github.com/futurechris/QuadTree 该开源库写得还是不错的,可读性也高
/// date    : 2021/02/18
/// author  : jave.lin
/// </summary>
/// <typeparam name="T">四叉树中需要被包裹的实体对象类型</typeparam>
public class QuadTree<T> : IDisposable
{
    /// <summary>
    /// 四叉树的 Leaf 叶子类
    /// </summary>
    public class Leaf
    {
        public Branch branch;                                       // 枝干
        public T value;                                             // 包裹的数据对象
        public QTAABB aabb;                                         // 该叶子的 AABB
    }
    /// <summary>
    /// 四叉树的 Branch 枝干类
    /// </summary>
    public class Branch
    {
        public QuadTree<T> belongTree;                              // 所属的 四叉树
        public Branch parent;                                       // 父枝干
        public QTAABB aabb;                                         // 该枝干
        public QTAABB[] aabbs = new QTAABB[4];                      // 分支的四象限的 AABB       (先创建对象:空间换时间,省去后续的大量 != null 判断)
        public Branch[] branches = new Branch[4];                   // 分支的枝干                (先创建对象:空间换时间,省去后续的大量 != null 判断)
        public List<Leaf> leaves = new List<Leaf>();                // 拥有的叶子                (先创建对象:空间换时间,省去后续的大量 != null 判断)
        public List<Leaf> crossBranchesLeaves = new List<Leaf>();   // 横跨多个 枝干 的叶子      (先创建对象:空间换时间,省去后续的大量 != null 判断)
        public int depth;                                           // 该枝干的深度
        public bool hasSplit;                                       // 有无再次分过枝干
        public bool hasRecycle;                                     // 是否被回收了
    }

    public const int MAX_LIMIT_LEVEL = 32;                          // 四叉树最大可以设置的深度
    
    public const int DEFAULT_MAX_LEVEL = 10;
    public const int DEFAULT_MAX_LEAF_PER_BRANCH = 50;

    public float cullingDistance = float.NaN;

    // 叶子表
    private Dictionary<T, Leaf> leavesDict = new Dictionary<T, Leaf>();
    // 跟枝干
    public Branch root;
    // 最大层级
    private int maxLevel;
    // 叶子到达该数量时就会再次划分出枝干
    private int maxLeafPerBranch;

    private bool[] insert_helper = new bool[4];

    // 枝干、叶子的池子(减少 GC 频繁触发的问题),每个池子在自身的类对象下管理即可,看情况而是否该成 static 的
    private Stack<Branch> branchPool = new Stack<Branch>();
    private Stack<Leaf> leafPool = new Stack<Leaf>();
    private ListPool<T> listDataPool = new ListPool<T>();
    private List<QTAABB> aabbs_helper = new List<QTAABB>();

    /// <summary>
    /// 构建四叉树
    /// </summary>
    /// <param name="aabb">整个QuadTree的最大aabb</param>
    /// <param name="maxLevel">四叉树的最大深度</param>
    /// <param name="maxLeafPerBranch">四叉树单个叶子的最大数量</param>
    public QuadTree(QTAABB aabb, 
        int maxLevel = DEFAULT_MAX_LEVEL, int maxLeafPerBranch = DEFAULT_MAX_LEAF_PER_BRANCH)
        : this(aabb.x, aabb.y, aabb.w, aabb.h, maxLevel, maxLeafPerBranch)
    {

    }
    /// <summary>
    /// 构建四叉树
    /// </summary>
    /// <param name="x">整个QuadTree的最大aabb的x</param>
    /// <param name="y">整个QuadTree的最大aabb的y</param>
    /// <param name="w">整个QuadTree的最大aabb的w</param>
    /// <param name="h">整个QuadTree的最大aabb的h</param>
    /// <param name="maxLevel">四叉树的最大深度</param>
    /// <param name="maxLeafPerBranch">四叉树单个叶子的最大数量</param>
    public QuadTree(float x, float y, float w, float h, 
        int maxLevel = DEFAULT_MAX_LEVEL, int maxLeafPerBranch = DEFAULT_MAX_LEAF_PER_BRANCH)
    {
        _Reset(x, y, w, h, maxLevel, maxLeafPerBranch);
    }
    /// <summary>
    /// 销毁
    /// </summary>
    public void Dispose()
    {
        root = null;
        if (leavesDict != null)
        {
            leavesDict.Clear();
            leavesDict = null;
        }
        if (branchPool != null)
        {
            branchPool.Clear();
            branchPool = null;
        }
        if (leafPool != null)
        {
            leafPool.Clear();
            leafPool = null;
        }
        if (listDataPool != null)
        {
            listDataPool.Clear();
            listDataPool = null;
        }
        if (aabbs_helper != null)
        {
            aabbs_helper.Clear();
            aabbs_helper = null;
        }
    }
    /// <summary>
    /// 清理内部枝干结构到最初的状态
    /// </summary>
    public void Clear()
    {
        if (root != null)
        {
            var src_aabb = root.aabb;
            _RecycleBranchToPool(root);
            leavesDict.Clear();

            root = _GetBranchFromPool(null, 0, ref src_aabb);
        }
    }
    /// <summary>
    /// 插入(目前使用与 静态 QT 树的逻辑写法,所以插入处理成本比较大,但是在 Select 会大大提升性能)
    /// 如果要写动态 QT,那么 Insert 需要重新写一套逻辑,要尽可能的简单分割处理,那么时候可以另起一个测试项目来编写处理
    /// 现在的静态版本 QT 的插入时处理了跨 多枝干 的存放到父级节点的优化处理的,如果动态版本的话就不需要了
    /// 但是需要外部对筛选结果的去重
    /// </summary>
    /// <param name="value">插入的对象</param>
    /// <param name="aabb">插入对象对应的aabb</param>
    /// <returns>插入成功返回 true</returns>
    public bool Insert(T value, QTAABB aabb)
    {
        return Insert(value, ref aabb);
    }
    /// <summary>
    /// 插入(目前使用与 静态 QT 树的逻辑写法,所以插入处理成本比较大,但是在 Select 会大大提升性能)
    /// 如果要写动态 QT,那么 Insert 需要重新写一套逻辑,要尽可能的简单分割处理,那么时候可以另起一个测试项目来编写处理
    /// 现在的静态版本 QT 的插入时处理了跨 多枝干 的存放到父级节点的优化处理的,如果动态版本的话就不需要了
    /// 但是需要外部对筛选结果的去重
    /// </summary>
    /// <param name="value">插入的对象</param>
    /// <param name="aabb">插入对象对应的aabb</param>
    /// <returns>插入成功返回 true</returns>
    public bool Insert(T value, ref QTAABB aabb)
    {
        Leaf leaf;
        if (leavesDict.TryGetValue(value, out leaf))
        {
            leaf.aabb = aabb;
        }
        else
        {
            leaf = _GetLeafFromPool(value, ref aabb);
            leavesDict[value] = leaf;
        }
        return _Insert(root, leaf);
    }
    /// <summary>
    /// aabb范围筛选
    /// </summary>
    /// <param name="aabb">筛选的aabb范围</param>
    /// <param name="ret">筛选的结构存放列表对象</param>
    public void Select(QTAABB aabb, List<T> ret)
    {
        Select(ref aabb, ret);
    }
    /// <summary>
    /// aabb范围筛选
    /// </summary>
    /// <param name="aabb">筛选的aabb范围</param>
    /// <param name="ret">筛选的结构存放列表对象</param>
    /// <param name="moreActuallySelect">是否进行更精准一些的筛选</param>
    public void Select(ref QTAABB aabb, List<T> ret, bool moreActuallySelect = true)
    {
        ret.Clear();
#if __ENABLE_COMPLETE_CONTAINS_BRANCH_HANDLE__

#if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree._SelectByAABB");
#endif

        List<T> compeleteInAABB_ret = listDataPool.FromPool();
        _SelectByAABB(ref aabb, ret, compeleteInAABB_ret, root);

#if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#endif

        if (moreActuallySelect)
        {
            for (int i = ret.Count - 1; i > -1; i--)
            {
                if (!aabb.IsIntersect(leavesDict[ret[i]].aabb))
                {
                    ret.RemoveAt(i);
                }
            }
        }
        if (compeleteInAABB_ret.Count > 0)
        {
            ret.AddRange(compeleteInAABB_ret);
        }
        listDataPool.ToPool(compeleteInAABB_ret);
#else
        
#if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree._SelectByAABB");
#endif

        _SelectByAABB(ref aabb, ret, root);

#if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#endif

        if (moreActuallySelect)
        {
            for (int i = ret.Count - 1; i > -1; i--)
            {
                if (!aabb.IsIntersect(leavesDict[ret[i]].aabb))
                {
                    ret.RemoveAt(i);
                }
            }
        }
#endif
    }
    /// <summary>
    /// aabb范围筛选
    /// </summary>
    /// <param name="x">筛选的aabb范围的x</param>
    /// <param name="y">筛选的aabb范围的y</param>
    /// <param name="w">筛选的aabb范围的w</param>
    /// <param name="h">筛选的aabb范围的h</param>
    /// <param name="ret">筛选的结构存放列表对象</param>
    public void Select(float x, float y, float w, float h, List<T> ret)
    {
        Select(new QTAABB { x = x, y = y, w = w, h = h }, ret);
    }
    /// <summary>
    /// 点选
    /// </summary>
    /// <param name="pos">点选坐标</param>
    /// <param name="ret">点选的结构存放列表对象</param>
    public void Select(Vector2 pos, List<T> ret)
    {
        Select(ref pos, ret);
    }
    /// <summary>
    /// 点选
    /// </summary>
    /// <param name="pos">点选坐标</param>
    /// <param name="ret">点选的结构存放列表对象</param>
    /// <param name="moreActuallySelect">是否进行更精准一些的筛选</param>
    public void Select(ref Vector2 pos, List<T> ret, bool moreActuallySelect = true)
    {
#if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree.Select(ref Vector2 pos, List<T> ret, bool moreActuallySelect = true)");
#endif
        ret.Clear();
        _SelectByPos(ref pos, ret, root);
        if (moreActuallySelect)
        {
            for (int i = ret.Count - 1; i > -1; i--)
            {
                if (!leavesDict[ret[i]].aabb.Contains(pos))
                {
                    ret.RemoveAt(i);
                }
            }
        }
#if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#endif
    }
    /// <summary>
    /// 点选
    /// </summary>
    /// <param name="x">点选坐标x</param>
    /// <param name="y">点选坐标y</param>
    /// <param name="ret">点选的结构存放列表对象</param>
    public void Select(float x, float y, List<T> ret)
    {
        Select(new Vector2(x, y), ret);
    }
    /// <summary>
    /// 多选
    /// </summary>
    /// <param name="aabbs">多个aabb范围</param>
    /// <param name="ret">筛选的结构存放列表对象</param>
    /// <param name="moreActuallySelect">是否进行更精准一些的筛选(注意:还是 AABB 的概要筛选,如果需要精准到自定义的碰撞筛选级别,这里最好传入 false,再让外部去筛选, 会增加消耗)</param>
    public void Select(List<QTAABB> aabbs, List<T> ret, bool moreActuallySelect = true)
    {
        ret.Clear();

#if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree.Select(List<QTAABB> aabbs, List<T> ret, bool moreActuallySelect = true)");
#endif


#if __ENABLE_COMPLETE_CONTAINS_BRANCH_HANDLE__

#   if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree.Select.1");
#   endif
        List<T> compeleteInAABB_ret = listDataPool.FromPool();
        _SelectByAABBS(aabbs, ret, compeleteInAABB_ret, root);
#   if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#   endif

#   if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree.Select.2"); // 消耗有点大
#   endif
        if (moreActuallySelect)
        {
            for (int i = ret.Count - 1; i > -1; i--)
            {
                var intersect = false;
                foreach (var aabb in aabbs)
                {
                    if (aabb.IsIntersect(leavesDict[ret[i]].aabb))
                    {
                        intersect = true;
                        break;
                    }
                }
                if (!intersect)
                {
                    ret.RemoveAt(i);
                }
            }
        }
#   if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#   endif

        if (compeleteInAABB_ret.Count > 0)
        {
            ret.AddRange(compeleteInAABB_ret);
        }
        listDataPool.ToPool(compeleteInAABB_ret);
#else

#   if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree.Select.1");
#   endif
        _SelectByAABBS(aabbs, ret, root);
#   if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#   endif

#   if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree.Select.2"); // 消耗有点大
#   endif
        if (moreActuallySelect)
        {
            for (int i = ret.Count - 1; i > -1; i--)
            {
                var intersect = false;
                foreach (var aabb in aabbs)
                {
                    if (aabb.IsIntersect(leavesDict[ret[i]].aabb))
                    {
                        intersect = true;
                        break;
                    }
                }
                if (!intersect)
                {
                    ret.RemoveAt(i);
                }
            }
        }

#   if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#   endif

#endif

#if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#endif
    }
    /// <summary>
    /// aabb范围筛选,并根据 relactivePos 点与 QuadTree 的 cullingDistance 做距离剔除
    /// </summary>
    /// <param name="aabb">筛选的aabb范围</param>
    /// <param name="ret">筛选的结构存放列表对象</param>
    /// <param name="selectFromPos">与 QuadTree 的 cullingDistance 做距离剔除 的相关的点</param>
    public void Select(QTAABB aabb, List<T> ret, Vector2 selectFromPos)
    {
        Select(ref aabb, ret, selectFromPos);
    }
    /// <summary>
    /// aabb范围筛选
    /// </summary>
    /// <param name="aabb">筛选的aabb范围</param>
    /// <param name="ret">筛选的结构存放列表对象</param>
    /// <param name="selectFromPos">与 QuadTree 的 cullingDistance 做距离剔除 的相关的点</param>
    /// <param name="moreActuallySelect">是否进行更精准一些的筛选</param>
    public void Select(ref QTAABB aabb, List<T> ret, Vector2 selectFromPos, bool moreActuallySelect = true)
    {
        if (_IsCullingDistance(ref aabb, selectFromPos))
        {
            ret.Clear();
            return;
        }
        Select(ref aabb, ret, moreActuallySelect);
    }
    /// <summary>
    /// aabb范围筛选
    /// </summary>
    /// <param name="x">筛选的aabb范围的x</param>
    /// <param name="y">筛选的aabb范围的y</param>
    /// <param name="w">筛选的aabb范围的w</param>
    /// <param name="h">筛选的aabb范围的h</param>
    /// <param name="ret">筛选的结构存放列表对象</param>
    /// <param name="selectFromPos">与 QuadTree 的 cullingDistance 做距离剔除 的相关的点</param>
    public void Select(float x, float y, float w, float h, List<T> ret, Vector2 selectFromPos)
    {
        if (_IsCullingDistance(new QTAABB { x = x, y = y, w = w, h = h }, selectFromPos))
        {
            ret.Clear();
            return;
        }
        Select(x, y, w, h, ret);
    }
    /// <summary>
    /// 点选
    /// </summary>
    /// <param name="pos">点选坐标</param>
    /// <param name="ret">点选的结构存放列表对象</param>
    /// <param name="selectFromPos">与 QuadTree 的 cullingDistance 做距离剔除 的相关的点</param>
    public void Select(Vector2 pos, List<T> ret, Vector2 selectFromPos)
    {
        if (_IsCullingDistance(pos, selectFromPos))
        {
            ret.Clear();
            return;
        }
        Select(ref pos, ret);
    }
    public void Select(ref Vector2 pos, List<T> ret, Vector2 selectFromPos, bool moreActuallySelect = true)
    {
        if (_IsCullingDistance(pos, selectFromPos))
        {
            ret.Clear();
            return;
        }
        Select(ref pos, ret, moreActuallySelect);
    }
    /// <summary>
    /// 点选
    /// </summary>
    /// <param name="x">点选坐标x</param>
    /// <param name="y">点选坐标y</param>
    /// <param name="ret">点选的结构存放列表对象</param>
    /// <param name="selectFromPos">与 QuadTree 的 cullingDistance 做距离剔除 的相关的点</param>
    public void Select(float x, float y, List<T> ret, Vector2 selectFromPos)
    {
        Select(new Vector2(x, y), ret, selectFromPos);
    }
    /// <summary>
    /// 多选
    /// </summary>
    /// <param name="aabbs">多个aabb范围</param>
    /// <param name="ret">筛选的结构存放列表对象</param>
    /// <param name="selectFromPos">与 QuadTree 的 cullingDistance 做距离剔除 的相关的点</param>
    /// <param name="moreActuallySelect">是否进行更精准一些的筛选(注意:还是 AABB 的概要筛选,如果需要精准到自定义的碰撞筛选级别,这里最好传入 false,再让外部去筛选, 会增加消耗)</param>
    public void Select(List<QTAABB> aabbs, List<T> ret, Vector2 selectFromPos, bool moreActuallySelect = true)
    {
        if (aabbs.Count == 0)
        {
            return;
        }

        aabbs_helper.Clear();
        aabbs_helper.AddRange(aabbs);
        for (int i = 0; i < aabbs.Count; i++)
        {
            if (_IsCullingDistance(aabbs_helper[i], selectFromPos))
            {
                aabbs_helper.RemoveRange(i, aabbs.Count - i);
                break;
            }
        }

        if (aabbs_helper.Count == 0)
        {
            return;
        }

        Select(aabbs_helper, ret, moreActuallySelect);
    }
    private bool _IsCullingDistance(Vector2 selectPos, Vector2 selectFormPos)
    {
        // culling by distance
        return _IsCullingDistance(ref selectPos, selectFormPos);
    }
    private bool _IsCullingDistance(ref Vector2 selectPos, Vector2 selectFormPos)
    {
        // culling by distance
        var cd = cullingDistance;
        if (!float.IsNaN(cd) && cd > 0)
        {
            var pow2cd = cd * cd;
            if (pow2cd < Vector2.SqrMagnitude(selectPos - selectFormPos))
            {
                return true;
            }
        }
        return false;
    }
    private bool _IsCullingDistance(QTAABB aabb, Vector2 relactivePos)
    {
        return _IsCullingDistance(ref aabb, relactivePos);
    }
    private bool _IsCullingDistance(ref QTAABB aabb, Vector2 relactivePos)
    {
        // culling by distance
        var cd = cullingDistance;
        if (!float.IsNaN(cd) && cd > 0)
        {
            var pow2cd = cd * cd;

            var tl = aabb.top_left;
            var tr = aabb.top_right;
            var br = aabb.bottom_right;
            var bl = aabb.bottom_left;

            var culling_count = 0;
            if (pow2cd < Vector2.SqrMagnitude(tl - relactivePos))
            {
                culling_count++;
            }
            if (pow2cd < Vector2.SqrMagnitude(tr - relactivePos))
            {
                culling_count++;
            }
            if (pow2cd < Vector2.SqrMagnitude(br - relactivePos))
            {
                culling_count++;
            }
            if (pow2cd < Vector2.SqrMagnitude(bl - relactivePos))
            {
                culling_count++;
            }
            return (culling_count == 4);
        }
        return false;
    }

#if __ENABLE_COMPLETE_CONTAINS_BRANCH_HANDLE__
    private void _SelectByAABB(ref QTAABB aabb, List<T> ret, List<T> compeleteInAABB_ret, Branch branch)
    {
        if (branch == null)
        {
            return;
        }

#if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree._SelectByAABB(ref QTAABB aabb, List<T> ret, List<T> compeleteInAABB_ret, Branch branch)");
#endif
        if (branch.aabb.Contains(ref aabb))
        {
            // 完整包含了底下枝干的 aabb
            _SelectAllValues(branch, compeleteInAABB_ret);
        }
        else
        {
            // 与部分的交集
            if (branch.aabb.IsIntersect(ref aabb))
            {
                foreach (var l in branch.leaves)
                {
                    ret.Add(l.value);
                }
                foreach (var l in branch.crossBranchesLeaves)
                {
                    ret.Add(l.value);
                }
                foreach (var b in branch.branches)
                {
                    _SelectByAABB(ref aabb, ret, compeleteInAABB_ret, b);
                }
            }
        }

#if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#endif
    }
#else
    private void _SelectByAABB(ref QTAABB aabb, List<T> ret, Branch branch)
    {
        if (branch == null)
        {
            return;
        }

#if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree._SelectByAABB(ref QTAABB aabb, List<T> ret, Branch branch)");
#endif
        if (branch.aabb.Contains(ref aabb))
        {
            // 完整包含了底下枝干的 aabb
            _SelectAllValues(branch, ret);
        }
        else
        {
            // 与部分的交集
            if (branch.aabb.IsIntersect(ref aabb))
            {
                foreach (var l in branch.leaves)
                {
                    ret.Add(l.value);
                }
                foreach (var l in branch.crossBranchesLeaves)
                {
                    ret.Add(l.value);
                }
                foreach (var b in branch.branches)
                {
                    _SelectByAABB(ref aabb, ret, b);
                }
            }
        }
#if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#endif
    }
#endif
    private void _SelectAllValues(Branch branch, List<T> ret)
    {
        if (branch == null)
        {
            return;
        }
#if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree._SelectAllValues(Branch branch, List<T> ret)");
#endif
        if (branch != null)
        {
            foreach (var l in branch.leaves)
            {
                ret.Add(l.value);
            }
            foreach (var l in branch.crossBranchesLeaves)
            {
                ret.Add(l.value);
            }
            foreach (var b in branch.branches)
            {
                _SelectAllValues(b, ret);
            }
        }
#if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#endif
    }
    private void _SelectByPos(ref Vector2 pos, List<T> ret, Branch branch)
    {
        if (branch == null)
        {
            return;
        }

#if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree._SelectByPos(ref Vector2 pos, List<T> ret, Branch branch)");
#endif
        if (branch.aabb.Contains(ref pos))
        {
            foreach (var l in branch.leaves)
            {
                ret.Add(l.value);
            }
            foreach (var l in branch.crossBranchesLeaves)
            {
                ret.Add(l.value);
            }
            foreach (var b in branch.branches)
            {
                _SelectByPos(ref pos, ret, b);
            }
        }
#if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#endif
    }
#if __ENABLE_COMPLETE_CONTAINS_BRANCH_HANDLE__
    private void _SelectByAABBS(List<QTAABB> aabbs, List<T> ret, List<T> compeleteInAABB_ret, Branch branch)
    {
        if (branch == null)
        {
            return;
        }
#if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree._SelectByAABBS(List<QTAABB> aabbs, List<T> ret, List<T> compeleteInAABB_ret, Branch branch)");
#endif
        if (branch.aabb.AnyContainsBy(aabbs))
        {
            // 完整包含了底下枝干的 aabb
            _SelectAllValues(branch, compeleteInAABB_ret);
        }
        else
        {
            // 与部分的交集
            if (branch.aabb.AnyIntersect(aabbs))
            {
                foreach (var l in branch.leaves)
                {
                    ret.Add(l.value);
                }
                foreach (var l in branch.crossBranchesLeaves)
                {
                    ret.Add(l.value);
                }
                foreach (var b in branch.branches)
                {
                    _SelectByAABBS(aabbs, ret, compeleteInAABB_ret, b);
                }
            }
        }
#if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#endif
    }
#else
    private void _SelectByAABBS(List<QTAABB> aabbs, List<T> ret, Branch branch)
    {
        if (branch == null)
        {
            return;
        }
#   if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree._SelectByAABBS");
#   endif
        if (branch.aabb.AnyContainsBy(aabbs))
        {
            // 完整包含了底下枝干的 aabb
            _SelectAllValues(branch, ret);
        }
        else
        {
            // 与部分的交集
            if (branch.aabb.AnyIntersect(aabbs))
            {
                foreach (var l in branch.leaves)
                {
                    ret.Add(l.value);
                }
                foreach (var l in branch.crossBranchesLeaves)
                {
                    ret.Add(l.value);
                }
                foreach (var b in branch.branches)
                {
                    _SelectByAABBS(aabbs, ret, b);
                }
            }
        }


#   if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#   endif
    }
#endif

    private bool _Insert(Branch branch, Leaf leaf)
    {
#if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree._Insert(Branch branch, Leaf leaf)");
#endif
        var ret = false;
        if (!branch.aabb.IsIntersect(ref leaf.aabb))
        {
            // 不在该枝干管理范围外,则不处理插入
            //ret = false;
        }
        else
        {
            if (branch.hasSplit)
            {
                // 将之前 叶子 的插入到 子枝干 上去
                if (branch.leaves.Count > 0)
                {
                    _SrcLeavesInsertToSubBranches(branch);
                }
                ret = _InsertSingleLeaf(branch, leaf);
            }
            else
            {
                if (branch.depth <= maxLevel && (branch.leaves.Count + branch.crossBranchesLeaves.Count) >= maxLeafPerBranch)
                {
                    // 已达最大深度限制,已超过对应的数量,那么再次细分该枝干
                    branch.hasSplit = true;
                    ret = _Insert(branch, leaf);
                }
                else
                {
                    // 未达最大深度限制,未超过对应的数量,那么插入该枝干
                    branch.leaves.Add(leaf);
                    leaf.branch = branch;
                    ret = true;
                }
            }
        }
#if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#endif
        return ret;
    }
    private void _SrcLeavesInsertToSubBranches(Branch branch)
    {
#if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree._SrcLeavesInsertToSubBranches(Branch branch)");
#endif
        for (int i = 0; i < branch.leaves.Count; i++)
        {
            var l = branch.leaves[i];
            _InsertSingleLeaf(branch, l);
        }
        branch.leaves.Clear();
#if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#endif
    }

    private bool _InsertSingleLeaf(Branch branch, Leaf leaf)
    {
#if __ENABLE_QT_PROFILER__
        Profiler.BeginSample($"QuadTree._InsertSingleLeaf(Branch branch, Leaf leaf)");
#endif
        var contains_with_branch_count = 0;
        var branch_idx = -1;
        for (int i = 0; i < 4; i++)
        {
            if (branch.aabbs[i].IsIntersect(ref leaf.aabb))
            {
                contains_with_branch_count++;
                insert_helper[i] = true;
                branch_idx = i;
                if (branch.branches[i] == null || branch.branches[i].hasRecycle)
                {
                    branch.branches[i] = _GetBranchFromPool(branch, branch.depth + 1, ref branch.aabbs[i]);
                }
            }
        }

        var ret = false;
        if (contains_with_branch_count > 1)
        {
            // 与多个枝干有交集,就插入到父级的枝干上
            // 这里就不判断 maxLeafPerBranch 的限制了
            branch.crossBranchesLeaves.Add(leaf);
            leaf.branch = branch;
            ret = true;
        }
        else
        {
            // 插入到对应的枝干上
            //System.Diagnostics.Debug.Assert(branch_idx > -1 && branch_idx < 4);
            if (branch_idx < 0 || branch_idx > 3) branch_idx = 3;
            ret = _Insert(branch.branches[branch_idx], leaf);
        }
#if __ENABLE_QT_PROFILER__
        Profiler.EndSample();
#endif
        return ret;
    }

    private Branch _GetBranchFromPool(Branch parent, int depth, ref QTAABB aabb)
    {
        var ret = branchPool.Count > 0 ? branchPool.Pop() : new Branch();
        ret.belongTree = this;
        ret.parent = parent;
        ret.depth = depth;
        ret.aabb = aabb;
        ret.hasRecycle = false;
        float halfW = aabb.w * 0.5f;
        float halfH = aabb.h * 0.5f;
        float midX = aabb.x + halfW;
        float midY = aabb.y + halfH;
        ret.aabbs[0].Set(aabb.x, aabb.y, halfW, halfH); // top-left
        ret.aabbs[1].Set(midX,   aabb.y, halfW, halfH); // top-right
        ret.aabbs[2].Set(midX,   midY,   halfW, halfH); // bottom-right
        ret.aabbs[3].Set(aabb.x, midY,   halfW, halfH); // bottom-left
        return ret;
    }
    private void _RecycleBranchToPool(Branch branch)
    {
        if (branch == null)
        {
            return;
        }
        branch.belongTree = null;
        branch.parent = null;
        branch.hasSplit = false;
        branch.hasRecycle = true;
        branchPool.Push(branch);

        foreach (var l in branch.leaves)
        {
            _RecycleLeafToPool(l);
        }
        branch.leaves.Clear();
        foreach (var l in branch.crossBranchesLeaves)
        {
            _RecycleLeafToPool(l);
        }
        branch.crossBranchesLeaves.Clear();

        for (int i = 0; i < branch.branches.Length; i++)
        {
            _RecycleBranchToPool(branch.branches[i]);
            branch.branches[i] = null;
        }
    }
    private Leaf _GetLeafFromPool(T value, ref QTAABB aabb)
    {
        var ret = leafPool.Count > 0 ? leafPool.Pop() : new Leaf();
        ret.value = value;
        ret.aabb = aabb;
        return ret;
    }
    private void _RecycleLeafToPool(Leaf leaf)
    {
        leaf.branch = null;
        leaf.value = default;
        leafPool.Push(leaf);
    }
    private void _Reset(float x, float y, float w, float h,
        int maxLevel = DEFAULT_MAX_LEVEL, int maxLeafPerBranch = DEFAULT_MAX_LEAF_PER_BRANCH)
    {
        System.Diagnostics.Debug.Assert(w == 0 || h == 0, "QTAABB is Zero");
        System.Diagnostics.Debug.Assert(maxLevel < MAX_LIMIT_LEVEL, $"QuadTree MaxLevel cannot more than : {MAX_LIMIT_LEVEL}");

        this.maxLevel            = maxLevel;
        this.maxLeafPerBranch    = maxLeafPerBranch;

        var aabb = new QTAABB { x = x, y = y, w = w, h = h };
        if (root != null)
        {
            root.aabb = aabb;
        }
        else
        {
            root = _GetBranchFromPool(null, 0, ref aabb);
        }
    }
}

TestingQuadTree.cs

using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 测试自己编写的 静态 QuadTree 的功能
/// 如果要编写 动态版本的 QuadTree,那么再 Insert 的逻辑要尽可能简要
/// 后续有空我可以再写另一个 动态版本的 QuadTree
/// date    : 2021/02/20
/// author  : jave.lin
/// </summary>
public class TestingQuadTree : MonoBehaviour
{
    public enum eShowGOType
    {
        None = 0,
        ShowInFrustum = 1,
        ShowNotInFrustum = 2,
        All = -1,
    }

    [Header("镜头")]
    public Camera cam;

    [Header("四叉树内的对象")]
    public GameObject[] goes;

    [Header("原始 四叉树 的根对象")]
    public GameObject goes_root;

    [Header("绘制镜头的 aabb 的颜色")]
    public Color cam_aabb_color = Color.red;
    [Header("绘制镜头视锥的 aabb 的颜色")]
    public Color cam_frustum_color = Color.yellow;
    [Header("绘制四叉树的枝干 aabb 的颜色")]
    public Color qt_branches_color = Color.cyan;
    [Header("绘制四叉树的叶子 aabb 的颜色")]
    public Color qt_leaves_color = Color.green;
    [Header("绘制镜头视锥内的 aabb 叉树的叶子的 aabb 的颜色")]
    public Color qt_leaves_in_frustum_color = Color.blue;

    [Header("是否绘制镜头的 aabb")]
    public bool draw_gizmos_cam_aabb = true;
    [Header("是否绘制镜头视锥的 wireframe")]
    public bool draw_gizmos_cam_wireframe = true;
    [Header("是否绘制四叉树的枝干 aabb")]
    public bool draw_gizmos_qt_branches = true;
    [Header("是否绘制四叉树的叶子 aabb")]
    public bool draw_gizmos_qt_leaves = true;
    [Header("是否绘制镜头视锥内的 aabb 叉树的叶子的 aabb")]
    public bool draw_gizmos_qt_leaves_in_frustum_aabb = true;

    [Header("控制 GameObject 的显示类型")]
    public eShowGOType show_GO_type = eShowGOType.ShowInFrustum;

    [Header("视锥水平剔除空间扩大的 unit 单位")]
    public float frustum_h_padding = 5;

    [Header("视锥分段的多层 AABB 的级别")]
    [Range(1, 20)]
    public int frustum_AABB_level = 3;

    [Header("开启的话,结果会再精准一些,但会增加部分计算量(也可以关闭后,让外部来处理:稍微精确一些的筛选)")]
    public bool more_actually_select = true;

    [Header("测试时实时重插四叉树(暴力重构会导致很卡,消耗 CPU 很大,3456 个对象就需要花费 8.5ms-+)")]
    public bool reconstrct_qt_runtime = false;

    private QuadTree<GameObject> qt;
    private List<GameObject> qt_select_ret_helper;
    private List<QTAABB> cam_aabbs = new List<QTAABB>();

    private void Start()
    {
        // 计算整个场景的 bounds
        var scene_bounds = CalculateSceneAABB();

        // 创建四叉树对象
        qt = new QuadTree<GameObject>(scene_bounds);
        qt_select_ret_helper = new List<GameObject>();

        // 给 qt 插入每一个
        InsertQTObjs();
    }

    private void Update()
    {
        QT_Select();
        UpdateGoVisible();

        if (reconstrct_qt_runtime)
        {
            ReconstructQT();
        }
    }

    private Bounds CalculateSceneAABB()
    {
        var ret = new Bounds();
        foreach (var go in goes)
        {
            var renderer = go.GetComponent<Renderer>();
            if (renderer != null)
            {
                ret.Encapsulate(renderer.bounds);
            }
        }
        return ret;
    }

    private void QT_Select()
    {
        // 概要筛选
        qt.Select(cam_aabbs, qt_select_ret_helper, more_actually_select);
    }

    private void UpdateGoVisible()
    {
        switch (show_GO_type)
        {
            case eShowGOType.None:
                goes_root.SetAct(false);
                break;
            case eShowGOType.ShowInFrustum:
                goes_root.SetAct(true);
                foreach (var go in goes)
                {
                    go.SetAct(false);
                }
                if (qt != null && qt_select_ret_helper != null)
                {
                    // 概要筛选
                    qt.Select(cam_aabbs, qt_select_ret_helper, more_actually_select);
                    foreach (var go in qt_select_ret_helper)
                    {
                        go.SetAct(true);
                    }
                }
                break;
            case eShowGOType.ShowNotInFrustum:
                goes_root.SetAct(true);
                foreach (var go in goes)
                {
                    go.SetAct(true);
                }
                if (qt != null && qt_select_ret_helper != null)
                {
                    // 概要筛选
                    qt.Select(cam_aabbs, qt_select_ret_helper, more_actually_select);
                    foreach (var go in qt_select_ret_helper)
                    {
                        go.SetAct(false);
                    }
                }
                break;
            case eShowGOType.All:
                goes_root.SetAct(true);
                foreach (var go in goes)
                {
                    go.SetAct(true);
                }
                break;
            default:
                break;
        }
    }

    private void InsertQTObjs()
    {
        // 给 qt 插入每一个
        foreach (var go in goes)
        {
            var renderer = go.GetComponent<Renderer>();
            if (renderer != null)
            {
                qt.Insert(go, renderer.bounds);
            }
        }
    }
    private void ReconstructQT()
    {
        // 暴力重构法

        // 先全部重新清理
        qt.Clear();
        // 全部重新插入
        InsertQTObjs();

        // 动态四叉树,暂时不处理(后续需要再查阅资料,学习后,再制作)
    }

    private void OnDrawGizmos()
    {
        // 绘制 cam 的 aabb
        if (draw_gizmos_cam_aabb) _DrawCameraAABB();
        // 绘制 cam 的 wireframe
        if (draw_gizmos_cam_wireframe) _DrawCameraWireframe();
        // 绘制 qt 中每个枝干 的 aabb
        if (draw_gizmos_qt_branches) _DrawQTBranchesAABBs();
        // 绘制 qt 中每个 leaf 的 aabb
        if (draw_gizmos_qt_leaves) _DrawQTLeavesAABBs();
        // 绘制 qt 中每个在 frustum aabb 内的 leaf 的 aabb
        if (draw_gizmos_qt_leaves_in_frustum_aabb) _DrawQTInFrustumLeavesAABBs();
    }

    private void _DrawCameraAABB()
    {
        QTAABB.GetCameraAABBs(cam, cam_aabbs, frustum_AABB_level, frustum_h_padding);

        foreach (var aabb in cam_aabbs)
        {
            _DrawQTAABB(aabb, cam_aabb_color);
        }
    }

    private void _DrawCameraWireframe()
    {
        Gizmos.color = cam_frustum_color;

        // 可参考我以前的一篇文章:https://blog.csdn.net/linjf520/article/details/104994304#SceneGizmos_35
        Matrix4x4 temp = Gizmos.matrix;
        Gizmos.matrix = Matrix4x4.TRS(cam.transform.position, cam.transform.rotation, Vector3.one);
        if (!cam.orthographic)
        {
            // 透视视锥
            Gizmos.DrawFrustum(Vector3.zero, cam.fieldOfView, cam.farClipPlane, cam.nearClipPlane, cam.aspect);
        }
        else
        {
            // 正交 cube
            var far = cam.farClipPlane;
            var near = cam.nearClipPlane;
            var delta_fn = far - near;

            var half_height = cam.orthographicSize;
            var half_with = cam.aspect * half_height;
            var pos = Vector3.forward * (delta_fn * 0.5f + near);
            var size = new Vector3(half_with * 2, half_height * 2, delta_fn);

            Gizmos.DrawWireCube(pos, size);
        }
        Gizmos.matrix = temp;
    }

    private void _DrawQTBranchesAABBs()
    {
        if (qt != null)
        {
            _DrawBranch(qt.root, qt_branches_color);
        }
    }

    private void _DrawQTLeavesAABBs()
    {
        if (qt != null)
        {
            _DrawLeafsOfBrances(qt.root, qt_leaves_color);
        }
    }

    private void _DrawQTInFrustumLeavesAABBs()
    {
        if (qt != null && qt_select_ret_helper != null)
        {
            foreach (var go in qt_select_ret_helper)
            {
                var renderer = go.GetComponent<Renderer>();
                _DrawBoundsXZ(renderer.bounds, qt_leaves_in_frustum_color);
            }
        }
    }

    private void _DrawBranch(QuadTree<GameObject>.Branch branch, Color color)
    {
        if (branch == null)
        {
            return;
        }

        // draw this branch
        _DrawQTAABB(branch.aabb, color);

        // draw sub branches
        foreach (var b in branch.branches)
        {
            _DrawBranch(b, color);
        }
    }

    private void _DrawLeafsOfBrances(QuadTree<GameObject>.Branch branch, Color color)
    {
        if (branch == null)
        {
            return;
        }
        foreach (var b in branch.branches)
        {
            if (b == null)
            {
                continue;
            }
            foreach (var l in b.leaves)
            {
                _DrawQTAABB(l.aabb, color);
            }
            _DrawLeafsOfBrances(b, color);
        }
    }

    private void _DrawBoundsXZ(Bounds bounds, Color color)
    {
        Gizmos.color = color;

        var min = bounds.min;
        var max = bounds.max;

        var start_pos = min;
        var end_pos = min;
        end_pos.x = max.x;

        Gizmos.DrawLine(start_pos, end_pos);

        start_pos = end_pos;
        end_pos = start_pos;
        end_pos.z = max.z;

        Gizmos.DrawLine(start_pos, end_pos);

        start_pos = end_pos;
        end_pos = start_pos;
        end_pos.x = min.x;

        Gizmos.DrawLine(start_pos, end_pos);

        start_pos = end_pos;
        end_pos = start_pos;
        end_pos.z = min.z;

        Gizmos.DrawLine(start_pos, end_pos);
    }

    private void _DrawQTAABB(QTAABB aabb, Color color)
    {
        Gizmos.color = color;

        var min = aabb.min;
        var max = aabb.max;

        var start_pos = new Vector3(min.x, 0, min.y);
        var end_pos = start_pos;
        end_pos.x = max.x;

        Gizmos.DrawLine(start_pos, end_pos);

        start_pos = end_pos;
        end_pos = start_pos;
        end_pos.z = max.y;

        Gizmos.DrawLine(start_pos, end_pos);

        start_pos = end_pos;
        end_pos = start_pos;
        end_pos.x = min.x;

        Gizmos.DrawLine(start_pos, end_pos);

        start_pos = end_pos;
        end_pos = start_pos;
        end_pos.z = min.y;

        Gizmos.DrawLine(start_pos, end_pos);
    }
}

public static class MyExt
{
    public static void SetAct(this GameObject go, bool value)
    {
        if (go.activeSelf != value)
        {
            go.SetActive(value);
        }
    }
}

问题

我这个例子是应用在:3D 顶视角下的 2D 四叉树剔除


Project


扩展方案

后面还有 四叉树 + 视锥cascaded + instancing + custom lod 的场景优化方案

之前制作到一半,但是给人使绊子,就没再继续撸下去了,后续在家重新路一套完整的

  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值