【Godot】SubViewport Techniques - Minimaps, 3D in 2D, and Render Textures to Enhance Godot's Expressiveness

Created: 2025-12-08Last updated: 2025-12-16

Learn SubViewport in Godot Engine. Covers practical techniques from minimap and 2D/3D hybrid UI implementation to dynamic render textures and performance optimization.

Introduction: Taking Game Expression to the Next Level with SubViewport

Have you encountered these challenges while developing in Godot Engine?

  • Want to place a minimap in the corner of the screen to clearly show a vast map to players.
  • Want to preview items in 3D models that characters are equipping in the inventory screen.
  • Want effects like portals or security cameras that display real-time views of other locations.

These challenges are difficult to solve with a single game screen alone. However, mastering Godot's powerful SubViewport node makes all of them possible.

SubViewport creates a "virtual screen" independent from the main screen, allowing you to freely reuse its rendering result as a texture. This dramatically improves UI, game logic, and graphic expression.


SubViewport Core Concepts: An Independent Rendering World

The key to understanding SubViewport is that it's "independent from the main rendering pipeline." Scenes placed under this node have their own world, camera, and rendering settings, and are drawn in a completely separate location.

And ViewportTexture serves as the bridge to bring those rendering results into the main scene. By setting the SubViewport's output to this texture, you can apply it to TextureRect (for UI), Sprite2D, or even 3D model materials.

Basic Setup

  1. Add Nodes: Add a SubViewportContainer to the scene, then add a SubViewport as its child. (Using SubViewportContainer makes size management easier)
  2. Create Content: Build the scene you want to display (e.g., 2D/3D nodes, camera) as children of SubViewport.
  3. Display: Place a TextureRect at the same level as SubViewportContainer, set its Texture property to "New ViewportTexture", and specify the SubViewport path in the inspector.

Use Case 1: Implementing a Feature-Rich Minimap

Minimaps are a representative use case for SubViewport. Let's look at implementations that go beyond simple player-following.

Advanced GDScript: Displaying Multiple Targets and Icon Control

Practical code that displays not just the player, but enemies and items on the minimap, changing icons based on state.

# minimap_controller.gd (Attached to SubViewport)
extends SubViewport

@onready var player: CharacterBody2D = get_tree().get_first_node_in_group("player")
@onready var minimap_camera: Camera2D = $MinimapCamera

# Minimap scale (conversion ratio from world coordinates to minimap coordinates)
@export var map_scale: float = 0.1

# Cache references to enemy icons (avoid per-frame node searches)
var enemy_icons: Dictionary = {}

# Cache textures with preload (avoid per-frame load())
const ICON_NORMAL = preload("res://assets/enemy_icon.png")
const ICON_ALERT = preload("res://assets/enemy_alert_icon.png")

func _process(delta: float) -> void:
    if not is_instance_valid(player):
        return

    # Make camera follow player
    minimap_camera.global_position = player.global_position

    # Convert enemy positions to minimap coordinates and display
    update_enemy_icons()

func update_enemy_icons() -> void:
    # Get existing enemies and update icons
    for enemy in get_tree().get_nodes_in_group("enemies"):
        if not is_instance_valid(enemy):
            continue

        # Create icon if none exists
        if not enemy_icons.has(enemy):
            var icon = TextureRect.new()
            icon.texture = ICON_NORMAL
            add_child(icon)
            enemy_icons[enemy] = icon

        var icon: TextureRect = enemy_icons[enemy]

        # Change icon based on state
        icon.texture = ICON_ALERT if enemy.is_in_alert_state() else ICON_NORMAL

        # Convert world coordinates to minimap local coordinates
        # TextureRect uses position (not global_position)
        var relative_pos = enemy.global_position - minimap_camera.global_position
        icon.position = relative_pos * map_scale + size / 2

This code has the SubViewport itself monitor enemy positions and dynamically create/update icons within the minimap. This separates main game logic from minimap rendering logic.


Use Case 2: Interactive 3D Previews in 2D UI

Player satisfaction greatly improves if they can rotate and view 3D models of characters or items in inventory screens. This is also SubViewport's specialty.

Implementation Steps and Interaction

  1. Place SubViewportContainer and SubViewport in the UI scene.
  2. Inside SubViewport, place Camera3D, WorldEnvironment (for background and ambient light), DirectionalLight3D, and the 3D model you want to display (MeshInstance3D, etc.).
  3. Attach a script to SubViewportContainer to receive mouse input and rotate the 3D model.
# 3d_preview_viewport.gd (Attached to SubViewportContainer)
extends SubViewportContainer

