Sceneview Android - 3D/ARを体験する - 

Android Advent Calendar 15日目を担当させていただきます。@hiraokです。

3Dモデルを使って何かしたいなということで面白そうなOSSがあったので試してみました。

https://github.com/SceneView/sceneview-android

JetpackCompose で 3DとARをロードできるものになります。

導入方法は至って簡単でREADMEに記載の通りセットアップすると使えます。
※3Dを使う場合とARを使用する場合でDependencyが異なるので注意してください。

sampleコードもプロジェクトに用意されております。

https://github.com/SceneView/sceneview-android/tree/main/samples

3Dを読み込むサンプル
・model-viewer-compose
ARを読み込むサンプル
・ar-model-viewer-compose

サンプルを動かすと以下のように動きます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164151/6f006603-3bc9-300d-5a52-4c61eb7fec69.gif" width=320/>


## 実装
3D or AR viewerを最低限動かす実装

### 3D viewer
rememberでEngine、model、HDRなどのアセットのローダーを設定
```kotlin
val engine = rememberEngine()
val modelLoader = rememberModelLoader(engine)
val environmentLoader = rememberEnvironmentLoader(engine)
```
cameraNodeでcameraのpositionなどを設定(option)

```kotlin
val cameraNode = rememberCameraNode(engine = engine).apply {
    position = Position(z = -12.0f, x = 4.0f)
}
```
設定しない場合はDefaultCameraNodeが使用されます。

https://github.com/SceneView/sceneview-android/blob/1ef76d204e2fb3a1e716bca92de4339aecbaec58/sceneview/src/main/java/io/github/sceneview/SceneView.kt#L867

SceneにModelNodeの追加と設定値の入力
```kotlin
Scene(modifier = Modifier.fillMaxSize(),
      engine = engine,
      modelLoader = modelLoader,
      childNodes = rememberNodes {
            add(
                ModelNode(
                    modelInstance = modelLoader.createModelInstance("models/range.glb"),
                )
            )
        },
        environment = environmentLoader.createHDREnvironment("environments/sample.hdr")!!,
        cameraNode = cameraNode,
    )
```
実際に3Dモデル、背景を表示する部分です。

https://github.com/SceneView/sceneview-android/blob/1ef76d204e2fb3a1e716bca92de4339aecbaec58/sceneview/src/main/java/io/github/sceneview/Scene.kt#L47

engine, modelLoaderを渡して、childNodesがList<Node>を受け付けているので`rememberNodes`で
ModelNodeをListに追加してあげます。

`ModelNode`については`ModelLoader#createModelInstance()`に`app/src/main/assets/models`に配置した`glb` or `gltf`形式の3DモデルのFilePathを設定
※Local以外にもネットワークからも非同期でLoadできます。

https://github.com/SceneView/sceneview-android/blob/1ef76d204e2fb3a1e716bca92de4339aecbaec58/sceneview/src/main/java/io/github/sceneview/loaders/ModelLoader.kt#L365C9-L365C9

`environment`はシーンの照明環境とスカイボックスを定義します。  
スカイボックスとしてHDR（High Dynamic Range)イメージを設定して背景として利用するため`assets/environment`に配置した.hdr形式のFilePathを`EnvironmentLoader#createHDREnvironment`に設定します。


全体コード
```kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            SceneviewSampleTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    GalleryContent()
                }
            }
        }
    }
}

@Composable
fun GalleryContent() {
    val engine = rememberEngine()
    val modelLoader = rememberModelLoader(engine)
    val environmentLoader = rememberEnvironmentLoader(engine)
    val cameraNode = rememberCameraNode(engine = engine).apply {
        position = Position(z = -12.0f, x = 4.0f)
    }
    Scene(
        modifier = Modifier.fillMaxSize(),
        engine = engine,
        modelLoader = modelLoader,
        childNodes = rememberNodes {
            add(
                ModelNode(
                    modelInstance = modelLoader.createModelInstance("models/range.glb"),
                )
            )
        },
        environment = environmentLoader.createHDREnvironment("environments/sample.hdr")!!,
        cameraNode = cameraNode,
    )
}
```

### 動き
FreeでとってきたRangeRoverを空に浮かべたgif

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164151/0b876024-1683-a7fa-4782-18addbc07019.gif" width=320 />


### AR viewer
とっても簡単で`Scene`を`ARScene`に変える、cameraNodeを`ARCameraNode`に変える
ぐらいでできてしまいます。

全体コード
```kotlin
@Composable
fun GalleryContent() {
    val engine = rememberEngine()
    val modelLoader = rememberModelLoader(engine)
    val environmentLoader = rememberEnvironmentLoader(engine)
    val cameraNode = rememberARCameraNode(engine = engine).apply {
        position = Position(z = -12.0f, x = 4.0f)
    }
    ARScene(
        modifier = Modifier.fillMaxSize(),
        engine = engine,
        modelLoader = modelLoader,
        childNodes = rememberNodes {
            add(
                ModelNode(
                    modelInstance = modelLoader.createModelInstance("models/range.glb"),
                    scaleToUnits = 1f
                )
            )
        },
        environment = environmentLoader.createHDREnvironment("environments/sky.hdr")!!,
        cameraNode = cameraNode
    )
}
```

### 動き
設定が雑なのでモデルがめちゃくちゃデカくなっておりますができてます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164151/7fa62fb8-c591-0cf8-0614-c3d2b9ed97a4.png" width=320/>

車内も見られるので車のイメージをつかむ用途で使ったりするのもいいかもしれません。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164151/07d9c824-9758-8430-1162-e167527843b8.gif" width=320 />


## 困ったこと
・ロードできるglb or gltfを見つけるのに苦労した
→　現時点で動く素材がフリーで手に入ることがわかったサイト
https://www.cgtrader.com/
https://polyhaven.com/

・ARを動かしている間スマホがめちゃくちゃ熱くなる
→ 設定次第な気がするので調査

## まとめ
JetpackComposeでここまで簡単に3DモデルARを動かしたりすることができるので中身や3Dグラフィックの知識を身につけたらもっとうまくいろいろやれたりできるんだろうなと感じました。以前3DモデルをOpenGLで動かしたときより格段に楽でした。
今後に期待できるので個人的にもいろいろ試してみたいと感じました。

※今回は動かすことに重点を置いているので説明などで欠けていたり間違えていたりすることもあるかもなのでコメントいただけると嬉しいです。