Godot Engineで複数のカメラを重ねて表示する

# はじめに

自分が開発しているVRMViewMeisterでも使用している機能なのだが、Unityではカメラコンポーネントに深度とカリングマスクが備わっており、それによってカメラの映し出す映像の優先度をつけて重ねて表示できたりする。
（URPだとカメラはレンダータイプとスタック機能で同じことができる）

ではGodotで同じことをしたいと思ったときどうすればいいのか。調べてもドンピシャなテクニックが見つからなかった。
というわけで公式マニュアルを見て試した結果を今回紹介したい。

# Unityでは

たとえばVRMViewMeisterではカメラを次のように構成して映像を表示している。

| 名前 | 深度 | カリングマスク |
|:-|:-:|:-|
| FrontMainCamera  | 10  | Default,Player,Stage  |
| SubCamera  | 50  | UserUI  |
| IKHandleCamera  | 70  | Handle,SubPlayer  |
| Main Camera  |  99 |  TransparentFX,Ignore Raycast,...,UI |

※全てを同時に使っているわけではない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/dad9ae3d-dad2-5f88-43e3-fb3451304768.png)

VRMViewMeisterで表現したかったのは、下図のような3DモデルのIKを動かすのに、IKの操作ハンドル用の図形を常に全面で表示したかったのだ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/320f2ffd-10dd-af8f-8af3-681ce0b4c5ee.png)

## URP環境では

Main Cameraをレンダータイプ＝基本にし、それ以外をオーバーレイにして次のように構成した。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/95d59e16-8a52-f4a3-c34d-60263a087d1e.png)

URP環境でもこのカメラをすべて活用しているわけではない。

## Unityでの実現方法

<dl>
<dt>Builtin RP</dt>
<dd>深度とカリングマスクを適切に使い分ける</dd>
<dt>Universal RP</dt>
<dd>レンダータイプとカリングマスク、スタックを使い分ける</dd>
</dl>

至極簡単だ。（管理の仕方によってはすごく大変だろうが）

# Godot では

じゃあGodotで同じことを実現したいと思ったときどうすればいいのか。
GodotにもCamera3Dというノードがある。同じことができれば一番スマートなのだろうが、Camera3Dでは似たプロパティは `Cull Mask` しかなさそうだ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/d3ea26e5-ddf7-3aed-bd31-9269937c1334.png)

それにCamera3Dを複数追加しても、そのままではそれぞれのカメラの映像を活用できない。

# 実現方法

結論から言うと、次の3つを使うことでだいたい同じことをできることがわかった。

* Camera3D
* SubViewport
* SubViewportContainer

公式マニュアルでは次のページだ。

[Viewport](https://docs.godotengine.org/ja/4.x/classes/class_viewport.html)
[Using Viewports](https://docs.godotengine.org/ja/4.x/tutorials/rendering/viewports.html)


## サンプルの図形

ゲーム中に使う3Dノードは、普通にルートノードの任意の場所に追加していってよい。


**シリンダー**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/a6f6357d-043f-9556-12ed-58b89a52c96f.png)

VisualInstance3Dの `Layers` を1にした。

**球体**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/6a528339-c77c-ee77-84cd-3bb55150d164.png)

VisualInstance3Dの `Layers` を2にした。

なおかつ、優先度別に重ねて表示したのがわかるように、あえてシリンダーの内部に埋もれて見えなくなるように配置した。

:::note warn
**期待値**
球体がシリンダーに埋もれて隠れることなく、表示されること
:::



## SubViewportContainerを追加する

映像を重ねたい `Cull Mask` のまとまりの分、Camera3Dを用意すると思いきや、ただそのままルートノードに追加しても意味がない。
最初にするのは、 `SubViewportContainer` をシーンに追加することだ。

`SubViewportContainer` はControlノードの派生なので、こんな感じで配置する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/0cf743b0-5e9a-52c9-23a4-8d69e938a385.png)

```
Control
->SubViewportContainer
```

Controlのプロパティは次のとおりにする。

```
Layout:
  Anchors Preset: Rect全面
  Transform:
    Size: 
      x: [指定の値]
      y: [指定の値]
  Container Sizing:
    Horizontal: 塗りつぶし
      拡大: オン
    Vertical: 塗りつぶし
      拡大: オン
```

SubViewportContainerのプロパティは次の通りにする。

```
Layout:
  Anchors Preset: Rect全面
  Transform:
    Size: 
      x: [指定の値]
      y: [指定の値]
```

 ポイントとなるのはこれらのプロパティだ。ControlとSubViewportContainer、それぞれのLayoutグループのTransformのSizeにて、目的とするビューポートのサイズを設定する。
 
 そしてアンカープリセットは `Rect全面` にすることによって、描画を全面に塗りつぶして表示させる。
そうしないとカメラ映像はUI周りに影響されて極端に小さかったりずれたりすることになる。

この段階ではこのような表示になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/45757a3d-b7c1-3b96-858b-dd6afd2c6e5a.png)

当然何も映らない。


## 必要な数のCamera3DとSubViewportを用意する

