【Godot】SubViewportの活用法 - ミニマップ・3Dプレビュー・レンダーテクスチャ

作成: 2025-12-08最終更新: 2026-07-08

GodotのSubViewportを、独立した仮想画面という基本概念から解説。ミニマップ、インベントリの3Dプレビュー、監視カメラ風レンダーテクスチャの実装と、Update Modeや解像度によるパフォーマンス最適化まで、具体的なコードと図で学びます。

Godotでゲームを作っていると、こんな「画面の中にもう一つの画面が欲しい」場面に出会います。マップを見渡す ミニマップ を隅に置きたい。インベントリで装備を 3Dモデルでぐるぐる回して見せたい。監視カメラやポータルのように 別の場所の映像をリアルタイムに映したい

これらは通常の1画面だけでは実現が難しく、専用のノード SubViewport の出番です。SubViewportメイン画面とは独立した「仮想の画面」 を作り、その描画結果を テクスチャとして 自由に貼り付けられる機能。これを押さえると、UIとグラフィック表現の引き出しが一気に増えます。

大きな画面フレームの中に小さなサブ画面(カメラと立方体が映る別世界)があり、その映像がメイン画面へ転送されるSubViewportのイメージ

この記事でわかること

  • SubViewport の正体—— 独立したレンダリング世界ViewportTexture
  • ミニマップ の作り方(俯瞰カメラで別描画して隅に表示)
  • 監視カメラ風 のレンダーテクスチャ+シェーダー演出
  • 実践: インベントリの3Dプレビュー(ドラッグで回る装備)
  • Update Mode・解像度・cull_mask による パフォーマンス最適化

Sponsored

SubViewportのコア概念:独立したレンダリング世界

SubViewport を理解する鍵は、 「メインの描画から独立している」 という一点です。このノードの下に置いたシーンは、独自のカメラ・ワールド・レンダリング設定を持ち、 別の場所で描かれます

そして、その描画結果をメイン画面に持ち込む橋渡し役が ViewportTexture です。SubViewport の出力をこのテクスチャにすると、TextureRect(UI用)・Sprite2D・3Dモデルのマテリアルなど、 テクスチャを貼れる場所ならどこにでも その映像を映せます。

SubViewport(カメラと立方体が映る独立した仮想画面)がViewportTextureを介してメイン画面のTextureRect・Sprite2D・3Dメッシュに貼られる流れを示した図

基本的なセットアップ

  1. ノード追加SubViewportContainer を追加し、その子に SubViewport を置く(コンテナを使うとサイズ管理が楽)。
  2. 中身を作るSubViewport の子として、映したいシーン(2D/3Dノードとカメラ)を組む。
  3. 表示する :別途 TextureRect を置き、その Texture に「新規 ViewportTexture」を設定して、対象の SubViewport を指定する。

この「独立した画面をテクスチャ化して貼る」流れさえ掴めば、以降の活用法はすべてこの応用です。

Sponsored

活用法1:ミニマップを作る

ミニマップは SubViewport の代表例です。 プレイヤーを追う俯瞰カメラで世界を別描画し、その映像を画面の隅に小さく表示 します。メイン画面とは別のカメラを持てるからこそ、同じ世界を「真上から」同時に映せるわけです。

ゲーム画面の中央にプレイヤーが立ち、右下の角にミニマップ枠(プレイヤーの濃い青ドットと敵の淡い青ドット)が表示されている図

プレイヤーだけでなく、敵やアイテムのアイコンも動的に表示する実践的なコードです。SubViewport 自身にアタッチして、ミニマップの描画ロジックをメインのゲームロジックから分離します。

extends SubViewport

@onready var minimap_camera: Camera2D = $MinimapCamera

# 敵アイコンを使い回すためキャッシュ(毎フレームの生成・検索を避ける)
var enemy_icons: Dictionary = {}
const ICON_NORMAL = preload("res://assets/enemy_icon.png")
const ICON_ALERT  = preload("res://assets/enemy_alert_icon.png")

func _process(_delta: float) -> void:
    var player := get_tree().get_first_node_in_group("player")
    if not is_instance_valid(player):
        return
    # 俯瞰カメラをプレイヤーに追従させる
    minimap_camera.global_position = player.global_position

    for enemy in get_tree().get_nodes_in_group("enemies"):
        if not is_instance_valid(enemy):
            continue
        # アイコンが無ければ1つだけ生成してキャッシュ
        if not enemy_icons.has(enemy):
            var new_icon := Sprite2D.new()
            add_child(new_icon)
            enemy_icons[enemy] = new_icon
        var icon: Sprite2D = enemy_icons[enemy]
        # 警戒中かどうかでアイコンを差し替える
        icon.texture = ICON_ALERT if enemy.is_in_alert_state() else ICON_NORMAL
        icon.global_position = enemy.global_position

