ARKit で球体に環境マッピング (Storyboard などを使わずソースコードで実現)

## 概要

- iOS の ARKit を使って拡張現実で環境マッピングするアプリを作る
- Storyboard の状態を修正せず、ソースコードだけで実現する

## 環境マッピングとは

[環境マッピング \- Wikipedia](https://ja.wikipedia.org/wiki/%E7%92%B0%E5%A2%83%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0)

> 環境マッピング（かんきょうマッピング、英: environment mapping）とは、3次元コンピュータグラフィックスにおけるテクスチャマッピングの手法の1つで、3次元形状の表面に擬似的な周囲環境の映り込みを再現する手法である。

[もう現実と区別つかない？ ARKit 2で見えたAppleが本気でARに取り組む理由（西田宗千佳） \- Engadget 日本版](https://japanese.engadget.com/2018/06/08/arkit2/)

> 鏡や銀食器はもちろん、車のボディやスマホの画面、磁器に至るまで、およそ光沢のあるものには、すべて「周囲の風景や色」が反射している。この反射を物体に映し込む技術を「環境マッピング」という。

## ARKit 2 ではカメラに映った風景と機械学習の結果から環境テクスチャを生成している

ARKit 2 はカメラに映った風景から環境テクスチャを生成できる。

[Adding Realistic Reflections to an AR Experience \| Apple Developer Documentation](https://developer.apple.com/documentation/arkit/adding_realistic_reflections_to_an_ar_experience)

> ARKit generates environment textures by collecting camera imagery during the AR session. Because ARKit cannot see the scene in all directions, it uses machine learning to extrapolate a realistic environment from available imagery.

## 今回の環境

- Xcode 10.3
- ARKit 2
- SceneKit
- Swift
- iPhone X + iOS 12.4.1

## プロジェクトの作成

テンプレートから iOS + Argumented Reality App を選択する。

<img width="733" alt="mirror-sphere-1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/890515a1-93d4-6e6c-d0e8-711dc2bf646b.png">

Language に Swift を、Content Technology に SceneKit を指定する。

<img width="732" alt="mirror-sphere-2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/2dcbb200-c221-021e-5cc4-05b0120bf4ac.png">

AppDelegate.swift と ViewController.swift という Swift のソースコードが用意される。

<img width="863" alt="mirror-sphere-3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/d6b73d39-a08c-a807-ae4a-230db5c8ab55.png">

## ソースコード

ViewController.swift を修正する。

viewDidLoad メソッドを以下のように書き換える。
SCNSphere による球体をAR空間に表示する。
球体の持つマテリアルに、鏡面のように景色が映り込むパラメータを指定する。

```swift
override func viewDidLoad() {
    super.viewDidLoad()
    
    // Set the view's delegate
    sceneView.delegate = self
    
    // Show statistics such as fps and timing information
    sceneView.showsStatistics = true
    
    // シーンを生成
    let scene = SCNScene()

    // Set the scene to the view
    sceneView.scene = scene
    
    // 球体を生成
    let sphere = SCNSphere(radius: 0.2)
    if let material = sphere.firstMaterial { // SCNMaterial
        // 鏡面球体のパラメータ
        material.lightingModel = .physicallyBased // 物理ベースのレンダリング
        material.metalness.contents = 1.0
        material.metalness.intensity = 1.0
        material.roughness.intensity = 0.0
    }

    // 0.5メートルの高さ、目の前1メートルの場所に置く
    let sphereNode = SCNNode(geometry: sphere)
    sphereNode.position = SCNVector3(0, 0.5, -1)

    // AR空間に球体を追加
    sceneView.scene.rootNode.addChildNode(sphereNode)
}
```

viewWillAppear メソッドを以下のように書き換える。
環境マッピングを有効にする設定を追加している。

```swift
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    // Create a session configuration
    let configuration = ARWorldTrackingConfiguration()

    // 環境マッピングを有効にする
    configuration.environmentTexturing = .automatic

    // Run the view's session
    sceneView.session.run(configuration)
}
```

## 実機での実行結果

実機 (iPhone X + iOS 12.4.1) にインストールして実行してみる。
周囲の景色が球体に写り込んでいるのがわかる。

![mirror-sphere-4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/997e9347-d2d0-d565-adfa-d6c6e065c3b1.jpeg)

よく見ると、真ん中のスクリーンショットの際に写り込んだペットボトルの景色が、右のスクリーンショットでもまだ残ったままになっているのがわかる。ARKit 2 の環境マッピングでは位置関係を把握して古い画像と現在の画像をうまく混ぜるようだが、アプリの作り方によっては離れた場所の古い画像が残ってしまうことがあるようなので注意したほうが良さそう。

## 指定するパラメータの情報

### ARWorldTrackingConfiguration.environmentTexturing

ARKit 2 はカメラに映った風景から環境テクスチャを生成できる。
environmentTexturing には automatic を指定する。

[environmentTexturing \- ARWorldTrackingConfiguration \| Apple Developer Documentation](https://developer.apple.com/documentation/arkit/arworldtrackingconfiguration/2977509-environmenttexturing)

> ARKit can generate environment textures during an AR session using camera imagery, allowing SceneKit or a custom rendering engine to provide realistic image-based lighting for virtual objects in your AR experience.

[ARWorldTrackingConfiguration\.EnvironmentTexturing\.automatic \- ARWorldTrackingConfiguration\.EnvironmentTexturing \| Apple Developer Documentation](https://developer.apple.com/documentation/arkit/arworldtrackingconfiguration/environmenttexturing/automatic)

> ARKit automatically determines when and where to generate environment textures.

### SCNMaterial.lightingModel

物理ベースのレンダリングをするため lightingModel には physicallyBased を指定する。

[SCNMaterial\.LightingModel \- SCNMaterial \| Apple Developer Documentation](https://developer.apple.com/documentation/scenekit/scnmaterial/lightingmodel)

> Constants specifying the lighting and shading algorithm to use for rendering a material.

[physicallyBased \- SCNMaterial\.LightingModel \| Apple Developer Documentation](https://developer.apple.com/documentation/scenekit/scnmaterial/lightingmodel/1640553-physicallybased)

> Shading based on a realistic abstraction of physical lights and materials.

### SCNMaterial.metalness.contents

metalness の contents には 1.0 ぐらいがちょうど良さそう。0.5だと薄すぎる色に見えるし、2.0だと濃い色になってしまう。

[contents \- SCNMaterialProperty \| Apple Developer Documentation](https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395372-contents)

> A number (NSNumber), specifying a uniform scalar value for the material's surface (useful for physically based properties such as metalness)

### SCNMaterial.metalness.intensity

metalness の intensity には 1.0 を指定する。

[metalness \- SCNMaterial \| Apple Developer Documentation](https://developer.apple.com/documentation/scenekit/scnmaterial/1640554-metalness)

> This property measures only the total intensity of color values; texture contents are best defined in grayscale.
> 
> This property generally approximates aspects of a physical surface—such as index of refraction, tendency to produce sharp reflections, and tendency to produce Fresnel reflections at grazing angles—that together produce an overall metallic or nonmetallic (also called dielectric) appearance. Lower values (darker colors) cause the material to appear more like a dielectric surface. Higher values (brighter colors) cause the surface to appear more metallic.
> 
> This property applies only when the material’s lightingModel value is physicallyBased.

[intensity \- SCNMaterialProperty \| Apple Developer Documentation](https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395407-intensity)

> A number between 0.0 and 1.0 that modulates the effect of the material property. Animatable.

### SCNMaterial.roughness.intensity

roughness の intensity には 0.0 を指定する。

[roughness \- SCNMaterial \| Apple Developer Documentation](https://developer.apple.com/documentation/scenekit/scnmaterial/1640555-roughness)

> This property measures only the total intensity of color values; texture contents are best defined in grayscale.
> 
> This property approximates the level of microscopic detail—for example tiny bumps and cracks—in a surface. By approximating these “microfacets” as a single term, this property helps produce lighting calculations that resemble the energy-conserving laws of real-world physics, resulting in more realistic variation between matte and shiny surfaces. Lower values (darker colors) cause the material to appear shiny, with well-defined specular highlights. Higher values (brighter colors) cause specular highlights to spread out and the diffuse color of the material to become more retroreflective.
> 
> This property applies only when the material’s lightingModel value is physicallyBased.

[intensity \- SCNMaterialProperty \| Apple Developer Documentation](https://developer.apple.com/documentation/scenekit/scnmaterialproperty/1395407-intensity)

> A number between 0.0 and 1.0 that modulates the effect of the material property. Animatable.

## 参考資料
- [Realistic reflections and environment textures in ARKit 2\.0](https://medium.com/@ARKit/realistic-reflections-and-environment-textures-in-arkit-2-0-d8d0f1332eed)
- [ARKit 2 の環境マッピング AREnvironmentProbeAnchor を試してみる \- Apple Engine](http://appleengine.hatenablog.com/entry/2018/06/26/200159)