Safie Engineers' Blog!

Safieのエンジニアが書くブログです

AndroidでSceneViewを使って、3D/ARを表示する

こんにちは!Safie第2開発部のAndroidエンジニアのジェローム(@yujiro45)です。

Androidで3D/ARモデルを表示するのは難しそうに見え、技術的に大変そうですね。
この記事では、Sceneview-androidを使用して簡単に3DとARモデルを表示する方法を紹介します。

SceneViewとは

SceneViewGoogle FilamentARCoreを使用して3Dモデルを表示するライブラリです。
Thomas Gorisse(トーマス・ゴリス)と彼のチームによって開発されました。

  • Google Filament:Googleが開発している物理ベースのリアルタイムレンダリングエンジンです。
  • ARCore:Googleが開発したAndroid端末向けのARフレームワークです。

SceneViewは、以前のライブラリであるSceneformのKotlinバージョンであり、完全にKotlinで書き直されています。Sceneformはもはやメンテナンスされていません。
技術的には、SceneViewは3Dスペースで、カメラは中央に位置しており、座標はx=0、y=0、z=1です。該当部分のコード

導入

この記事では、KotlinとJetpack Composeを利用する導入方法について説明します。ただし、SceneViewはAndroidのxml layoutやFlutterReact Nativeでも利用可能です。

SceneViewは2つのビューを提供します。

  • ARSceneView : Google FilamentとARCoreの両方を使用して、3DおよびARを表示する目的で利用されます。
  • SceneView : Google Filamentを使用して3Dモデルを表示するために利用されます。

SceneViewを追加するには、必要に応じてGradleファイルに適切な依存関係を追加するだけです。

ARSceneView

dependencies {
    implementation("io.github.sceneview:arsceneview:X.X.X")
}

SceneView

dependencies {
    implementation("io.github.sceneview:sceneview:X.X.X")
}

💡 GradleファイルにARSceneViewの依存関係を追加すると、ARSceneViewとSceneViewの両方を使用できます。ただし、SceneViewの依存関係のみを追加した場合は、SceneViewのみを使用できます。

実装

SceneViewは現在、GLTFおよびGLBファイルをサポートしています。3Dモデルのファイルをres/rawフォルダに入れてください。3Dモデルを表示するには、ModelNodeを使用する必要があります。これにより、3Dモデルから3Dノードを作成することができます。

*glbとgltfファイルは以下のサイトで無料でダウンロード出来ます。

3Dファイルを追加したら、以下のコードを使用して表示できます。

ARSceneView

SceneviewexampleTheme {
    val engine = rememberEngine()
    val modelLoader = rememberModelLoader(engine)
    // 3Dモデルの読み込み
    val modelNode = ModelNode(
        modelInstance = modelLoader.createModelInstance(R.raw.android)
    ).apply {
        // 3Dモデルの初期設定
        scale = Scale(1 / 40f)
        position = Position(0f)
    }
    ARScene(
        modifier = Modifier.fillMaxSize(),
        engine = engine,
        modelLoader = modelLoader,
        childNodes = rememberNodes {
            // モデルノードの追加
            add(modelNode)
        }
    )
}

SceneView

SceneviewexampleTheme {
    val engine = rememberEngine()
    val modelLoader = rememberModelLoader(engine)
    // 3Dモデルの読み込み
    val modelNode = ModelNode(
        modelInstance = modelLoader.createModelInstance(R.raw.android)
    ).apply {
        // 3Dモデルの初期設定
        scale = Scale(1 / 40f)
        position = Position(0f)
    }
    Scene(
        modifier = Modifier.fillMaxSize(),
        engine = engine,
        modelLoader = modelLoader,
        childNodes = rememberNodes {
            // モデルノードの追加
            add(modelNode)
        }
    )
}

ビルドが通ったら、3Dモデルが表示されました! 🎉

ARSceneView SceneView

出典:Sketchfab, https://sketchfab.com/3d-models/android-7c30eda007684abbb78ea4b99d22fc2c (2024/10/07アクセス)

SceneViewにノードを追加するとき、ドキュメントによれば、ノードのデフォルトの位置は(x=0.0f, y=0.0f, z=0.0f)で、軸は下の画像のようになります。

カメラを別の位置に移動したい場合は、以下のように指定します。

SceneviewexampleTheme {
    //...
    Scene(
        //...
        cameraNode = rememberCameraNode(engine).apply {
            position = Position(z=5f)
        }
        //...
    )
}

アニメーション

ModelNodeのtransform()というメソッドを使用して、位置や回転やScaleを変更することができます。
https://github.com/SceneView/sceneview-android/blob/1e303adf92f1f7b67eb9aa4f9fc731d75213ac97/sceneview/src/main/java/io/github/sceneview/node/Node.kt#L620-L633

fun transform(
    position: Position = this.position,
    quaternion: Quaternion = this.quaternion,
    scale: Scale = this.scale,
    smooth: Boolean = isSmoothTransformEnabled,
    smoothSpeed: Float = smoothTransformSpeed
) = transform(Transform(position, quaternion, scale), smooth, smoothSpeed)

