深度解析Holograms 230: Spatial mapping 空间映射

Hololens官方给出的Tutorial不算多,其中内容最丰富的要数Holograms 230: Spatial mapping 空间映射这一篇了,主要讲的是空间的概念与操作,封装在HoloToolkit里面,这里逐步解析一下。

首先是Scanning扫描,这个跟前面Hologram101差不多就是打开SpatialMapping,让Hololens扫描空间,不过这里提供了一种途径让你把扫描的结果存下来,并且可以在Unity打开,这个还是挺棒的,比如自己的房间除了自己在3ds max建模,还可以用这种扫描的方式获得3d模型,当然这个的精度一般。因为这个模型是用三角形为单位来描述的,描述曲面就没什么精度可言了,其次就是可调节每立方米的三角形数量来提高精度,不过这个会影响性能。这个性能是由Hololen的处理器即并不快的atom来决定的,而不是你强大的pc的处理器。保存下来的SRMesh.obj可以在unity打开,可以在unity模拟hololens现场获得房间的结果。联想起当初玩Kinect的时候也可以3D扫描,当然精度也是不咋地。

后面开始就一环扣一环了。先说一下总的步骤,先是扫描获得Mesh数据,然后在Mesh数据里面提取平面信息,分析平面信息用SurfaceNormal来判断平面类型是地面、天花、桌子,还是墙。然后用以cube来表示的定制化平面分别创建这些面,然后在面上面放置物体。如果还没晕,那我们继续。

第一步,在PlaySpaceManager里面,我们可以看到一下参数,分别讲解一下。

limitScanningByTime:布尔类型标识是否限定时间。

scanTime:如果限定那是多少秒。

defaultMaterial:扫描的时候用什么来mesh空间。

secondaryMaterial:扫描完了换种显示方式来说明扫描结束了。

minimumFloors:最少的地面数。

minimumWalls:最少的墙面数。

初始化一下,用默认材料显示空间mesh,添加扫描完成事件。然后每1帧都检查是否限定了时间和扫描是否结束,如果扫描时间还没到什么都不做,继续扫描就是了,否则停止扫描,调用CreatePlanes()生成平面并标记结束。生成平面就要用到第二步的东西了,一会再详细解释。

刚说了有个扫描完成事件,那么在生成平面并标记结束后就会触发这个SurfaceMeshesToPlanes_MakePlanesComplete。这一步调用第二的GetActivePlanes()函数来生成horizontal和vertical两个list来分别存放水平和垂直的面(生成的不是现实的)。如果面的数量不足,也就是说1面墙和1个地面都没有,那就一切重新开始。如果面的数量足够,那么先删掉一些定点和三角形,提高性能嘛。并且替换mesh材料来指示完成。然后就开始在生成的平面(注:不是现实的墙或地板,而是我们用cube来表示的墙或地板的平面)的墙上上挂相框,地上放投影机,这个第3步会解释一下。


第二步,在SurfaceMeshesToPlanes里面,先看参数

ActivePlanes:存储生成的平面,不是现实的墙或地面。

SurfacePlanePrefab:是我们用来表示面的物体,这里没有用plane而是用的cube,原因应该是cube的单位是1米可以用来匹配真实环境。这个cube设置厚度为1cm也就是像个面而不是一个方块。有个参数是UpNormalThreshold,用来表示可接受法线的临界值,从注释看越接近1越精确到标准,这里设置的是0.9。这是个神奇的参数,可以用来判断面是水平的还是垂直的,垂直的必定是墙,水平的还可以判断是天花还是地面还是桌子(深入研究表明,如果是墙,数值应该是0,如果是地面就是朝上的,数值应该是1,如果是天花朝下的,数值应该是-1,误差在0.1范围内可接受)。要做到这点还要设置天花buffer和地面buffer,也就是地面以上多高以上才算桌子。最后还可以为每一种类型的面设置材料。实际操作的时候可以根据Mesh的平面信息赋值给这个面,来初始化位置大小旋转等。具体参考SurfacePlane.cs文件,不太难就不解释了。

MinArea:最小扫描面积。

drawPlanesMask:将要画出来的平面的集合,不在里面的不画。

destroyPlanesMask:不画的平面集合,需要销毁来降低内存使用量。

