RealityKitをnonARモードで使ってみた

# RealityKitをnonARモードで使ってみた

Xcode 16からiOSでもReality Composer Proが利用できるようになったので、こちらで紹介しています  
コードでそれぞれ設定しても良いのですが、Reality Composer Proでシーンを作成する方が便利です

https://qiita.com/fuziki/items/6d693537e969c16847fe

[RealityKit](https://developer.apple.com/documentation/realitykit)はAR表示を推しているFrameworkですが、CameraModeに[.nonAR](https://developer.apple.com/documentation/realitykit/arview/cameramode-swift.enum/nonar)があったので、これを試してみました

作成したみたものがコチラ↓です

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">RealityKitをnonARモードで使ってみた<a href="https://t.co/Ms9okoRYls">https://t.co/Ms9okoRYls</a> <a href="https://t.co/51X0BxqU59">pic.twitter.com/51X0BxqU59</a></p>&mdash; ふじき (@fzkqi) <a href="https://twitter.com/fzkqi/status/1815723807589704154?ref_src=twsrc%5Etfw">July 23, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Xcode 15からReality Composerが利用できなくなったため、加筆修正しました

https://forums.developer.apple.com/forums/thread/737870


# arモードとnonARモードで違う所

基本的にRealityKitはarモードでもnonARモードでも同じように扱うことができました
nonARモードで意識したのは下記の4点です、順番に取り扱って行きます  
1. ARViewをnonARで作成する
1. カメラの追加
1. Lightの追加
1. Skyboxの設定
1. これらを1つのAnchorに追加する

## ARViewをnonARで作成する
cameraModeをnonARでARViewを作成します

```swift
let arView = ARView(frame: .zero, cameraMode: .nonAR, automaticallyConfigureSession: false)
```

### USDZの読み込み
USDZファイルを読み込んでモデルを表示します
アニメーションがあれば、リピートで再生させます
[Appleのサイト](https://developer.apple.com/jp/augmented-reality/quick-look/)からダウンロードしてきました

```swift
let plane = try ModelEntity.loadModel(named: "toy_biplane_idle")
plane.availableAnimations.forEach {
    plane.playAnimation($0.repeat())
}

let worldAnchor = AnchorEntity(world: .zero)
worldAnchor.addChild(plane)
arView.scene.anchors.append(worldAnchor)
```

こちらが結果です
飛行機が表示されているようですが、イマイチわかりません🥺

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/60e62a66-959d-ebf2-05e8-9774f557d1e5.png" width="600">


## カメラの追加

arモードではカメラの位置はデバイスの位置になるのですが、nonARモードでは手動でカメラを追加してあげる必要があります
[PerspectiveCamera](https://developer.apple.com/documentation/realitykit/perspectivecamera)を使用します
適当にカメラの位置と向きを設定します

```swift
let camera = PerspectiveCamera()
camera.camera.fieldOfViewInDegrees = 60
camera.look(at: .zero, from: [1, 0.5, 1], relativeTo: nil)

worldAnchor.addChild(camera)
```

こちらが結果です
斜め上から見ているので分かりやすくはなりましたが、真っ暗でよくわかりません🥺

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/4ce859f7-3d66-e1c7-4294-e08448e05c39.png" width="600">

## Lightの追加

真っ暗なので、Lightを追加します
LightはPerspectiveCamera、DirectionalLight、SpotLightの3種類があります
今回はDirectionalLightを配置しました

```swift
let directionalLight = DirectionalLight()
directionalLight.light.color = .white
directionalLight.light.intensity = 5000
directionalLight.look(at: .zero, from: .init(x: 0, y: 20, z: 5), relativeTo: nil)

worldAnchor.addChild(directionalLight)
```

こちらが結果です
飛行機は明るくなりましたが、背景が真っ暗で寂しいです🥺

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/51c5e35c-6f7e-a51d-6dec-ac0c4e71a316.png" width="600">

## Skyboxの設定
ARView.Environment.Background.skyboxを使って背景として設定します
  1. [https://polyhaven.com/a/alps_field](https://polyhaven.com/a/alps_field)から全天球の画像をダウンロードします
  1. `{任意の名前}.skybox` というリファレンスフォルダを追加し、その中にダウンロードした画像を配置します
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/951a59ae-215e-ca27-2d3d-b24aef478ce2.png)
  1. `EnvironmentResource.load(named: "{任意の名前}")` でロードして、backgroundに設定します
    * `{任意の名前}.skybox` というリファレンスフォルダがある場合、ビルド時に自動的にrealityenvファイルに変換され、アプリにバンドルされるようです

```swift
let skybox = try! EnvironmentResource.load(named: "alps_field_1k")
arView.environment.background = .skybox(skybox)
```

Swift Packageでは.skyboxを処理してくれなかったため、Swift Packageのみで設定する場合、オレオレSkyboxを作成して追加します

```swift
let skyboxTexture = try TextureResource.load(named: "alps_field_1k", in: .module)
var skyboxMaterial = UnlitMaterial()
skyboxMaterial.color = .init(texture: .init(skyboxTexture))
let skyboxComponent = ModelComponent(mesh: .generateSphere(radius: 1000), materials: [skyboxMaterial])

let skybox = Entity()
skybox.components.set(skyboxComponent)
skybox.scale *= .init(1, 1, -1)

worldAnchor.addChild(skybox)
```

こちらが結果です
全体的に明るくなってイイ感じです！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/0f669ea2-b510-814d-c30f-976dc0db14b7.png" width="600">

## これらを1つのAnchorに追加する
これはワンポイントで、上でしれっとやっているのですが、カメラなど表示に必要なものを1つのAnchorに追加して、それを追加する必要があります
Anchorが別れているとカメラに反映されないので注意が必要でした

```swift
let worldAnchor = AnchorEntity(world: .zero)
worldAnchor.addChild(plane)
worldAnchor.addChild(camera)
worldAnchor.addChild(skybox)
worldAnchor.addChild(directionalLight)
arView.scene.anchors.append(worldAnchor)
```

## その他
SceneEvents.Updateのタイミングでカメラを更新します

```swift
let len: Float = 0.8
var cameraRot: (yaw: Float, pitch: Float) = (yaw: 0, pitch: 0)

arView.scene
    .publisher(for: SceneEvents.Update.self)
    .sink { [weak self] (_: SceneEvents.Update) in
        self?.updateEvent()
    }
    .store(in: &cancellables)

func updateEvent() {
    camera.position = plane.position + .init(
        x: len * sin(cameraRot.yaw) * cos(cameraRot.pitch),
        y: len * sin(cameraRot.pitch),
        z: len * cos(cameraRot.yaw) * cos(cameraRot.pitch)
    )

    camera.look(
        at: plane.position,
        from: camera.position,
        relativeTo: nil
    )
}
```

# おわりに
RealityKitをnonARで使ってみました
SkyboxやカメラなどnonARで使うためのような機能もありますが、Lighting Shaderが設定できないなど、微妙に痒い所に手が届かない点があり、nonARモードでガッツリ使うには用途が限定される印象でした
しかし、RoomPlanなどAppleはARに力が入っているようなので、Frameworkの機能の追加、拡張に期待~~したいです~~していたのですが、Vision Proが始まりReality Composerが使えなくなり、悲しい気持ちになりましたw