Render to texture

A 3D scene rendered inside a SubViewport and shown as the texture of a monitor standing on a 2D screen.

The editor above is running this project. Change a line and it reloads.

Godot classes used

SubViewportTextureRectCamera3D

The code

scripts/main.gd
extends Node

const SCREEN_SIZE: Vector2i = Vector2i(576, 352)
const BEZEL: float = 22.0

var pivot: Node3D
var led: Polygon2D
var elapsed: float = 0.0


func _ready() -> void:
	_add_background()

	var view: Vector2 = get_viewport().get_visible_rect().size
	var origin: Vector2 = (view - Vector2(SCREEN_SIZE)) * 0.5

	var render: SubViewport = SubViewport.new()
	render.size = SCREEN_SIZE
	render.own_world_3d = true
	render.render_target_update_mode = SubViewport.UPDATE_ALWAYS
	render.msaa_3d = Viewport.MSAA_4X
	add_child(render)

	_fill_render(render)
	_add_cabinet(origin, view)

	var screen: TextureRect = TextureRect.new()
	screen.texture = render.get_texture()
	screen.position = origin
	screen.size = Vector2(SCREEN_SIZE)
	add_child(screen)

	print('the 3D scene lives inside a SubViewport, the screen is just its texture')


func _process(delta: float) -> void:
	elapsed += delta
	pivot.rotation.y = elapsed * 0.8
	led.color = Color('#8eef97') if fmod(elapsed, 1.0) < 0.6 else Color('#2a2f3d')


func _fill_render(render: SubViewport) -> void:
	var sky_material: ProceduralSkyMaterial = ProceduralSkyMaterial.new()
	sky_material.sky_top_color = Color(0.05, 0.06, 0.12)
	sky_material.sky_horizon_color = Color(0.16, 0.18, 0.28)
	sky_material.ground_bottom_color = Color(0.02, 0.02, 0.04)
	sky_material.ground_horizon_color = Color(0.1, 0.11, 0.16)

	var sky: Sky = Sky.new()
	sky.sky_material = sky_material

	var env: Environment = Environment.new()
	env.background_mode = Environment.BG_SKY
	env.sky = sky
	env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
	env.ambient_light_energy = 0.8

	var world: WorldEnvironment = WorldEnvironment.new()
	world.environment = env
	render.add_child(world)

	var light: DirectionalLight3D = DirectionalLight3D.new()
	light.rotation_degrees = Vector3(-50, -40, 0)
	light.light_energy = 1.3
	light.shadow_enabled = true
	render.add_child(light)

	var camera: Camera3D = Camera3D.new()
	camera.position = Vector3(0, 2.4, 5.4)
	camera.rotation_degrees = Vector3(-18, 0, 0)
	render.add_child(camera)
	camera.current = true

	pivot = Node3D.new()
	render.add_child(pivot)

	var colors: Array[Color] = [Color('#8da5f3'), Color('#8eef97'), Color('#fc7f7f'), Color('#f3d48d')]

	for i in colors.size():
		var mesh: BoxMesh = BoxMesh.new()
		mesh.size = Vector3(1.1, 1.1, 1.1)

		var material: StandardMaterial3D = StandardMaterial3D.new()
		material.albedo_color = colors[i]
		material.roughness = 0.4

		var box: MeshInstance3D = MeshInstance3D.new()
		box.mesh = mesh
		box.material_override = material
		box.position = Vector3(cos(TAU * i / 4.0), 0.6, sin(TAU * i / 4.0)) * 1.9
		box.rotation.y = TAU * i / 4.0
		pivot.add_child(box)

	var floor_mesh: PlaneMesh = PlaneMesh.new()
	floor_mesh.size = Vector2(14, 14)

	var floor_material: StandardMaterial3D = StandardMaterial3D.new()
	floor_material.albedo_color = Color('#2a2f3d')

	var ground: MeshInstance3D = MeshInstance3D.new()
	ground.mesh = floor_mesh
	ground.material_override = floor_material
	render.add_child(ground)


func _add_cabinet(origin: Vector2, view: Vector2) -> void:
	var bezel: ColorRect = ColorRect.new()
	bezel.color = Color('#2a2f3d')
	bezel.position = origin - Vector2(BEZEL, BEZEL)
	bezel.size = Vector2(SCREEN_SIZE) + Vector2(BEZEL, BEZEL) * 2.0
	add_child(bezel)

	var stand: Polygon2D = Polygon2D.new()
	stand.color = Color('#2a2f3d')
	stand.polygon = PackedVector2Array(
		[
			Vector2(-40, 0),
			Vector2(40, 0),
			Vector2(110, 64),
			Vector2(-110, 64),
		]
	)
	stand.position = Vector2(view.x * 0.5, origin.y + SCREEN_SIZE.y + BEZEL)
	add_child(stand)

	led = Polygon2D.new()
	led.color = Color('#8eef97')
	led.polygon = PackedVector2Array([Vector2(0, 0), Vector2(12, 0), Vector2(12, 12), Vector2(0, 12)])
	led.position = origin + Vector2(SCREEN_SIZE.x - 12, SCREEN_SIZE.y + 6)
	add_child(led)

	var caption: Label = Label.new()
	caption.text = 'SubViewport 576 x 352'
	caption.add_theme_font_size_override('font_size', 20)
	caption.add_theme_color_override('font_color', Color('#8da5f3'))
	caption.position = origin + Vector2(0, SCREEN_SIZE.y + BEZEL + 78)
	add_child(caption)


func _add_background() -> void:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

More Camera examples