Safie Engineers' Blog!

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

Android XR SceneCoreを使ってみよう

この記事はセーフィー株式会社 Advent Calendar 2025の12月6日の記事です。

こんにちは!

セーフィー株式会社のAndroidエンジニアのジェローム(@yujiro45)です🎄

昨年、私が書いた「AndroidでSceneViewを使って、3D/ARを表示する」という記事で、アプリに3D/AR機能を簡単に実装できるライブラリを紹介しました。 しかし現在、SceneViewはメンテナンスされておらず、コントリビューターさんのGitHubでのコメントによれば新しいリリースの予定はありません。

出典:Github, https://github.com/SceneView/sceneview-android/issues/623#issuecomment-3097955731 (2025/11/12アクセス)

本記事では、Android XRの新しいライブラリ「SceneCore」を使って、アプリ内に3D/ARを表示する方法を説明します。

Android XRとは

Android XRは、AR/VR向けのヘッドセットやスマートグラス用に作られた、AI搭載のAndroid OSです。アプリを3D空間に表示・操作でき、開発ではCompose for XRやSceneCoreなどのライブラリを使って実装します。

Android XRのEmulator作成方法

Android XRは、XRが対応した端末でしか使えないので端末持っていない場合は、XRのEmulatorを作成しないといけないです。

セッションの作成は、Android XR デバイスでのみサポートされています。互換性のないデバイスでセッションを作成しようとすると、結果は失敗します。 https://developer.android.com/develop/xr/jetpack-xr-sdk/add-session

Android StudioのデバイスマネージャーでXRのカテゴリがあります。そこで、XRのEmulatorを簡単に作成できます。

SceneView vs SceneCore

3Dモデルを表示する

💡この記事では、SceneCoreの最新版(執筆時点)である1.0.0-alpha08を使用しています。 SceneViewは最新版の2.3.0を使用しています。

SceneViewとSceneCoreの違いを理解するため、両方のライブラリで「ドロイド君」の3Dモデルを表示してみましょう!٩( 'ω' )و

出典:Sketchfab, https://sketchfab.com/3d-models/android-7c30eda007684abbb78ea4b99d22fc2c (2025/11/12アクセス)

SceneView
SceneviewexampleTheme {
    val engine = rememberEngine()
    val modelLoader = rememberModelLoader(engine)
    // 3Dモデル用のノードを作成
    val modelNode = ModelNode(
            // 3Dモデルの読み込み (/res/raw)
        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)
        }
    )
}

結果)

詳細は、以前の記事「AndroidでSceneViewを使って、3D/ARを表示する」で説明しているので、ぜひ読んでください〜😃

SceneCore
Subspace {
    val session = LocalSession.current ?: return@Subspace
    val capabilities = LocalSpatialCapabilities.current
    var entity by remember(session) { mutableStateOf<GltfModelEntity?>(null) }

    LaunchedEffect(session, capabilities.isContent3dEnabled) {
        // Full Spaceモードに切り替え
        if (!capabilities.isContent3dEnabled) {
            session.scene.requestFullSpaceMode()
            return@LaunchedEffect
        }
        // 生成は1回だけ
        if (entity == null) {
            // 3Dモデルの読み込み (app/src/main/assets/models/android.glb)
            val model = GltfModel.create(session, Paths.get("models", "android.glb"))
            // 3Dモデルを作成する
            entity = GltfModelEntity.create(session, model).apply {
                // Positionを設定する
                setPose(Pose(Vector3(0f, 0f, -1.5f), Quaternion.Identity))
                // Scaleを設定する
                setScale(0.2f)
            }
        }
    }

    // Lifecycle管理
    DisposableEffect(session) {
        onDispose {
            entity?.setEnabled(false)
            entity?.parent = null
            entity = null
        }
    }
}

結果)Android XRで3Dモデルを表示できました〜!

SceneViewとSceneCoreの主な違いは以下となります。