前のコードでは、3Dモデルを表示する事が出来ましたので、モデルをタップしたときに180度回転させるようにしましょう。

ARSceneView

SceneviewexampleTheme {
    val engine = rememberEngine()
    val modelLoader = rememberModelLoader(engine)
    // 3Dモデルの読み込み
    val modelNode = ModelNode(
        modelInstance = modelLoader.createModelInstance(R.raw.android)
    ).apply {
        // 3Dモデルの初期設定
        scale = Scale(1 / 40f)
        position = Position(0f)
    }
    ARScene(
        modifier = Modifier.fillMaxSize(),
        engine = engine,
        modelLoader = modelLoader,
        childNodes = rememberNodes {
            // モデルノードの追加
            add(modelNode)
        },
        // タッチイベントのリスナー
        onGestureListener = rememberOnGestureListener(
            onSingleTapConfirmed = { _, node ->
                if (node == modelNode) {
                    // モデルノードの回転
                    modelNode.transform(
                        rotation = Rotation(z = 180f),
                        smooth = true
                    )
                }
            }
        )
    )
}

SceneView

SceneviewexampleTheme {
    val engine = rememberEngine()
    val modelLoader = rememberModelLoader(engine)
    // 3Dモデルの読み込み
    val modelNode = ModelNode(
        modelInstance = modelLoader.createModelInstance(R.raw.android)
    ).apply {
        // 3Dモデルの初期設定
        scale = Scale(1 / 40f)
        position = Position(0f)
    }
    Scene(
        modifier = Modifier.fillMaxSize(),
        engine = engine,
        modelLoader = modelLoader,
        childNodes = rememberNodes {
            // モデルノードの追加
            add(modelNode)
        },
        // タッチイベントのリスナー
        onGestureListener = rememberOnGestureListener(
            onSingleTapConfirmed = { _, node ->
                if (node == modelNode) {
                    // モデルノードの回転
                    modelNode.transform(
                        rotation = Rotation(z = 180f),
                        smooth = true
                    )
                }
            }
        )
    )
}

結果を見てみましょう!

ARSceneView SceneView

ARで動画を表示する

3Dモデルの表示方法がわかったので、以下の動画をExoPlayer x Sceneviewで表示できるように実装していきましょう!
こちらで無料動画を入手できます。

出典:Pexels, https://www.pexels.com/ja-jp/video/13299023/ (2024/10/07アクセス)

Sceneviewのソースコードを見ると、VideoNodeが無効になっていることがわかりました。

また、Nodeのテクスチャを変更するには、Google FilamentのMaterialInstanceを作成し、SceneviewのMaterialLoaderを使用して新しいテクスチャを適用する必要があります。そのためには、以下の手順を実行する必要があります。

こちらがコードです!✨

class ExoPlayerVideoMaterial(
    engine: Engine,
    exoPlayer: ExoPlayer,
    private val materialLoader: MaterialLoader,
) {

    // SurfaceTexture作成
    private val surfaceTexture = SurfaceTexture(0).apply {
        detachFromGLContext()
    }

    // SurfaceTexture→Surface作成
    private val surface = Surface(surfaceTexture)

    // Textureのために、FilamentのStream作成
    private val stream = Stream.Builder()
        .stream(surfaceTexture)
        .build(engine)

    // Texture作成
    private val texture = VideoTexture.Builder()
        .stream(stream)
        .build(engine)

    // VideoTextureを使ったMaterialInstance作成
    val videoInstance
        get() = materialLoader.createVideoInstance(videoTexture = texture).apply {
            setExternalTexture(texture)
        }

    init {
        // ExoPlayerのSurface設定
        exoPlayer.setVideoSurface(surface)
    }
}

Sceneview側では、作成したExoPlayerVideoMaterialを使いましょう!

SceneviewexampleTheme {
    // ExoPlayerの設定...
    val player = ExoPlayer...
    // さっきの作成したExoPlayerVideoMaterial
    val videoMaterial = ExoPlayerVideoMaterial(engine, player, materialLoader)
    val videoNode = PlaneNode(
        engine = engine,
        // 動画は16:9のため
        size = Size(16f, 9f),
        // NodeのMaterialInstance設定
        materialInstance = videoMaterial.videoInstance,
    )
    ARScene(
        //...
        childNodes = rememberNodes {
            // ノードの追加
            add(videoNode)
        },
    )
}

結果を見てみましょう!

ARで動画を表示出来ました!😀

まとめ

Sceneviewは、3DやARコンテンツを表示するための非常に便利なライブラリです。設定や使用がとても簡単です。しかし、私の個人的な経験から言うと、ドキュメントがあまり多くないため、もう少し複雑なことをしようとすると、Githubのソースコードを見る必要があります。アプリに3Dを追加したい場合は、ぜひ試してみてください!

モバイルチームは開発する仲間を募集しています! https://open.talentio.com/r/1/c/safie/pages/54204

© Safie Inc.