Meagher 1982 八叉树 — 把立方体一分为八,递归地装下一整个 3D 世界
是什么
八叉树(Octree)是一种把 3D 空间不断对半切的数据结构。日常类比:你想找出一座城市里”哪些格子有建筑”。最笨的办法是把城市切成 1 米 × 1 米 × 1 米的小方块,挨个检查——城市稍大就爆内存。
聪明办法:先把整个城市当一个大方块。如果这个方块整块都是建筑或者整块都是空气,那就一个标签搞定,不用细分。否则把它切成 8 个一样大的子方块(左前下、右前下、左后下、右后下、左前上、右前上、左后上、右后上),递归对每个子方块问同样的问题。
为什么是 8 个?因为 3D 空间有 3 个轴(x/y/z),每个轴对半切,2×2×2 = 8。
Meagher 1982 这篇论文系统地把这个想法变成可计算的数据结构 + 算法集合——存储、布尔运算(并、交、差)、几何变换、显示渲染——全部用八叉树做。从此 3D 几何引擎、体绘制、碰撞检测、Minecraft、稀疏体素都建在它上面。
为什么重要
- 没有八叉树就没有 Minecraft:游戏世界不是一个 4096×4096×256 的大数组(那是 40 亿格),而是一棵八叉树。空气和岩石各自是大块,只在边界细分
- GPU 体素全局光照(VXGI、Voxel Cone Tracing)每帧都在八叉树上 ray-march
- 碰撞检测:物理引擎(Bullet、PhysX)用八叉树先剔除”显然不会碰”的物体对
- 点云压缩:LiDAR 扫描得到几亿个点,八叉树存比裸数组小 10 倍以上
- 稀疏体素八叉树(SVO) 是 NVIDIA 2010 后实时光追的关键数据结构
- 核心洞见:3D 数据有大量”大片相同”的区域,递归切分让你只在边界花钱
核心要点
-
节点只有两种状态:要么是叶子(整块同色/同材质,存一个值就够),要么是内部节点(不同质,存 8 个孩子指针)。这个二分把”自适应分辨率”变成数据结构的天然属性
-
三种灰度:黑(全实心)/ 白(全空)/ 灰(混合,需要继续切)。算法一直递归到”黑或白”为止。论文里叫 BWG(Black/White/Gray)
-
布尔运算就是两棵树同步走:
A ∪ B时,两棵八叉树同时往下走。如果 A 当前节点全黑(实心),结果就是黑,不用看 B 的子树。这种”早停”让布尔运算的复杂度从 O(N³) 降到 O(表面积) -
空间索引天然就是路径:从根到任意叶子的路径(每步 0-7 选一个子方块)就是一个 3 比特/层 的莫顿码(Z-order curve)。这意味着位置 = 路径,不需要额外索引结构
-
存储成本随”边界复杂度”而非”体积”涨:一个光滑球体的八叉树节点数大约是 O(R²)(正比于表面积),而不是 O(R³)(体积)。这就是它比裸 3D 数组高效的根本原因
一图看懂八叉树切分
想象一个 8×8×8 的立方体世界(512 个格子)。如果右上前那一小角有个”球”,其他全空:
- 第 0 层:根节点(覆盖 8×8×8)—— 灰色(既有空又有实,需要切)
- 第 1 层:8 个 4×4×4 的子节点 —— 7 个白(全空,叶子,停),1 个灰(继续切)
- 第 2 层:那个灰节点切成 8 个 2×2×2 —— 大部分白,少数灰
- 第 3 层:继续往下,直到每个叶子都”全黑”或”全白”
总节点数远小于 512——空气区一进去就被一个白叶子吃掉。这就是”按需细分”的视觉意义。
实践案例
案例 1:Minecraft 区块的本质
Minecraft 把世界切成 16×16×384 的区块(chunk)。看似是数组,实际现代版本里:
- 每个 16×16×16 的子块(section)如果全是空气 → 不分配数组,存一个空指针
- 如果全是同种方块(比如石头)→ 存一个值
- 否则才展开成 4096 个槽位
这是一棵深度 4 的隐式八叉树。你挖一个洞 = 把”叶子节点”分裂成 8 个孩子。
案例 2:碰撞检测的剪枝
物理引擎要判断 1000 个物体之间是否两两碰撞 → 朴素 O(N²) = 50 万次检查。 用八叉树:把所有物体塞进树,只检查同一个子方块(或相邻子方块)里的物体对。 1000 个物体均匀分布时,每个子方块大概 10 个物体 → 100 个方块 × C(10,2) = 4500 次检查。100 倍提速。
案例 3:GPU 上的稀疏体素八叉树
NVIDIA 在 2010 后用 SVO 做实时全局光照:
- 把场景烘焙成一棵八叉树存在显存里
- 着色器对每个像素发一根光线,沿八叉树往下走(ray-march)
- 走到第一个”非空叶子”就停,读颜色
这条 GPU 八叉树遍历代码,思想 100% 来自 Meagher 1982,只是搬到了并行硬件上。
案例 4:CSG 实体建模的求值
CAD 软件让你写:球 ∪ 立方体 - 圆柱。怎么算结果?把每个基础体素化成八叉树,再按论文的布尔规则两两合并:
- 并:两节点一黑就是黑,两白才是白,否则继续递归
- 交:两节点一白就是白,两黑才是黑
- 差:A 减 B,B 黑就是白,B 白则保 A
整个 CSG 表达式的求值就是几次八叉树两两遍历。1980 年代的实体建模工业全部跑在这套算法上。
算法骨架(伪代码视角)
最核心的”切分构造”是这样:
build(volume): if volume 全黑: return Leaf(BLACK) if volume 全白: return Leaf(WHITE) if 已到最大深度: return Leaf(主导色) children = [] for i in 0..7: sub = volume 的第 i 个 2×2×2 子方块 children.append(build(sub)) return Node(children)布尔并操作几乎一样优雅:
union(A, B): if A 全黑 or B 全黑: return Leaf(BLACK) if A 全白: return B if B 全白: return A return Node([union(A.child(i), B.child(i)) for i in 0..7])整篇论文的算法本质都是”递归 + 两个早停条件 + 8 路展开”。一旦看穿这个模板,体绘制、变换、点查询全是变体。
踩过的坑
-
不是所有 3D 数据都适合八叉树:如果你的数据”到处都不一样”(高频噪声、湍流、毛发),八叉树会一直分到最深,比裸数组还费——多了所有内部节点的指针开销。规则:有大片同质区才选八叉树
-
指针八叉树缓存不友好:每跳一层都要解一次指针,CPU 缓存命中率低。现代实现用线性八叉树(Linear Octree,把节点按莫顿码排进数组)或完全展开的紧凑表示(每节点 8 字节存 8 个孩子的相对偏移)
-
平衡 vs 自由:论文原版的八叉树是”自由切”——某层可能左半边切到第 8 层,右半边只切到第 3 层。但有些算法(比如 Marching Cubes 跨层接缝)要求相邻叶子深度差不超过 1,这叫”受限八叉树”(Restricted Octree),需要额外的修复步骤
-
2D 类比不能完全照搬:很多人先学四叉树(Quadtree,2D 版本,4 个孩子),以为 octree 就是它的 3D 推广。基本对,但 3D 的常数因子(8 vs 4)让”自由分裂”的退化情况更糟,存储需要更小心
适用 vs 不适用场景
适用:
- 体素世界 / 体绘制 / 医学 CT 数据(大片同质组织)
- 点云 / LiDAR(点稀疏分布在 3D 空间)
- 静态场景的碰撞检测、可见性剔除
- 基于网格的物理模拟(自适应网格细化 AMR)
不适用:
- 高频细节均匀分布(每个体素都不同)→ 用裸数组或 BVH
- 大量动态物体(每帧都在动)→ 重建八叉树成本高,用 Bounding Volume Hierarchy
- 表面已用网格(mesh)表达 → 用 BVH 或 KD-tree
历史小故事(可跳过)
- 1980 年:Donald Meagher 在伦斯勒理工学院(RPI)写技术报告,提出八叉树编码
- 1982 年:正式期刊版本《Geometric Modeling Using Octree Encoding》发表在 CGIP(Computer Graphics and Image Processing)
- 同年 Jackins-Tanimoto 也发表了类似想法,但 Meagher 的版本最系统(包括 boolean、变换、显示)
- 1980s:CAD/CAM 行业大量采用,做实体建模(CSG 树的求值底座)
- 1990s:游戏引擎(Quake 系列的 BSP 是表亲,八叉树用于体素和遮挡剔除)
- 2010s:NVIDIA SVO、Minecraft 全民化、点云处理(PCL 库默认数据结构)
40 多年过去,八叉树仍是 3D 计算最常被复用的”积木”之一。
学到什么
- 递归 + 早停 = 自适应——只在数据真的不均匀时才花存储,是计算机科学最朴素的省钱思想之一
- 空间到比特路径的映射:3 比特/层的莫顿码让”位置”和”内存地址”统一,是后续很多空间索引(R-tree、Geohash)的源头之一
- 数据结构跟数据特性强绑定:同质区多 → 八叉树赢;噪声高 → 裸数组赢。没有”通用最好”的结构
- 40 年前的论文今天仍是工业基石:图形学的很多核心数据结构(八叉树、KD 树、BVH)都诞生在 1975-1985 这十年
延伸阅读
- 论文原文:Meagher 1982 - Geometric Modeling Using Octree Encoding
- 视频讲解:Sebastian Lague — Coding Adventure: Quadtrees(先看 2D 版本最直观)
- 实战代码:PCL Octree 教程(点云库的八叉树示例)
- 进阶:Cyril Crassin — GigaVoxels(2009) — 把 SVO 推到 GPU 实时光追
关联
- bentley-1975-kdtree —— KD 树是另一种空间分割(按超平面切),八叉树是按固定立方体切
- goral-1984-radiosity —— radiosity 用八叉树加速 form factor 求值
- 3d-gaussian-splatting —— 现代神经渲染也借鉴了空间分层加速思想
- loop-1987-subdivision —— 同期的几何递归思想,但用在曲面而不是空间
反向链接
- 3d-gaussian-splatting —— 3D Gaussian Splatting — 用一堆 3D 模糊光斑重建场景
- catmull-1974-zbuffer —— Catmull 1974 Z-buffer — 用一张深度图解决谁挡谁的问题
- curless-levoy-1996-tsdf —— Curless-Levoy TSDF — 把多次扫描融成一个干净的 3D 模型
- goral-1984-radiosity —— Goral 1984 Radiosity — 把建筑工程的辐射热传导算法搬进图形学
- loop-1987-subdivision —— Loop 1987 — 三角形网格的递归光滑细分