如何使用重力在 Android Sceneform 级别中定向 GLTF

Sam*_*Sam 5 android orientation kotlin arcore sceneform

我一直在努力将垂直放置的 3d 模型 GLB 格式正确放置在垂直表面上。

澄清一下,我不是指识别垂直表面的困难,这本身就是一个完全不同的问题。

删除设置的常见样板以最小化这篇文章。

我正在使用一个扩展 ARFragment 的片段。

class SceneFormARFragment: ArFragment() {
Run Code Online (Sandbox Code Playgroud)

当然,我已经提供了一些调整的配置。

override fun getSessionConfiguration(session: Session?): Config {
    val config = super.getSessionConfiguration(session)
    // By default we are not tracking and tracking is driven by startTracking()
    config.planeFindingMode = Config.PlaneFindingMode.DISABLED
    config.focusMode = Config.FocusMode.AUTO
    return config
}
Run Code Online (Sandbox Code Playgroud)

为了开始和停止我的 AR 体验,我在片段中编写了几个方法,如下所示。

private fun startTracking() = viewScope.launchWhenResumed {
    try {
        arSceneView.session?.apply {
            val changedConfig = config
            changedConfig.planeFindingMode = Config.PlaneFindingMode.HORIZONTAL_AND_VERTICAL
            configure(changedConfig)
        }

        logv("startTracking")
        planeDiscoveryController.show()
        arSceneView.planeRenderer.isVisible = true
        arSceneView.cameraStreamRenderPriority = 7
    } catch (ex: Exception) {
        loge("error starting ar session: ${ex.message}")
    }
}
private fun stopTracking() = viewScope.launchWhenResumed {
    try {
        arSceneView.session?.apply {
            val changedConfig = config
            changedConfig.planeFindingMode = Config.PlaneFindingMode.DISABLED
            configure(changedConfig)
        }

        logv("stopTracking")
        planeDiscoveryController.hide()
        arSceneView.planeRenderer.isVisible = false
        arSceneView.cameraStreamRenderPriority = 0
    } catch (ex: Exception) {
        loge("error stopping ar session: ${ex.message}")
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您想知道“启动和停止”AR 体验的原因是为了最大化此覆盖屏幕上繁重的其他 UX 交互的 GPU 周期,因此我们根据其他事物的当前实时数据状态等待启动或停止正在发生的事情。

好的继续。

让我们回顾一下 HitResult 处理:在这个方法中,我做了一些事情:

  1. 从云端加载两种不同的 TV 3d 模型(壁挂式和立式安装)
  2. 如果他们点击了新区域,我会删除所有活动模型
  3. 从命中结果创建一个锚节点并为其分配一个名称以便稍后将其删除
  4. 向它添加一个 TVTransformableNode 并为其分配一个名称以便稍后检索和操作它
  5. 确定水平支架安装3D模型电视的外观方向并将anchorNode的worldRotation设置为新的lookRotation。(注意*,我觉得应该将旋转应用于 TVNode,但无论出于何种原因,它似乎只有在我将其应用于 AnchorNode 时才有效。)此摄像机位置数学似乎也有助于垂直壁挂式电视面向外和正确锚定。(我已经查看了 GLB 模型,我知道它们从墙壁模型的背面和地板模型的底部正确固定)
  6. 然后我将节点的平面移动限制为它自己各自的平面类型,这样地板模型就不会向上滑动到墙壁上,并且墙壁模型也不会向下滑动到地板上。

就是这样。水平放置效果很好,但垂直放置总是随机的。

OnTapArPlane 代码如下:

 private fun onARSurfaceTapped() {
    setOnTapArPlaneListener { hitResult, plane, _ ->
        var isHorizontal = false
        val renderable = when (plane.type) {
            Plane.Type.HORIZONTAL_UPWARD_FACING -> {
                isHorizontal = true
                standmountTVRenderable
            }
            Plane.Type.VERTICAL                 -> wallmountTVRenderable
            else                                -> {
                activity?.toast("Do you want it to fall on your head really?")
                return@setOnTapArPlaneListener
            }
        }

        lastSelectedPlaneOrientation = plane.type
        removeActive3DTVModel()

        val anchorNode = AnchorNode(hitResult.createAnchor())
        anchorNode.name = TV_ANCHOR_NAME
        anchorNode.setParent(arSceneView.scene)

        val tvNode = TransformableNode(this.transformationSystem)
        tvNode.scaleController.isEnabled = false
        tvNode.setParent(anchorNode)
        tvNode.name = TV_NODE_NAME
        tvNode.select()

        // Set orientation towards camera
        // Ref: https://github.com/google-ar/sceneform-android-sdk/issues/379
        val cameraPosition = arSceneView.scene.camera.worldPosition
        val tvPosition = anchorNode.worldPosition
        val direction = Vector3.subtract(cameraPosition, tvPosition)

        if(isHorizontal) {
            tvNode.translationController.allowedPlaneTypes.clear()
            tvNode.translationController.allowedPlaneTypes.add(Plane.Type.HORIZONTAL_UPWARD_FACING)
        } else {
            tvNode.translationController.allowedPlaneTypes.clear()
            tvNode.translationController.allowedPlaneTypes.add(Plane.Type.VERTICAL)
        }

        val lookRotation = Quaternion.lookRotation(direction, Vector3.up())
        anchorNode.worldRotation = lookRotation

        tvNode.renderable = renderable

        addVideoTo3DModel(renderable)
    }
}
Run Code Online (Sandbox Code Playgroud)

忽略 addvideoTo3dModel 调用,因为它工作正常,我将其注释掉只是为了确保它不起作用。

我尝试过的事情。

  1. 这里描述的那样不旋转的情况下提取平移很有趣,它确实使电视每次都与地板保持水平,但是电视总是安装在底座上,而不是中央背面。所以很糟糕。
  2. 我试过查看各种帖子并将 Unity 或 ARCore 的内容直接翻译成 Sceneform,但没有得到任何影响结果的东西。例子
  3. 我已经尝试从飞机和姿势中创建锚点,如本答案所示,但没有运气
  4. 我已查看此链接,但从未发现任何有用的信息
  5. 我已经跟踪了这个问题并尝试了线程中人们推荐的解决方案,但没有运气
  6. 我尝试的最后一件事,这有点尴尬,哈哈。我在 Stack Overflow 中打开了所有 256 个标有“SceneForm”的标签,并查看了其中的每一个,以找出任何有帮助的东西。

所以我已经用尽了互联网。我剩下的就是询问社区,当然也可以向 Android 的 SceneForm 团队发送帮助,我也会这样做。

我最好的猜测是我需要做 Quaternion.axisRotation(Vector3, Float),但我猜测或尝试过的一切都没有奏效。我假设我需要使用手机的 xyz 的 worldPostion 值设置 localRotation 可能有助于识别重力。我真的只是不知道了,哈哈。

我知道 Sceneform 很新,而且文档很糟糕,而且可能因为缺少内容或文档标题而根本不存在。开发人员一定真的不希望人们使用它,但我猜:(。

我要说的最后一件事是,除了旋转的垂直放置之外,在我当前的实现中,一切都运行良好。只是为了避免在此讨论中出现兔子踪迹,我没有任何其他问题。

哦,还有我注意到的最后一条线索。电视几乎似乎围绕垂直平面的中心旋转,根据我点击的位置,底部几乎似乎指向平面的任意中心,如果这有助于任何人弄清楚的话。

哦,是的,我知道 GLB 中缺少我的纹理,我错误地打包它们并打算稍后修复它。

附上截图。 在此处输入图片说明 在此处输入图片说明

Sam*_*Sam 4

好吧,我终于明白了。我花了一段时间和一些认真的尝试和错误来旋转每个节点、轴、角度和旋转,然后才最终将其放置好。所以我会分享我的结果,以防其他人也需要这个。

最终结果如下: 在此输入图像描述

当然,这对你如何握持手机以及它对周围环境的理解有一定的主观性,但在我所做的横向和纵向测试中,它现在总是非常接近水平,没有失败。

这就是我所学到的。

在anchorNode上设置worldRotation将有助于使用一点减法来保持3D模型面向相机视图。

val cameraPosition = arSceneView.scene.camera.worldPosition
val tvPosition = anchorNode.worldPosition
val direction = Vector3.subtract(cameraPosition, tvPosition)
val lookRotation = Quaternion.lookRotation(direction, Vector3.up())
anchorNode.worldRotation = lookRotation
Run Code Online (Sandbox Code Playgroud)

然而,这并没有解决垂直放置的方向问题。我发现如果我在外观旋转上进行 90 度的 X 旋转,它每次都会起作用。根据您的 3D 模型,它可能会有所不同,但我的锚点是中后部,所以我不确定它如何确定向上的方向。然而,我注意到每当我在 tvNode 上设置 worldRotation 时,它都会将电视置于水平位置,但会向前倾斜 90 度。经过各种轮换之后,我终于得到了答案。

val tvRotation = Quaternion.axisAngle(Vector3(1f, 0f, 0f), 90f)
tvNode.worldRotation = tvRotation
Run Code Online (Sandbox Code Playgroud)

这解决了我的问题。所以 onSurfaceTap 和放置的最终结果是这样的:

   setOnTapArPlaneListener { hitResult, plane, _ ->
                var isHorizontal = false
                val renderable = when (plane.type) {
                    Plane.Type.HORIZONTAL_UPWARD_FACING -> {
                        isHorizontal = true
                        standmountTVRenderable
                    }
                    Plane.Type.VERTICAL -> wallmountTVRenderable
                    else -> {
                        activity?.toast("Do you want it to fall on your head really?")
                        return@setOnTapArPlaneListener
                    }
                }

                lastSelectedPlaneOrientation = plane.type
                removeActive3DTVModel()

                val anchorNode = AnchorNode(hitResult.createAnchor())
                anchorNode.name = TV_ANCHOR_NAME
                anchorNode.setParent(arSceneView.scene)

                val tvNode = TransformableNode(this.transformationSystem)
                tvNode.scaleController.isEnabled = false //disable scaling
                tvNode.setParent(anchorNode)
                tvNode.name = TV_NODE_NAME
                tvNode.select()

                val cameraPosition = arSceneView.scene.camera.worldPosition
                val tvPosition = anchorNode.worldPosition
                val direction = Vector3.subtract(cameraPosition, tvPosition)

                //restrict moving node to active surface orientation
                if (isHorizontal) {
                    tvNode.translationController.allowedPlaneTypes.clear()
                    tvNode.translationController.allowedPlaneTypes.add(Plane.Type.HORIZONTAL_UPWARD_FACING)
                } else {
                    tvNode.translationController.allowedPlaneTypes.clear()
                    tvNode.translationController.allowedPlaneTypes.add(Plane.Type.VERTICAL)

                    //x 90 degree rotation to flat mount TV vertical with gravity
                    val tvRotation = Quaternion.axisAngle(Vector3(1f, 0f, 0f), 90f)
                    tvNode.worldRotation = tvRotation
                }

                //set anchor nodes world rotation to face the camera view and up
                val lookRotation = Quaternion.lookRotation(direction, Vector3.up())
                anchorNode.worldRotation = lookRotation

                tvNode.renderable = renderable
                viewModel.updateStateTo(AriaMainViewModel.ARFlowState.REPOSITIONING)
            }
Run Code Online (Sandbox Code Playgroud)

到目前为止,这已经在纵向和横向方面经过了相当彻底的测试,没有出现任何问题。我在使用 Sceneform 时仍然遇到其他问题,例如,即使存在有效表面,点也仅显示大约一半的时间,当然,如果墙上没有图片,当前的 SDK 无法在单色墙上进行垂直检测或者用来区分墙壁的东西。

另外,执行屏幕截图也不好,因为它不包含 3D 模型,因此需要自定义像素复制工作,而且我的屏幕截图有点慢,但至少它们可以工作,这不感谢 SDK。

所以他们还有很长的路要走,用他们的产品开辟道路是令人沮丧的,而且缺乏文档,而且肯定缺乏对客户服务以及 GitHub 记录的问题的响应,但嘿,至少我明白了,我希望这会有所帮助其他人。

快乐编码!