使用体素场存储场景数据

使用两个数组来表示体素场

每个3D场景都可以进行体素化得到体素场,对于一个体素场,可以利用两个数组来存储,数组spanArr用于存储体素的上下表面高度,indexArr用于标识每一列体素在spanArr里的下标位置,举个例子,体素场如下图所示,每一个格子代表空间上的一列体素,体素列下方的第一个体素的下表面高度默认均为0,所以不进行记录,下图一个格子里数据为6,8,10,说明该列有两个体素,第一个体素下表面高度为0,上表面高度为6,第二个体素下表面高度为8,上表面高度为10:
在这里插入图片描述

假设图中左下角的体素对应的列是[0, 0],有:
spanArr = {3,5,7,9,4,3,8,2,2,6,8,10,10,2,2,3,4,5,1,3,9,6,7,5 }
indexArr = {1,4,5,6,7,8,9,12,13,14,15,18,21,22,23,24}

举个例子,根据上面的两个数组,获取第2行第3列的体素数据,这里的体素场的宽度w与高度h都是4:
先得到该列再indexArr中的索引,为(2- 1)* w + (3-1) = 4 + 2 = 6,而(indexArr[6] - indexArr[5] + 1)/2则为该列的体素个数,为1,再去读取spanArr[indexArr[5]] = spanArr[8] = 2
所以得到该列一共有一个体素,下表面数据为0,上表面数据为2

如何简化体素场占用的内存
上述的存储方式,基本就是两个很大的数组,比如下图所示的情况中,地图中间有四个完全相同的体素列,均为一个体素,上表面高度为2,下表面高度为0:

在这里插入图片描述

那么有没有什么方式去把它简化掉,首先尝试一种方法,如果一个体素的右上角,上方和右方的三个体素均为一样的,那么只存一个列的span数据到spanArr里,那么spanArr就变成了:
spanArr = {3,5,7,9,4,3,8,2,6,8,10,10,3,4,5,1,3,9,6,7,5 }

问题在于,如果spanArr乱掉了,如何保证indexArr仍能根据索引找到正确的特定列的体素数据,indexArr肯定是要变的,因为spanArr里的很多重复数据被删掉了,其长度也缩短了
此时的spanArr = { (3),(5,7,9),(4),(3),(8),2,(6,8,10),(10),(3,4,5),(1,3,9),(6),(7),(5) }
为了保证我们还能按照原来的规律来寻找体素列的数据,需要对index进行索引
在这里插入图片描述
如上图所示,红色数字代表了IndexArr在每一列存储的数字,由于有三列的体素均没有存储位置,所以该列对应在spanArr的索引相较于上一列的体素索引没有任何变化,这也说明了,对于第x列体素,如果indexArr[x] == indexArr[x - 1],说明该体素的数据被折叠了,所以我们需要找到其源数据。

如何找到源数据呢,如下图所示,对于绿色格点来说,尽管知道他的数据是复制的,但是其源数据无法得知到底是这三个蓝色格点的哪一个
在这里插入图片描述
为了区别数据被复制的体素列,和正常的体素列,需要加以区分,这里可以在spanArr的第一个体素的上表面,也就是spanArr在该列的第一个体素的数据的首个bit位加上标记,若首个bit位为1,则说明该体素为折叠体素列,那么我们取体素的步骤就变成了:

  1. 根据体素列的x和y索引,算出在IndexArr里的索引k,k = x + y * w
  2. 若k == 0,则k肯定是源数据,否则找到indexArr[k]和indexArr[k - 1],若二者相等,则k为复制数据,否则为源数据,若是源数据则跳到step3,否则跳到step4
  3. 跟之前的方法一样,源数据的列体素个数为(index[k] - index[k - 1] + 1) / 2,其体素数据区间在spanArr[index[k-1]]到spanArr[index[k]]之间
  4. 若不为源数据,则需要找到其源数据,源数据可能在该位置的左边、下边或左下角,此时可以分别取三个位置的Index索引值,与其上一个index索引值对比,如果不相等,说明该列的数据为源数据,但是并不确定就是由此列体素复制得到的,如下图所示,读取红色圈圈位置的体素列数据时,三个绿色点的体素列都是源数据列,右上角的体素列在取的时候无法得知其原本的体素在哪一列,所以要需要去检查这三列的体素列的数据标识,哪一列体素的数据带有位标识,就取该列的体素数据
    在这里插入图片描述

确立了分辨数据的方法,就容易按照对应的规则去储存数据了,储存方法如下:

  • 仍然是两个数组spanArr和indexArr,indexArr的大小不变,地图能分为wh列,则indexArr有wh个元素
  • 对于四个高度完全一样的,且呈一个方块排列的四个体素列,不再在spanArr里存储四份数据,只存储一份