ポイントは、 ミニマップの中身(カメラ・アイコン)が SubViewport の中で完結している ことです。メインのゲームは自分の描画に集中でき、ミニマップの表示ロジックはこのノードだけを見れば分かる——役割がきれいに分かれます。

Sponsored

活用法2:監視カメラ風レンダーテクスチャ

SubViewport の映像を シェーダーと組み合わせる と、単なる表示を超えた演出ができます。定番は、別の場所を映すカメラの映像に走査線やノイズを乗せた 「監視カメラ」 です。

別の部屋のCamera3Dが対象を映し、その映像がViewportTextureを介して手前のモニター(メッシュ)に走査線付きで表示される監視カメラの図
  1. 監視したい場所に SubViewportCamera3D を置く。
  2. メイン側にモニターとなる MeshInstance3D(例:PlaneMesh)を用意する。
  3. モニターのメッシュに ShaderMaterial を割り当て、映像に走査線とノイズを乗せる。
shader_type spatial;

uniform sampler2D screen_texture; // SubViewportの映像をここに渡す
uniform float scanline_intensity = 0.1;
uniform float noise_amount = 0.05;

void fragment() {
    // 縦位置に応じた走査線
    float scanline = sin(UV.y * 800.0) * scanline_intensity;
    // 位置から擬似ランダムなノイズを作る
    float noise = (fract(sin(dot(UV, vec2(12.9898, 78.233))) * 43758.5453) - 0.5) * noise_amount;

    vec4 tex = texture(screen_texture, UV);
    ALBEDO = tex.rgb - scanline - noise; // 映像を少し暗く乱して「監視カメラらしさ」を出す
}

最後に、SubViewport の映像(get_texture())をシェーダーの screen_texture に渡します。

extends MeshInstance3D

@export var camera_viewport: SubViewport # 監視カメラ側のSubViewportをInspectorで指定

func _ready() -> void:
    if not camera_viewport:
        return
    var mat := get_surface_override_material(0) as ShaderMaterial
    if mat:
        # SubViewportの出力テクスチャをシェーダーへ橋渡し
        mat.set_shader_parameter("screen_texture", camera_viewport.get_texture())

走査線やノイズを画面全体に流す表現は Fragment Shaderの基礎 の応用です。SubViewport は「素材となる映像」を用意し、シェーダーが「加工」を担当する——役割分担で覚えると分かりやすいです。

Sponsored

実践:インベントリの3D装備プレビューを組む

ここまでの「独立した画面をテクスチャに貼る」を、満足度の高いUI演出に落とし込みましょう。 インベントリ画面で、装備アイテムの3Dモデルをドラッグで回転プレビューする 機能です。

RPGの装備画面、キャラメイクの外見確認、ガチャ結果の3D表示——2D UIの中に3Dモデルを差し込みたい場面は多く、そのすべてが SubViewport の同じ型で作れます。

インベントリUIの左に装備スロット(兜・鎧・手袋・靴)、中央に回転台に乗った3Dの兜モデルと回転矢印、カーソルがドラッグして回している図

構成はこうです。SubViewport の中に、モデルを照らす最小限の3D環境を組みます。

- PreviewContainer (SubViewportContainer)  … マウス入力を受ける
  - SubViewport
    - Camera3D
    - DirectionalLight3D
    - WorldEnvironment          … 背景・環境光
    - TargetModel (Node3D)      … 回転させたい装備モデル

SubViewportContainer はGUI入力を受け取れるので、そこでドラッグを拾ってモデルを回します。

extends SubViewportContainer

@onready var model: Node3D = $SubViewport/TargetModel

var dragging := false

func _ready() -> void:
    mouse_filter = Control.MOUSE_FILTER_STOP # 入力を確実に受け取る

func _gui_input(event: InputEvent) -> void:
    if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
        dragging = event.pressed          # 押している間だけ回す
    elif event is InputEventMouseMotion and dragging:
        # 横方向のドラッグ量ぶんだけY軸回転
        model.rotate_y(deg_to_rad(-event.relative.x * 0.5))

