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)