算法思路:

  • 将indexArr置0,用来标记每一列的体素是否为复制体素列,初始时,所有体素列均未被标记
  • 存储数据时,从小到大,逐列遍历体素,先判断体素是否被标记为复制体素,具体判断方法为,取该列对应的indexArr的值,若为0,说明未被标记
  • 若没有标记,则需要判断这一列的数据是以折叠的源数据存储,还是普通的源数据进行存储。首先判断这三个方向的体素列有没有被标记过,如果存在任何一个体素列被标记过则按照普通源数据进行存储。否则,再去比较具体的数据,若完全相同,则标记这三列体素为复制体素,然后存入本列体素的数据到spanArr中,本列第一个体素数据的第一个bit置为1,存入spanArr中,然后将右方、上方和右上方的index值置为-1,作为标记,表示这些是复制数据,若并不完全相同,则按照普通源数据的进行存储。
  • 若存在标记,则取其上一个的体素的值的index,在indexArr中对其进行赋值


后来的一点记录

一. 边缘体素的标记
前面提到了,作为一个SpanArr的数据,如果一列是源数据,那么其第一个ushort的高度数据的第一位为1,代表这是源数据,而对于每一个体素的上表面,如果该体素是边缘体素,那么把它的ushort的高度数据的第二位置为1,这样还剩下14位的高度值,范围在*2^14 - 1)*cellheight之间


二. 能否去掉源数据的标记
考虑过这个想法,因为已经有的方法就是,如果一列体素的数据id对应的IndexArr[id] == IndexArr[id-1],那么说明这一列体素的数据是抄袭的左边、下边或者左下边的数据的,那么能否单独通过这个判断源数据在哪呢?

答案是不行,因为IndexArr[id] == IndexArr[id-1]只能判断一个体素是不是抄袭的别人的数据,体素数据分为三种:

  • 抄袭别人的数据的体素,其IndexArr[id] == IndexArr[id-1],不会存储其SpanArr的数据
  • 普通体素,也就是完全没有被合并的体素
  • 源数据体素,这一列的体素是合并的,代表四个列

也就是说,没有了ushort高度数据的第一位标记,就无法区别前面说到的第二种和第三种的体素,如下图所示:
在这里插入图片描述
如果B是源数据,AC为普通数据,D为复制的数据,那么对于D这个位置的列,无法知道它的源数据来自于哪一列,因为ABC的IndexArr[id]!=IndexArr[id - 1]

穷举法去掉标记
不过我还是想到了一个办法,可以不用标记,因为如果D是复制的数据,那么只有以下三种情况:

  • D在源数据上方
  • D在源数据右方
  • D在源数据右上方

如下图所示:
在这里插入图片描述

以上三种情况下:

  • D在源数据上方,则B为源数据,DEF为复制数据
  • D在源数据右方,则C为源数据,GHD为复制数据
  • D在源数据右上方,则A为源数据,BCD为复制数据

如果不用标记的方法,就去判断这三种情况好了,哪一种情况下,三个方位的都为复制数据,就能找到对应的源数据



Unity导航网格高度精度不准确的校正方案

由于Unity导航网格的高度精度不准确,而Unity的heightmesh精度较低,所以这里做了相关的校正方案

比如说还是这个场景,体素化的数据如下图所示,体素的高度精度为0.1m,那么[0,0]列对应的体素高度为下表面0m,上表面0.3m,而[1,0]列对应的第一个体素下表面0m,上表面0.5m,第二个体素下表面0.7m,上表面0.9m
在这里插入图片描述
然后,再用Unity的SamplePosition的函数,把体素场的上表面的中心坐标输入进去,在对应的各个点进行采样,得到对应的高度数据,注意这里只记录体素上表面的高度差,因为下表面不会站人,不重要。

假设得到的数据如下图所示,这里的数据都是Unity的NavMesh上采样出来的点与体素场对应高度的差值:
在这里插入图片描述
那么可以规定,当高度差小于0.1m时,不予记录,同时,不单单只是记录一列,而是同时记录2*2个列的体素,如下图所示,可以看到左上角和右下角的区域里面的四列高度差都不大:
在这里插入图片描述

每2*2列算作一整个单元格,那么可以创建一个数组,这个数组存储的元素如下:
在这里插入图片描述

这里使用-1作为一个特殊的标记,-1代表这一块2*2的列区域内的高度值都相差不大,所以不记录任何数据,若值不为-1,说明对应的值有数据,具体的值,比如说0,代表着该列数据在体素数据中的索引。

所以这一个4*4的体素场最后的数据缩减为两个数组,一个index数组,一个short的spanArr数组,内容如下:

int index[4] = {
    
    0-1-14}// 这里的spanArr记录的不再是代表场景数据的体素场数据,而是相较于Unity
// 的SamplePosition的高度对应的矫正高度信息
// -21代表offset -0.21,也就是[0, 0]处体素对应的高度差
short spanArr[] = {
    
    -21301850-2030-10}            //精度为0.01,这里的值都会乘以0.01

另外,边界情况,比如说正好多两个而不是四个,这种情况会不会有问题,具体应用的时候还需要额外注意一下

猜你喜欢

转载自blog.csdn.net/alexhu2010q/article/details/108636687
今日推荐