首页 > 解决方案 > AR Android Sceneform SDK 仅在地板上展示模型

问题描述

我正在使用适用于 Android 版本的 Sceneform SDK

implementation 'com.google.ar.sceneform.ux:sceneform-ux:1.15.0'

我需要我的 3D 模型只显示在地板上。例如,我有一个 3D 笼子(简单的透明长方体),我需要将此 3D 模型放置在真实对象上。现在,如果我的真实对象有足够大的表面,模型将被放置在它的顶部而不是越过它,我需要避免行为。 在此处输入图像描述

这是这里的一些代码。

初始化 ArFragment 并在相机中心显示模型的逻辑。每当帧发生变化时,我都会在设备摄像头的中心进行 HitTest。

private fun initArFragment() {
    arFragment.arSceneView.scene.addOnUpdateListener {
        arFragment.arSceneView?.let { sceneView ->
            sceneView.arFrame?.let { frame ->
                if (frame.camera.trackingState == TrackingState.TRACKING) {
                    val hitTest =
                        frame.hitTest(sceneView.width / 2f, sceneView.height / 2f)
                    val hitTestIterator = hitTest.iterator()
                    if (hitTestIterator.hasNext()) {
                            val hitResult = hitTestIterator.next()
                            val anchor = hitResult.createAnchor()
                            if (anchorNode == null) {
                                anchorNode = AnchorNode()
                                anchorNode?.setParent(sceneView.scene)
                                transformableNode =
                                    DragTransformableNode(arFragment.transformationSystem)
                                transformableNode?.setParent(anchorNode)
                                boxNode = createBoxNode(.4f, .6f, .4f) // Creating cuboid 
                                boxNode?.setParent(transformableNode)
                            }
                            anchorNode?.anchor?.detach()
                            anchorNode?.anchor = anchor
                    }
                }
            }
        }
    }
}

我认为这是预期的行为,因为 HitTest 也会击中真实物体的表面。但不知道如何避免这种行为。有没有办法忽略真实物体并将 3D 模型始终放在地板上?

更新 我试图遵循@Mick 的建议。我正在尝试对所有 HitTestResult 进行分组。当 HitTest 完成后,我会列出所有可见平面的所有 HitResults。我按圆形 Y 轴对它们进行分组。 示例 {1.35 -> [Y 是 1.36776767,Y 是 1.35434343,Y 是 1.35999999,Y 是 1.37723278]} {1.40 -> [Y 是 1.4121212,Y 是 1.403232323,Y 是 1.44454545,Y 是 1.40001]}锚点我使用 FIRST HitResult 和从数组排序的键从 MIN 到 MAX 键在示例中它是 1.35。对于 Y 锚点,我得到一个 MIN 组元素的数组并得到它的平均值。

 val hitResultList = hitTestIterator.asSequence().toList().groupBy { round(it.hitPose.ty() * 20) / 20 }.minBy { it.key }?.value
 val hitResult = hitResultList?.first()!!
 val averageValueOfY = hitResultList?.map { it.hitPose.ty() }?.average()
 createModel(hitResult, averageValueOfY)

创建模型的方法

private fun createModel(newHitResult: HitResult, averageValueOfY: Double) {
    try {
        val newAnchorPose = newHitResult.createAnchor().pose
        anchorNode?.anchor?.detach()
        anchorNode?.anchor = arFragment.arSceneView.session?.createAnchor(Pose(floatArrayOf(newAnchorPose.tx(),
                averageValueOfY.toFloat(), newAnchorPose.tz()), floatArrayOf(0f, 0f, 0f, 0f)))
        isArBagModelRendered = true
        transformableNode?.select()
    } catch (exception: Exception) {
        Timber.d(exception)
    }
}

此代码更新有助于获得我试图实现的行为,我注意到有时我的 Y 锚点在地下,看起来好像在地板下检测到 MIN 平面 :( 我现在不知道如何解决这个问题.

标签: androidarcoresceneform3d-model

解决方案


实际上,我认为您的用例可能会面临两个不同的问题:

  • 正如您在问题中突出显示的那样,对象被放置在顶面上
  • 当模型实际放置在正确的位置时,遮挡,或不显示应该隐藏在对象后面的模型部分。

第一个问题的简单解决方案,以便您可以检查第二个问题,或者更准确的解决方法,可能是简单地让用户将对象放在真实对象的前面,即上面示例中的情况,然后移动它回来,直到它正是他们想要的地方。

如果您保持飞机突出显示,即显示检测到飞机的位置的网格线,用户可能更直观地“击中”地板而不是对象的顶部。

如果遮挡问题实际上是更严重的问题,这将允许您在您走得太远之前快速测试。

一个更复杂的解决方案是遍历平面并尝试比较每个平面中心的“姿势”,看看你是否能找到可靠的方法来确定哪个是地板 - 该方法是 Plane 类的一部分:

公共姿势 getCenterPose ()

返回检测到的平面中心的位姿。姿势变换后的 +Y 轴将法线指向平面外,+X 和 +Z 轴指向边界矩形的范围。

如果您确定地板始终是最大的平面,还有一些方法可以获取平面的宽度或深度:

公共浮动 getExtentX ()

返回沿以平面为中心的坐标空间的局部 X 轴测量的平面边界矩形的长度。

公共浮动 getExtentZ ()

返回沿以平面为中心的坐标系的局部 Z 轴测量的平面边界矩形的长度。

不幸的是,我认为没有任何现有的方便的帮助功能,例如“获取最低平面”或“获取最大平面”等。

请注意遮挡问题,有些框架和库确实提供了某些形式的基于软件的遮挡,即不需要设备具有额外的深度传感器,因此可能也值得探索一下。


推荐阅读