この記事はセーフィー株式会社 Advent Calendar 2025の12月6日の記事です。
こんにちは!
セーフィー株式会社のAndroidエンジニアのジェローム(@yujiro45)です🎄
昨年、私が書いた「AndroidでSceneViewを使って、3D/ARを表示する」という記事で、アプリに3D/AR機能を簡単に実装できるライブラリを紹介しました。 しかし現在、SceneViewはメンテナンスされておらず、コントリビューターさんのGitHubでのコメントによれば新しいリリースの予定はありません。

本記事では、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モデルを表示してみましょう!٩( 'ω' )و

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の動画を表示する

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で動画を表示できました!🎉

動画を表示するために、SceneViewとSceneCoreの主な違いは以下となります。
| SceneView | SceneCore | |
|---|---|---|
| ExoPlayerの表示方法 | VideoNodeがコメントアウトされているので、動画再生用の Material を作成必要があります。 |
SurfaceEntity を使うと、簡単に動画再生できます。 |
まとめ
SceneViewはメンテナンスされていないので、SceneCoreは有力な代替です。書き方はおおむね似ており、Googleが作っているライブラリです。ただし前述のとおり、Android XRに対応したデバイスでのみ動作します。一般的なスマートフォンでは実行できませんが、ぜひEmulatorで試して遊んでみてください!
モバイルチームは開発する仲間を募集しています!