如何在四叉树(Quadtree)中确保LOD间严格保持2:1的细分比例?
我最近在实现相机周围径向对称的四叉树LOD细分时,遇到了LOD间偶尔出现4:1跳变的问题,原本目标是严格保持2:1的细节渐变比例,这里分享一下我的问题排查和解决过程:
背景与初始思路
最开始要感谢TARN4T1ON的建议——用BoundingBox做距离检测帮我解决了之前的问题。为了实现径向对称的细节渐变,我采用了围绕相机的同心球壳+四叉树子节点AABB碰撞检测的方案:球壳半径按2^i递增(i为LOD层级),目的是让每个外层LOD的细节密度是内层的1/4(对应2:1的尺寸比例),通过球壳与四叉树的交集来控制细分程度。
但实际运行时,偶尔会出现LOD跳变到4:1的情况,显然是我的细分条件不够严谨。我原本写了_LODRadii函数生成一系列递增的球壳半径,但没用到;还尝试写_Lod0函数找到相机最近的球壳作为基准,想从内向外按2:1比例细分,但不知道怎么落地。
核心问题:原_Insert函数的缺陷
我的细分逻辑核心在_Insert函数,原本的逻辑是从最大的LOD层倒着遍历,当节点到相机的距离小于当前球壳半径,且节点尺寸≥半径时就细分节点:
_Insert(child, pos, lodRadii, numLayers, lod0) { const lodCenter = pos; const closestPoint = new THREE.Vector3(); child.newBounds.clampPoint(lodCenter, closestPoint); const squaredDistance = lodCenter.distanceToSquared(closestPoint); //squared distance from the camera to bounding box for(let i = numLayers; i >= 0; i--) { const lodRadius = lodRadii[i]; if(squaredDistance <= lodRadius ** 2 && child.size.x >= lodRadius ) { child.children = this._CreateChildren(child); for (let c of child.children) { this._Insert(c, pos, lodRadii, i, lod0); } break; } } }
这个逻辑的问题在于,没有严格绑定每个LOD层级的距离范围,导致节点细分的触发条件出现重叠或断层,从而产生非2:1的跳变。
解决方案:用区间式LOD桶替代单一半径
后来采纳了Iman Hedeshy的建议,改用区间式的LOD桶来定义每个层级的距离范围,确保每个层级的范围严格是前一个的2倍:
_LODBuckets(minRadius, numLayers){ const lodBuckets = []; lodBuckets.push({min: 0, max: minRadius}); //first entry: for lod between 0 and minRadius for(let i = 0; i < numLayers; i++){ lodBuckets.push({min: minRadius * 2 ** i, max: minRadius * 2 ** (i + 1)}); } return lodBuckets; }
每个LOD桶对应一个明确的距离区间:从0到最小半径是最细的LOD,后续每个桶的区间上限都是前一个的2倍,这样从根源上保证了LOD间的2:1比例关系。
效果与小问题修复
实现这个方案后,LOD的渐变效果立刻变得符合预期,非常优雅!不过一开始遇到了一个小问题:当最小LOD球壳与四叉树相交时,整个细分会消失,后来排查后修复了这个问题,现在运行正常。
备注:内容来源于stack exchange,提问作者Spiri

