SceneKitでシャボン玉を作る


今までSceneKitアドベントカレンダーで紹介した以下の記事、

- [SceneKitでプリミティブなジオメトリから3Dモデルを作ろう](https://qiita.com/k-boy/items/14205dd569a57eb44d96)
- [SceneKitのマテリアルのdiffuseを変更して3D物体の見た目を変える](https://qiita.com/k-boy/items/0ebc5b92e57807fa8b1e)
- [SceneKitのアニメーションサンプル集](https://qiita.com/k-boy/items/73a8ce28541966c7b20a)

これらのテクニックを組み合わせて、シャボン玉っぽいやつを作ってみましょう。

# 完成形

 シャボン玉っぽいやつに、さらに任意の画像を合成しています。Graffityで作った[magically](https://itunes.apple.com/jp/app/id1436168278?mt=8)というアプリで、体験することができます。

![bubble.gif](https://qiita-image-store.s3.amazonaws.com/0/111366/197d4d51-0f36-3bbd-8a35-d946c201541a.gif)

# シャボン玉っぽくする方法

- 球のジオメトリ
- シャボン玉のように輝いたマテリアルを使う
- 自分の場所から向こう側に飛ぶアニメーション
- 飛んだ後はふわふわ浮いている

こんな感じです。シャボン玉っぽさは絶妙なバランスで出来上がりますが、**要素を分解してみると意外とシンプルです。**

# 球のジオメトリの作成

それではまず、球のジオメトリを作ります。今回は全てコードで書いていきましょう。

```swift
let bubble = SCNSphere(radius: 0.1)
bubble.firstMaterial?.reflective.contents = #imageLiteral(resourceName: "bubble")
```

# シャボン玉のように輝いたマテリアルを使う

![bubble.png](https://qiita-image-store.s3.amazonaws.com/0/111366/560a370b-63ef-ed4b-aa96-cc116a9fbdc1.png)

単純にシャボン玉にしたい場合は、diffuseにこのmaterialを適用すればOKです。

```swift
let bubble = SCNSphere(radius: 0.1)
bubble.firstMaterial?.diffuse.contents = #imageLiteral(resourceName: "bubble")
```

## 任意の画像とシャボン玉っぽさをmixしたい

任意の画像とシャボン玉っぽさをmixしたいときは、以下のようにするとうまくいきました。diffuseとreflectiveを使っています。[reflective](https://developer.apple.com/documentation/scenekit/scnmaterial/1462520-reflective)は反射した時の模様を示すマテリアルのパラメータです。

```swift
let bubble = SCNSphere(radius: 0.1)
bubble.firstMaterial?.diffuse.contents = #imageLiteral(resourceName: "icon")
bubble.firstMaterial?.reflective.contents = #imageLiteral(resourceName: "bubble")
```

## transparency

また、シャボン玉は透けているので、transparencyを0.5にしてみました。

```swift
let bubble = SCNSphere(radius: 0.1)
bubble.firstMaterial?.diffuse.contents = #imageLiteral(resourceName: "icon")
bubble.firstMaterial?.reflective.contents = #imageLiteral(resourceName: "bubble")
bubble.firstMaterial?.transparency = 0.5
```

## おまけ： writesToDepthBuffer

通常、SceneKitでは、後から追加したnodeから描画していくので、透け感を出して、前に置いたシャボン玉から後ろのシャボン玉が透けて見える様子を再現したいときに、後ろのシャボン玉は描画されなくなってしまいます。

そこで、デフォルトはtrueになっている、writesToDepthBufferをfalseにすると、前にいるシャボン玉から後ろにいるシャボン玉が透けて見えるようになります。

|writesToDepthBuffer|true|false|
|:-:|:-:|:-:|
||![IMG_1056.jpg](https://qiita-image-store.s3.amazonaws.com/0/111366/3539250c-611b-6200-0533-63c4c690f39a.jpeg)|![IMG_1055.jpg](https://qiita-image-store.s3.amazonaws.com/0/111366/31caa483-bcd3-6e7d-e2de-b9ffb3e53675.jpeg)|
||重なったときに片方しかレンダリングしてない|重なっても両方見える|


```swift
let bubble = SCNSphere(radius: 0.1)
bubble.firstMaterial?.diffuse.contents = #imageLiteral(resourceName: "icon")
bubble.firstMaterial?.reflective.contents = #imageLiteral(resourceName: "bubble")
bubble.firstMaterial?.transparency = 0.5
bubble.firstMaterial?.writesToDepthBuffer = false
```

今回は、よりリアリティを出すためにwritesToDepthBufferをfalseにしてみました。まあ細かい話ですが。

# 自分の場所から向こう側に飛ぶアニメーション

こちらは簡単です。nodeに対してカメラから、カメラから数メートル先へmoveのアニメーションを適用します。

シャボン玉っぽさを出すために**easeOut**にしました。easeOutだと、徐々に減速します。

```swift
let move = SCNAction.move(to: toPosition, duration: speed)
move.timingMode = .easeOut
node.runAction(move)
```

# 飛んだ後はふわふわ浮いている

アニメーション自体は下のようにするだけです。

```swift
let rotate = SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1)
let roop = SCNAction.repeatForever(rotate)
node.runAction(roop)
```

しかし、これでは、ただ球が自転するだけで、ふわふわを再現できません。

そこで、親のnodeを作り、その配下にシャボン玉のnodeを追加し、ちょっと座標をずらしておくことで、惑星でいう公転みたいな動作を再現できます。

![solarsystem-rotation-1.jpg](https://qiita-image-store.s3.amazonaws.com/0/111366/7c686974-e00c-69eb-32c2-55b5e55a535a.jpeg)
https://www.kids.isas.jaxa.jp/zukan/solarsystem/solarsystem02.html

parentNodeを回転アニメーションさせると、子供のnodeがある軸を中心にくるくる回ってる様子を再現できます。

![bubble.gif](https://qiita-image-store.s3.amazonaws.com/0/111366/197d4d51-0f36-3bbd-8a35-d946c201541a.gif)

　ということで、コードは以下のようになりました。

```swift
// シャボン玉のNode
let node = SCNNode(geometry: bubble)
node.position = SCNVector3Make(0, 0.1, 0)

// 親のNodeを作る
let parentNode = SCNNode()
parentNode.addChildNode(node)

if let camera = sceneView.pointOfView {
    parentNode.position = camera.position
    
    let toPositionCamera = SCNVector3Make(0, 0, -2)
    let toPosition = camera.convertPosition(toPositionCamera, to: nil)
    
    let move = SCNAction.move(to: toPosition, duration: speed)
    move.timingMode = .easeOut
    parentNode.runAction(move) {
        // ふわふわアニメーション
        let rotate = SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1)
        let roop = SCNAction.repeatForever(rotate)
        parentNode.runAction(roop)
    }
}
sceneView.scene.rootNode.addChildNode(parentNode)
```

# まとめ

- シャボン玉感を出すために要素を分解する
- writesToDepthBuffer = falseで重なってもどちらも描画できる
- easeOutでシャボン玉感のある減速する動きを作れる
- 親Nodeを定義することで、公転のようなアニメーションを作れる

シャボン玉を作る過程を通して、SceneKitの様々なエッセンスを体感できると思います。面白いですね！

# サンプルコード

https://github.com/kboy-silvergym/ARKit-Emperor のBubbleの部分にあります。