SceneView SceneCore
3D Space Scene Subspace
3Dオブジェクト Node Entity
3DモデルのPath /res/raw/ app/src/main/assets/models/
Lifecycle対応 rememberEngine / rememberModelLoader / rememberNodes を使用すると、Compose がライフサイクルを面倒みるためlifecycleを対応するのは不要です。 SceneCore の Entity は XR側で自動破棄されないため、Composableの終了やセッション切替時に DisposableEffect(session) 内で明示的に解放しないといけないです(メモリリーク防止のため)。
3Dオブジェクトの追加方法 Scene を使うと、中に Node を作成できます。childNodes = rememberNodes { add(modelNode) } のように Node を追加すると、表示されます。 GltfModelEntity.create(session, model) を呼ぶと、3Dモデル用の Entity が作成され、.add() しなくても表示されます。
3Dスペースモードを切り替える 不要 Android XRのEntityを使う時、session.scene.requestFullSpaceMode() のように、Full Spaceモードに切り替えないといけないです。

ExoPlayerの動画を表示する

出典:Pexels, https://www.pexels.com/ja-jp/video/13299023/ (2025/11/12アクセス)

SceneView

SceneViewで以前の記事「AndroidでSceneViewを使って、3D/ARを表示する」で書いた通りで、Materialを作成しないといけないです。

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)
    }
}
SceneviewexampleTheme {
    // ExoPlayerの設定
    val player = remember {
        ExoPlayer.Builder(context).build().apply {
            repeatMode = Player.REPEAT_MODE_ALL
        }
    }
    // さっきの作成した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)
        },
    )
}

結果)

SceneCore

ExoPlayerの .setVideoSurface() を使うとSurface に再生できます。SceneCoreにはSurfaceEntityがあり、Surface を提供するため、ExoPlayerを接続してXRで再生できます。

Subspace {
    val session = LocalSession.current ?: return@Subspace
    val capabilities = LocalSpatialCapabilities.current
    val context = LocalContext.current

    // 作成した SurfaceEntity を保持
    var surface by remember(session) { mutableStateOf<SurfaceEntity?>(null) }

    // ExoPlayer はコンポーザブルのライフサイクルに合わせて1つだけ
    val player = remember {
        ExoPlayer.Builder(context).build().apply {
            repeatMode = Player.REPEAT_MODE_ALL
        }
    }
    // Lifecycleのため
    DisposableEffect(session) {
        onDispose {
            player.clearVideoSurface()
            player.release()
            surface?.setEnabled(false)
            surface?.parent = null
            surface = null
        }
    }

    LaunchedEffect(session, capabilities.isContent3dEnabled) {
        // Full Spaceモードの切り替え
        if (!capabilities.isContent3dEnabled) {
            session.scene.requestFullSpaceMode()
            return@LaunchedEffect
        }

        if (surface == null) {
                        // SurfaceEntity
            val surfaceEntity = SurfaceEntity.create(
                session = session,
                pose = Pose(
                        // Positionを設定する
                    Vector3(0f, 0f, -1.5f),
                ),
                // 動画は16:9のため
                shape = SurfaceEntity.Shape.Quad(FloatSize2d(16f, 9f)),

            )
            // Scaleを設定する
            surfaceEntity.setScale(0.25f)

            // 最初の黒塗りを避けるため、1フレーム目まで非表示
            surfaceEntity.setEnabled(false)

            // ExoPlayerをSurfaceEntityに接続して再生(res/raw/video.mp4)
            val uri = RawResourceDataSource.buildRawResourceUri(R.raw.video)
            player.setVideoSurface(surfaceEntity.getSurface())
            player.setMediaItem(MediaItem.fromUri(uri))
            player.prepare()
            player.addListener(object : Player.Listener {
                override fun onRenderedFirstFrame() {
                    screen.setEnabled(true)
                }
            })
            player.play()

            surface = surfaceEntity
        }
    }
}

結果)Android XRで動画を表示できました!🎉

動画を表示するために、SceneViewSceneCoreの主な違いは以下となります。

SceneView SceneCore
ExoPlayerの表示方法 VideoNodeがコメントアウトされているので、動画再生用の Material を作成必要があります。 SurfaceEntity を使うと、簡単に動画再生できます。

まとめ

SceneViewはメンテナンスされていないので、SceneCoreは有力な代替です。書き方はおおむね似ており、Googleが作っているライブラリです。ただし前述のとおり、Android XRに対応したデバイスでのみ動作します。一般的なスマートフォンでは実行できませんが、ぜひEmulatorで試して遊んでみてください!

モバイルチームは開発する仲間を募集しています!

open.talentio.com

open.talentio.com

© Safie Inc.