Sceneform 概览笔记

介绍

利用 Sceneform,可以轻松地在 AR 和非 AR 应用中渲染逼真的 3D 场景,主要包含内容如下:

  • 一个高级场景图 API
  • 逼真的 基于物理的渲染器 ,由 Filament 提供
  • 一个 Android Studio 插件 ,用于导入、查看和构建 3D asset
  • 谷歌官方文档接下来把 Sceneform 和 ARCore 在项目中的配置,又讲了一遍,pass。

    开始使用 Sceneform 和创建场景图的最简单方法是使用 ArFragment ,在layout中添加这个组件后即可在逻辑代码中使用。

    创建可渲染对象

    Renderable 是一个 3D 模型,可置于场景中的任何位置,由网格、材料和纹理组成。

    标准 Android ViewRenderable ,在 3D 场景中被渲染为平面的 2D 卡片,同时可以 通过触摸与它们交互
  • Sceneform 提供了多种工具和插件,用于将 3D asset 文件(OBJ、FBX、glTF)转换成 Sceneform 二进制 asset (SFB),此 asset 随后可以构建到 ModelRenderable 中。
  • 基本形状和材料 ,可通过编程方式结合,以便在运行时创建更复杂的物体。

    以上3种分别对应的是:1、普通2D效果(可导入图片和组件混合);2、导入文件实现的3D立体效果;3、代码实现的3D效果(如一个大圆球)。

    以第一种为例,先在 res > layout 中创建一个布局文件,然后构建 ViewRenderable 对象,代码示例如下:

    <?xml version="1.0" encoding="utf-8"?>
       Licensed under the Apache License, Version 2.0 (the "License");
       you may not use this file except in compliance with the License.
       You may obtain a copy of the License at
          http://www.apache.org/licenses/LICENSE-2.0
       Unless required by applicable law or agreed to in writing, software
       distributed under the License is distributed on an "AS IS" BASIS,
       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       See the License for the specific language governing permissions and
       limitations under the License.
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/testview"
        android:layout_width="120dp"
        android:layout_height="wrap_content"
        android:background="@drawable/ic_launcher"
        android:orientation="vertical"
        android:padding="4dp"
        android:clipChildren="false"
        android:clipToPadding="false" >
        <TextView
            android:id="@+id/orbitHeader"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Orbit Speed:"
            android:textAlignment="center" />
        <SeekBar
            android:id="@+id/orbitSpeedBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="4dp" />
        <TextView
            android:id="@+id/rotationHeader"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Rotation Speed:"
            android:textAlignment="center"
            android:paddingTop="2dp" />
        <SeekBar
            android:id="@+id/rotationSpeedBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="4dp" />
    </LinearLayout>
    
    // 需要引入这个类
    import com.google.ar.sceneform.rendering.ViewRenderable;
    // 后续是拿 testViewRenderable 放到场景中去
    private ViewRenderable testViewRenderable;
    ViewRenderable.builder()
        .setView(this, R.layout.test_view)
        .build()
        .thenAccept(renderable -> controlViewRenderable = renderable);
    

    可渲染对象创建完后,就是要把它放入场景中。

    构建场景并与之互动

    在不使用 AR 的情况下渲染场景,可以利用 SceneView 类渲染 3D 场景,无需使用设备的摄像头或 AR 会话。这在没有 AR 功能的应用中预览 3D 对象,或在不支持 AR 的设备上提供替代功能时十分有用。

    当用户触摸屏幕时,Sceneform 会将触摸事件传递至连接到节点与场景的事件处理程序和侦听器。不过可以直接利用ArFragment,这个已经封装了多种触摸事件的处理,ArFragment 已添加对点按(选择)、拖动(移动)、双指张合(缩放)和倾斜(旋转)手势的支持。

    创建自定义节点来构建画面,个人感觉这跟浏览器的DOM节点很像,前面创建的可渲染对象就是在这里使用的。节点更新可以使用动画来呈现效果,还可以设置一个灯光的位置,达到物体有阴影的效果。

        // 场景更新时执行的方法
        scene.addOnUpdateListener(
            (FrameTime frameTime) -> {
              if (faceRegionsRenderable == null || faceMeshTexture == null) {
                return;
              Collection<AugmentedFace> faceList =
                  sceneView.getSession().getAllTrackables(AugmentedFace.class);
              // Make new AugmentedFaceNodes for any new faces.
              for (AugmentedFace face : faceList) {
                if (!faceNodeMap.containsKey(face)) {
                  AugmentedFaceNode faceNode = new AugmentedFaceNode(face);
                  faceNode.setParent(scene);
                  faceNode.setFaceRegionsRenderable(faceRegionsRenderable);
                  faceNode.setFaceMeshTexture(faceMeshTexture);
                  // controlNode 是我增加的一个节点
                  Node controlNode = new Node();
                  // 放入可渲染对象
                  controlNode.setRenderable(controlViewRenderable);
                  // 缩放节点大小
                  controlNode.setLocalScale(new Vector3(0.25f, 0.25f, 0.25f));
                  // 设置节点位置
                  controlNode.setLocalPosition(new Vector3(0.0f, 0.0f, 0.0f));
                  // 挂在faceNode下
                  faceNode.addChild(controlNode);
                  faceNodeMap.put(face, faceNode);
              // Remove any AugmentedFaceNodes associated with an AugmentedFace that stopped tracking.
              Iterator<Map.Entry<AugmentedFace, AugmentedFaceNode>> iter =
                  faceNodeMap.entrySet().iterator();
              while (iter.hasNext()) {
                Map.Entry<AugmentedFace, AugmentedFaceNode> entry = iter.next();
                AugmentedFace face = entry.getKey();
                if (face.getTrackingState() == TrackingState.STOPPED) {
                  AugmentedFaceNode faceNode = entry.getValue();
                  faceNode.setParent(null);
                  iter.remove();
    

    3D空间如何定位Node

    上面讲到把可渲染对象放到 Node 里,然后把 Node 挂载在场景中就可以呈现视图效果。那在现实场景中如何定位一个 Node 呢?分析目前的示例代码,发现做法基本都是在 fragment 更新的时候做的处理:1、监听当前 fragment 在检测平面的时候;2、监听当前 fragment 更新。

    arFragment.setOnTapArPlaneListener(this::onPlaneTap); private void onPlaneTap(HitResult hitResult, Plane unusedPlane, MotionEvent unusedMotionEvent) { if (andyRenderable == null || hatRenderable == null) { return; // Create the Anchor. Anchor anchor = hitResult.createAnchor(); // .... arFragment.getArSceneView().getScene().addOnUpdateListener(this::onFrameUpdate); private void onFrameUpdate(FrameTime unusedframeTime) { Frame frame = arFragment.getArSceneView().getArFrame(); if (frame == null) { return; if (frame.getCamera().getTrackingState() != TrackingState.TRACKING) { return; float[] position = { 0, 0, -1f }; float[] rotation = { 0, 0, 0, 0.1f }; Session session = arFragment.getArSceneView().getSession(); Anchor myAnchor = session.createAnchor(new Pose(position, rotation)); anchorNode = new AnchorNode(myAnchor);