器が用意できたので実際に必要なCamera3DとSubViewportを `SubViewportContainer` の子ノードとして追加していく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/dcd2e547-2674-4ecb-3221-3670db7e1391.png)

:::note warn
**ポイント**
Unityの深度やスタックのように、目的の順番通りにSubViewportとCamera3Dのセットを追加してく必要がある。
つまり、 **一番下に描画したいカメラ映像を1番目** に追加し、以後は順に追加していく。
:::

### 1番目のSubViewportとCamera3D

SubViewportのプロパティは次の通り。

```
Size: ※Control・SubViewportContainerと同じ値
Viewport:
  Transparent BG: オフ
```

Camera3Dのプロパティは次の通り。

```
Cull Mask: ※目的に応じてオン・オフする
Current: オン
```

この段階ではこのような表示になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/1ae45a31-14e1-7646-5786-424d64b35cea.png)

:::note warn
見るべきポイントはプロパティ内のプレビュー表示だ。ちゃんとCamera3Dで設定した `Cull Mask` の通りに3Dモデルや他のノードが表示されていること。
:::


### 2番目以降のSubViewportとCamera3D

SubViewportのプロパティは次の通り。

```
Size: ※Control・SubViewportContainerと同じ値
Viewport:
  Transparent BG: オン
```

Camera3Dのプロパティは次の通り。

```
Cull Mask: ※目的に応じてオン・オフする
Current: オン
```

この段階でこのような表示になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/a7553e6f-a9a8-b990-d719-3626141c7bc4.png)


:::note warn
**ポイント**
* 2番目以降のSubViewportでポイントなのは、 `Transparent BG` を **オン** にすること
* SubViewportの **プレビュー表示** で、対象の `Cull Mask` だけの映像になっていること

これがもし `Transparent BG` が **オフ** だとこうなってしまう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/0797aaa8-9c03-16ac-898d-13fd1e293d2d.png)

シーンの画面にも2番目のCamera3Dの映像しか映らないことになる。

これがUnityでいうところの `深度のみ` や背景タイプの効果に相当する。
:::

### プロジェクトの設定

忘れてはならないのがプロジェクトの設定だ。

```
一般:
  表示:
    ウィンドウ:
      ストレッチ:
        モード: viewport
        アスペクト: expand
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/fadf7631-826e-6dee-75dd-9e57bb1cd1ab.png)

するとウィンドウ比率を変えた場合にこうなる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/58ae888d-4285-dfbf-5d8a-29a59727e9d4.png)

:::note info
設定は `モード: disabled, アスペクト: keep` でもいいかもしれない。
:::

設定が `モード: viewport, アスペクト: ignore` だとこうなる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/5e6d3da1-fa36-c970-6375-762064ab7d4f.png)


設定が `モード: viewport, アスペクト: keep` だとこうなる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/c21b69a3-e84f-dc1c-ebe7-507d31be2549.png)

このあたりの表示方法は目的に応じて決めたいところだ。

## 注意点

### カメラを動かす

無事映像が重なって表示されるようになったが、注意したいのはカメラが複数になったので、カメラを動かすときはすべて同時に同じ位置に動かさないといけない点だ。

例えばカメラを動かす別の操作用のノードを用意しておき、その子ノードとして `RemoteTransform3D` を追加し、移動や回転を管理する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/c6793e01-24b3-9331-7df3-c6b3024a4c51.png)

あとはRemoteTransform3Dの親ノードを動かすようにすれば、すべてのカメラが同時に同じ動き方をするようになる。

### 特定の優先度のカメラだけ非表示にしたい

このやり方の場合、SubViewportのプロパティを変更すればよい。

```
Viewport:
  Disable 3D: オン
```

これでそのSubViewportの3D映像が無効化される。つまり配下のCamera3DのCull Maskで指定された3Dモデルやノードは表示されなくなる。

:::note warn
シーンのツリービューにある `可視性の切り替え` をオフにしても効果がないので注意。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/7264ad12-1726-1814-1a3f-f971d91a1066.png)

:::

## まとめ

* SubViewportContainerの子ノードとして、SubViewportを必要な数だけ追加する
* SubViewportの子ノードとして、Camera3Dを追加する

* SubViewportのプロパティは次のとおりにする

| SubViewport | Transparent BG |
|:-:|:-:|
| 1番目  | オフ |
| 2番目  | オン |

* カメラを動かす場合は全てのカメラが同じ動きになるように工夫する



# 終わりに

これでGodotでもUnityで深度別にオブジェクトの表示を分けていたように、優先度別に積み重ねて表示できる。
ただ、個人的にこれでいいのか疑問に思うのは3D映像がUIのノードの配下ベースになってしまう点だ。

3Dのままだったら、デフォルトのビューポートに直接3Dのカメラ映像が描画されていたが、このやり方だとあくまでUIのコントロールのコンテナの表示領域に沿って映した3D映像となる。
まわりくどい気がする。


もしGodotで実はこうすればいいんだよ、とか、こうしたほうがもっとスマートだよなどのご意見や発見があれば教えていただけると助かります。

このやり方でいいのであれば、カメラ映像を重ねることがGodotでもできるようになるので参考にしていただければ幸いだ。