FloorYPosition:地面Y值,通过不断比较低于用户头部的平面,找出面积最大的平面表示地面。

CeilingYPosition:同上,表示天花。

delegate EventHandler:生成平面完成的触发事件。

MakePlanesComplete:同上,这是句柄

planesParent:相当于create empty,用来存放生成的所有面

snapToGravityThreshold:用来对齐

makingPlanes:布尔类型用来标识生成平面结束了没有

FrameTime:每帧内允许代码的处理时间。后面会看到不少yield return null;即暂停一下到下一帧继续执行,否则持续占用cpu的话下一帧到来就会很卡。如果按25帧每秒计算,1帧的时间有40毫秒。假设生成平面1共有1000毫秒的工作量,这里设置每帧只工作8毫秒来处理,那么就有时间处理别的工作。事实上永远是多于8毫秒的时候才停止。


初始化,设置还没开始生成平面,实例化ActivePlanes这个list,实例化planesParent并初始化。第一步里面生成平面的函数CreatePlanes()调用了这一步的MakePlanes(),而MakePlanes使用Coroutine协同处理这个比较耗时间的生成过程,实际执行函数是MakePlanesRoutine()。

MakePlanesRoutine函数会先销毁ActivePlanes里面的所有平面并清空list,这是因为在第一步如果面数不足要重新开始的话,本类里面的start()不会重新执行,就要先清空一下。这个函数有多个yield return null;参考上面的FrameTime解释就不多说了。首先创建meshData来存放mesh数据,并创建filters获取mesh数据。对于每一个不为空的filter,固定平面法线并添加进meshData里面。然后使用PlaneFinding.FindPlanes()函数从meshData里面找出所有大于最小面积的平面(此函数使用指针,不要深究了)。注意找出来的平面类型为BoundedPlane,也就是这个平面还包含边界即大小。后面有个获取用户设置的upNormalThreshold的一步。然后开始从平面里面找出地面和天花,boundedPlane.Plane.normal.y >= upNormalThreshold对平面做出限制,比较boundedPlane.Bounds.Center.y < 0即低于人头部位置,逐个比较所有平面留下面积最大的记录下boundedPlane.Bounds.Center.y作为FloorYPosition。天花也是同样步骤。

到前面的步骤都是真实空间的东西,后面开始创建虚拟的面来取代真实的面(说取代也不合适但比附加好理解)。对于每一个平面,首先用我们预设的SurfacePlanePrefab初始化一个GameObject叫destPlane,并加进planesParent作为子物体(设置transform就可以了)。然后surfacePlane.Plane = boundedPlane;这句把平面的信息赋值给这个destPlane的surfacePlane.Plane,这个Plane成员不在unity的inspector里面设置。这个赋值会执行SurfacePlane.cs里面的UpdateSurfacePlane()函数,包含设置位置大小旋转,根据SurfaceNormal.y判断平面类型属于地面、天花、桌子、墙,还是未知。并根据每种类型赋值材料。然后设置哪些类型可见,也就是未知的不可见,还把未知的销毁。可见的根据现实的层级设置层级,并且添加到ActivePlanes里面。所有平面都执行过后,设置makingPlanes为假也就是生成结束。


第三步,在SpaceCollectionManager,在生成的平面里面放hologram,参数只有1个spaceObjectPrefabs在unity拖放要放置的物体list。

首先建horizontalObjects和verticalObjects两个list用来存放要横着放和竖着放的物体list。然后把spaceObjectPrefabs里面的每个物体分配到前面2个list里面,然后调用CreateSpaceObjects()创建物体。

在CreateSpaceObjects里面,先创建UsedPlanes来存放那个平面已经放了东西,就不会再放了。然后有一段surfaces.Sort来把所有平面按距离人的远近排序,拍好后按由近到远放置物体。然后foreach遍历每一个横着放或竖着放的list里面的物体,用FindNearestPlane()找到最近的合适平面。合适包括没有使用过,平面面积比物体面积大,的最前面那个也就是最近的那个平面。没找到的话就悬浮在用户右前方。找到的话先设置平面用了,根据平面设置物体的位置和旋转(这个可以学一下)。最后根据位置和旋转把物体实例化一份加到SpaceCollection里面。


后面还有设置物体可以移动放置到哪里,和物体在墙里被遮挡的时候透视显示。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章