NTTドコモR&Dの技術ブログです。

Jetpack XRを使って3Dモデルの表示を行う

※ 本記事は 2026/3/31 以前にNTTコノキューにて記載した記事になります

0.はじめに

この記事はNTTコノキュー アドベントカレンダー2025の記事です。

こんにちは、NTTコノキューの山本です。

今回はJetpack XRを使ってAndroid XRアプリ向けに3Dモデルを表示させて行こうと思います。

1.開発環境

MacBook Apple M4

Android Studio Narwhal 4 Feature Drop | 2025.1.4 Canary 2

Jetpack XR SDK 1.0.0-alpha07

2.実装

基本的な事はDeveloperのアプリに3Dモデルを追加するに書いてあるのですが

サラッとしか書いていないので実際の実装ベースでご紹介しようと思います。

私が初めて挑戦したのはalpha04の時なのですが、alpha07までで結構変わったので

変更をしっかりと追わないといけないことに注意です。

2.1 モデルの読み込み

GltfModelのcreate()を使用して読み込みを行います。

表示する3Dモデルはglbファイルを使用するのですが


public suspend fun create(
    session: Session,
    assetData: ByteArray,
    assetKey: String,
)





public suspend fun create(session: Session, path: Path): GltfModel {
    require(!path.isAbsolute) {
        "GltfModel.create() expects a path relative to `assets/`, received absolute path $path."
    }
    return create(session.platformAdapter, path.toString())
}





public suspend fun create(session: Session, uri: Uri): GltfModel =
    create(session.platformAdapter, uri.toString())

byte array data , path , uri

が利用可能になっています。今回はpathを利用します。

モデルはプロジェクトのapp配下に

assets/modelsディレクトリを作っていただき、そこに配置してください。

val model = GltfModel.create(session, Paths.get("models/YOUR_FILE_NAME"))

2.2 entitiyの作成と制御

実際に使える様にするためにentityのインスタンスを作成します。 val entity = GltfModelEntity.create(session, model)

val modelPosition = Vector3(0f, 0f, 0f)

entity.setPose(Pose(modelPosition))

上記の様にVector3(x,y,z)で初期位置を決めてあげて、setPose()でポジションを指定する事で初期位置の設定ができます。

parent.addChild(entity) addChildしてあげる事で表示ができます。

entity.startAnimation(loop = true, animationName = animationName) モデルのアニメーションはstartAnimationでモデルに設定されたアニメーションをStringで指定してあげる事で再生が可能です。

2.3 Composableとして使用できる様にする

さて、entityを作成するだけで3Dモデルの描画自体はできるのですが

アプリのUIとして利用するにはVolumeという物に設定してあげる必要があります。

参考)

ボリュームを使用してレイアウトに3Dオブジェクトを配置する

Developerのサンプルが例として分かりやすいので引用させていただくと


Subspace {
    SpatialPanel(
        SubspaceModifier.height(1500.dp).width(1500.dp),
        dragPolicy = MovePolicy(),
        resizePolicy = ResizePolicy(),
    ) {
        ObjectInAVolume(true)
        Box(
            Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "Welcome",
                fontSize = 50.sp,
            )
        }
    }
}

ObjectInAVolume()


@OptIn(ExperimentalSubspaceVolumeApi::class)
@Composable
fun ObjectInAVolume(show3DObject: Boolean) {
    val session = checkNotNull(LocalSession.current)
    val scope = rememberCoroutineScope()
    if (show3DObject) {
        Subspace {
            Volume(
                modifier = SubspaceModifier
                    .offset(volumeXOffset, volumeYOffset, volumeZOffset) // Relative position
                    .scale(1.2f) // Scale to 120% of the size
            ) { parent ->
                scope.launch {
                    // Load your 3D model here
                }
            }
        }
    }
}

ObjectInAVolume()で先ほど紹介したcreate()とentityの作成をして貰って

SpatialPanelの中で通常のComposableの様に呼び出してもらうだけでOKです。

当初はイベントを検知する機能は無かったのですが、アップデートで追加されました。

そちらについては後日ご紹介させていただきます。

表示前、未確認であれば3Dコンテンツが描画可能かどうか

LocalSpatialCapabilities.current.isSpatialUiEnabled

で確認するのをお忘れなく。(これで通常のスマートフォン/XRデバイスでの動作を同一アプリで出しわけできる様になります)

こんな感じでアプリ内に3Dオブジェクトの配置が完了しました。

https://storage.googleapis.com/studio-cms-assets/projects/moWvmrd4a6/s-292x430_webp_c233011f-9f62-43bd-ac3f-1fe4f6585d67.png

3.おわりに

今回はアプリで3Dオブジェクトの配置を行うやり方についてご紹介させていただきました。

私は普段Androidエンジニアで、Unityの知識に乏しいのですが

コンテンツさえあればKotlinの知識だけで3Dモデルを扱う事ができるという点がかなり魅力的に感じました。

是非チャレンジしてみてください。