ポイントは2つです。

  • 2D UIと3Dモデルを1画面に同居させられる :本来2Dのインベントリ画面に、SubViewport を貼った SubViewportContainer を1つ置くだけで、3Dモデルがそのまま「UIパーツ」になります。装備を切り替えたいときは TargetModel の中身を差し替えるだけです。
  • 入力は SubViewportContainer が受ける :3Dモデルを直接クリック判定するのは面倒ですが、コンテナが _gui_input でドラッグを拾ってくれるので、回転や拡大の操作がシンプルに書けます。

回転にバネのような戻りや慣性を付けたくなったら、Tweenrotation を補間すると気持ちよくなります。

Sponsored

パフォーマンスと最適化

SubViewport は強力な反面、 追加のレンダリングパス を生むため、無計画に使うと重くなります。とくに効くのが更新頻度と解像度です。

Update ModeがAlwaysだと毎フレーム描き直して重く、When Visibleだと見える時だけ描いて軽いことを対比した図
  • Update Mode を用途に合わせる :常時更新の Always は最終手段。UIの3Dプレビューのように 開いている時だけ描けばよい なら When Visible、静止画で足りるなら Once、手動制御は Disabled を選びます。ミニマップのように毎フレーム動くものだけ Always にします。
  • 解像度を最小限に :3Dプレビューが画面上で256×256pxなら、SubViewport の解像度も256×256で十分。表示サイズより大きく描くのは無駄です。
  • cull_mask で描画対象を絞る(3D)Camera3Dcull_mask で、そのビューポートに映すべきレイヤーだけを描画対象にできます。プレビュー用カメラに背景ステージまで描かせない、といった無駄を省けます。

「まず作って、重かったら Update Mode と解像度から見直す」で十分です。とくにモバイル向けでは、各 SubViewport がドローコールを増やすことを頭の隅に置いておきましょう。

Sponsored

よくある間違いとベストプラクティス

よくある間違いベストプラクティス
Update Mode を常に Always にする必要な時だけ更新 。UIなら When Visible、手動なら Disabled。毎フレーム更新を避ける
表示サイズより高い解像度で描く表示サイズに合わせて解像度を最小化 (3Dプレビューなら256×256など)
3Dカメラの cull_mask を放置描画レイヤーを分離 し、そのビューポートに不要なオブジェクトを描かせない
1つの SubViewport に何もかも詰める役割ごとに分割 (ミニマップ用・プレビュー用)。個別最適がしやすくなる
軽い代替を検討しないコストと表現を天秤に 。静止プレビューはただの画像、単純なミニマップはUI描画でも足りる

おまけ:先に知っておくと良いこと

基本の3パターンが作れたら、次はこのあたりを知っておくと表現が広がります。

  • 入力を通すか止めるかSubViewportContainermouse_filter と、SubViewportHandle Input Locally で、クリックを中の3Dシーンまで届けるか、コンテナで受け止めるかを切り替えられます。3Dプレビューでは「コンテナで受ける」が基本です。
  • 画面全体のポストプロセスSubViewport にゲーム全体を描いてから、その映像にシェーダーをかけると、色収差・ブラー・レトロ風などの Fragment Shader 系ポストプロセスを画面全体に適用できます。
  • エフェクトや粒と重ねる :プレビューやモニターに GPUParticles2D の粒や光を足すと、演出の説得力が増します。
  • 3D空間の基礎から :3Dプレビューで Camera3D やライトの置き方に迷ったら、Node3DとMeshInstance3Dの基本 が土台になります。

まとめ

  • SubViewport は、独立したカメラ・シーンを持つ「仮想の画面」。その出力を ViewportTextureget_texture())でテクスチャ化して貼る
  • ミニマップ は俯瞰カメラで別描画、監視カメラ はシェーダーで加工、3Dプレビュー は2D UIに3Dを同居——すべて同じ「テクスチャに貼る」応用
  • 最適化Update ModeWhen Visible/Once)・解像度の最小化・cull_mask が三本柱
活用法主な目的パフォーマンスの鍵
ミニマップゲーム世界を俯瞰で見せるcull_mask で描画対象を限定
3Dプレビュー2D UI内で3Dモデルを見せる解像度を最小化・When Visible
レンダーテクスチャ別映像をシェーダーで加工更新頻度とシェーダー負荷のバランス

まずは SubViewportContainerSubViewport を1つ置き、中に Camera3D と適当なメッシュを入れて「2D画面の中に3Dが映る」感覚を掴んでみてください。そこからミニマップにもプレビューにも広げられます。

さらに学ぶために