@onready var subviewport: SubViewport = $SubViewport
@onready var model: Node3D = $SubViewport/TargetModel # Model to rotate

var is_dragging = false

func _ready() -> void:
    # Set mouse_filter to receive mouse input
    # Explicitly set as default may not pass input
    mouse_filter = Control.MOUSE_FILTER_STOP

func _gui_input(event: InputEvent) -> void:
    if event is InputEventMouseButton:
        if event.button_index == MOUSE_BUTTON_LEFT:
            is_dragging = event.is_pressed()

    if event is InputEventMouseMotion and is_dragging:
        # Rotate model on Y-axis based on mouse movement
        model.rotate_y(deg_to_rad(-event.relative.x * 0.5))

Since SubViewportContainer can receive GUI input, this kind of UI coordination is very easy.


Use Case 3: Advanced Visual Effects with Render Textures

SubViewport's true value emerges when combining its output as a texture with shaders. This enables dynamic expression beyond static UI displays.

Use Case: Security Camera with Post-Process Shaders

Apply effects like noise and scan lines to camera footage from another location to create a "security camera" look.

  1. Set up a SubViewport and Camera3D at the location you want to monitor.
  2. In the main scene, prepare a MeshInstance3D (e.g., PlaneMesh) as the monitor.
  3. Apply a ShaderMaterial to the monitor mesh with the following shader code.
// security_camera_shader.gdshader
shader_type spatial;

uniform sampler2D screen_texture;
uniform float noise_amount = 0.05;
uniform float scanline_intensity = 0.1;

void fragment() {
    vec2 uv = UV;
    // Scan lines
    float scanline = sin(uv.y * 800.0) * scanline_intensity;
    // Noise
    float noise = (fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453) - 0.5) * noise_amount;

    vec4 color = texture(screen_texture, uv);
    ALBEDO = color.rgb - scanline - noise;
}
  1. Use GDScript to pass the SubViewport's ViewportTexture to the shader's screen_texture parameter.
# monitor_screen.gd (Attached to MeshInstance3D)
extends MeshInstance3D

@export var camera_viewport: SubViewport

func _ready() -> void:
    if not camera_viewport:
        return

    var material: ShaderMaterial = self.get_surface_override_material(0)
    if material:
        var texture = camera_viewport.get_texture()
        material.set_shader_parameter("screen_texture", texture)

Performance, Common Mistakes and Best Practices

SubViewport is powerful, but unplanned use leads to performance degradation. Refer to the following table for efficient implementation.

Common MistakeBest Practice
Always setting Update Mode to AlwaysUpdate only when needed. Use When Visible for UI displays, Disabled for manual updates, and avoid per-frame updates in _process.
Setting unnecessarily high resolutionOptimize resolution. For 3D previews, use minimum resolution matching display size, like 256x256.
Not considering camera culling_maskSeparate render layers. Have minimap cameras only render layers that should appear on the minimap, eliminating unnecessary calculations.
Cramming everything into one SubViewportSplit SubViewport by role. Separate for minimap, 3D preview, etc. Makes management easier and allows individual optimization.
Not considering alternativesBalance cost and expression. Keep lighter alternatives in mind: static images for static previews, logic-based UI drawing for simple minimaps.

Especially in mobile game development, use SubViewport cautiously. Always keep in mind that each viewport generates additional draw calls and rendering passes.


Next Steps and Related Topics

Once you've mastered SubViewport, you can tackle more advanced expressions:

  • Dynamic Decals: Render bullet holes or character footprints via SubViewport and project onto meshes.
  • Fluid Simulation Visualization: Bake calculation results to textures via SubViewport to create interactive water surfaces.
  • Multi-Pass Rendering: Render the same scene multiple times with different shaders or settings to composite outline drawing or special effects.

Godot's official documentation and community tutorials are excellent resources for these topics.

Summary

This article explained SubViewport from basics to applications, with concrete code examples and optimization techniques. SubViewport isn't just a UI part—it's a crucial key for extending Godot's rendering pipeline and dramatically enhancing your game's expressiveness.

Use CasePrimary PurposePerformance Key
Feature-Rich MinimapProvide bird's-eye view of game world situation.Optimize Update Mode, limit render targets with culling_mask.
Interactive 3D PreviewAttractively display 3D models within 2D UI.Keep SubViewport resolution to minimum.
Render TexturesCombine real-time footage with shaders for advanced visual effects.Balance shader complexity with viewport update frequency.

Use these techniques to make your Godot projects even more unique and attractive.