我以前已经实现了行进立方体/四面体以渲染IsoSurface。它可以正常工作(YouTube),但是性能却很糟糕,因为我从来没有绕过根据视距实现可变的“细节级别”(甚至删除了旧的,遥远的块)。
我决定再次尝试一下,这次要正确地做。我首先创建了一个OctreeNode,它在Build()
被调用时的工作方式如下。
- 如果块太小而无法构建,请立即返回。
- 算出表面是否穿过该块的体积。
- 如果是这样,则决定我们是否要提高LOD(因为相机已关闭)
- 如果是这样,则生成8个子代并对其调用相同的过程
- 如果没有,请使用当前节点的尺寸构建网格
一些伪代码:
OctNode Build() {
if(this.ChunkSize < minChunkSize) {
return null;
}
densityRange = densitySource¹.GetDensityRange(this.bounds);
if(densityRange.min < surface < densityRange.max) {
if(loDProvider.DesiredLod(bounds)² > currentLoD) {
for(i 1 to 8) {
if(children[i] == null) {
children[i] = new OctNode(...)
}
children[i] = children[i].Build();
}
} else {
BuildMesh();
}
return this;
}
}
¹不仅可以返回某个点的密度,还可以确定给定体积的可能密度范围。
²LoD提供程序带有一个边界框,并根据摄像机的位置/视锥,用户设置等返回最大所需的LoD。
所以...这一切都很好。使用简单的球体作为“密度”源,并显示所有节点:
只是叶子:
但是,有两个问题:
- 我必须定义初始边界体积(并且它越大,我需要做的处理越多)
- 在树的根部,我不知道叶子的深度,所以我的LoD编号从最低质量(根)开始,并随着块的变小而增加。因为LoD现在相对于初始体积,所以当我想以特定的大小/质量进行操作时,它的用处不大。
我想到了几种选择,但两种选择似乎都有缺陷:
- 维护八位字节的集合,并根据距离添加/删除。无法看到如何很好地进行网格划分¹,此外,我还需要一个已知的空节点列表,特别是如果我想要任意3D曲面(以避免重复重新计算空体积)时
- 将父节点添加到当前根,然后为原始节点添加七个同级。这可以正常工作,并且可以按需进行,但是随着玩家在景观中移动,明智地收缩下来似乎很复杂。这也将使LoD编号的意义降低。
¹[为澄清下面的问题,目前,如果树中2个物理上相邻的节点位于不同的LOD,则我有一些代码可以强制转换顶点,以便在生成网格时不会出现接缝。我可以通过了解多个周围节点的密度来做到这一点。在并排有2个独立八叉树的情况下,我没有简单的方法来检索此信息,从而导致接缝。
解决此问题的最佳方法是什么?