RenderView¶
the main scene from a second camera, shown on an in-world monitor.
▶ Run in browserTags: 3d renderview render-to-texture camera
A RenderView renders the MAIN scene tree through a second Camera3D (a “security camera” hung above the set, marked visible=False so it never becomes the active camera) into an offscreen texture every frame. That texture is fed to a Material on a flat “monitor” slab in the same scene: a live security feed of the world the player is standing in. Contrast with SubViewport, which renders its OWN subtree; a RenderView has no offscreen subtree, it re-renders the one main world from another viewpoint.
The orange cube spins in both views at once (main camera and overhead feed), proving the monitor is a live same-frame capture, not a snapshot.
Usage: uv run python examples/features/3d/render_view.py
Source¶
1"""RenderView: the main scene from a second camera, shown on an in-world monitor.
2
3A RenderView renders the MAIN scene tree through a second Camera3D (a
4"security camera" hung above the set, marked visible=False so it never becomes
5the active camera) into an offscreen texture every frame. That texture is fed
6to a Material on a flat "monitor" slab in the same scene: a live security
7feed of the world the player is standing in. Contrast with SubViewport, which
8renders its OWN subtree; a RenderView has no offscreen subtree, it re-renders
9the one main world from another viewpoint.
10
11The orange cube spins in both views at once (main camera and overhead feed),
12proving the monitor is a live same-frame capture, not a snapshot.
13
14# /// simvx
15# tags = ["3d", "renderview", "render-to-texture", "camera"]
16# screenshot_frame = 30
17# ///
18
19Usage:
20 uv run python examples/features/3d/render_view.py
21"""
22
23from simvx.core import (
24 Camera3D,
25 DirectionalLight3D,
26 Input,
27 InputMap,
28 Key,
29 Material,
30 Mesh,
31 MeshInstance3D,
32 Node,
33 Quat,
34 RenderView,
35 Text2D,
36)
37from simvx.graphics import App
38
39WIDTH, HEIGHT = 1280, 720
40
41
42class RenderViewScene(Node):
43 def on_ready(self):
44 InputMap.add_action("quit", [Key.ESCAPE])
45
46 # ── Main camera (the player's view) ───────────────────────────────
47 # Added first and visible: the renderer's active camera.
48 self.add_child(Camera3D(position=(0, 2.2, 7.0), look_at=(0, 0.4, 0), up=(0, 1, 0)))
49 sun = self.add_child(DirectionalLight3D(intensity=2.5))
50 sun.direction = (-0.4, -1.0, -0.6)
51
52 # ── The security camera: a second Camera3D in the SAME world ──────
53 # visible=False keeps it from ever being picked as the active camera;
54 # the RenderView drives it explicitly by path.
55 # Aimed at the set's centre-right so the monitor (front-left, near the
56 # main camera) stays outside this camera's frustum: a view must never
57 # see the surface that displays its own feed.
58 security_cam = self.add_child(
59 Camera3D(
60 name="SecurityCam",
61 position=(4.5, 5.5, 4.5),
62 look_at=(1.2, 0.3, 0.3),
63 up=(0, 1, 0),
64 fov=45.0,
65 )
66 )
67 security_cam.visible = False
68 self.view = self.add_child(RenderView(camera="../SecurityCam", size=(512, 512)))
69
70 # ── The set: shared world content seen by BOTH cameras ────────────
71 self.add_child(
72 MeshInstance3D( # ground slab
73 mesh=Mesh.cube(1.0),
74 material=Material(colour=(0.35, 0.38, 0.42, 1.0), roughness=0.9),
75 position=(0, -0.75, 0),
76 scale=(12.0, 0.5, 12.0),
77 )
78 )
79 self.spinner = self.add_child(
80 MeshInstance3D(
81 mesh=Mesh.cube(1.2),
82 material=Material(colour=(0.95, 0.55, 0.15, 1.0), roughness=0.4),
83 position=(0, 0.4, 0),
84 )
85 )
86 self.add_child(
87 MeshInstance3D(
88 mesh=Mesh.sphere(0.5),
89 material=Material(colour=(0.3, 0.5, 0.95, 1.0), roughness=0.3),
90 position=(2.2, 0.0, 1.2),
91 )
92 )
93 self.add_child(
94 MeshInstance3D( # green pillar: reads clearly in the overhead feed
95 mesh=Mesh.cube(0.6),
96 material=Material(colour=(0.3, 0.8, 0.4, 1.0)),
97 position=(-2.2, 0.4, -1.0),
98 scale=(1.0, 3.0, 1.0),
99 )
100 )
101
102 # ── The in-world monitor showing the security feed ────────────────
103 # Material(albedo_map=render_view) is the first-class live-feed
104 # binding: the backend re-resolves the bindless slot every frame.
105 # Angled toward the main camera, and NOT in the security camera's
106 # line of sight (a view must never sample its own target).
107 self.add_child(
108 MeshInstance3D(
109 mesh=Mesh.cube(1.0),
110 material=Material(colour=(1, 1, 1, 1), albedo_map=self.view, unlit=True),
111 position=(-2.6, 1.5, 3.4),
112 rotation=Quat.from_euler(0.0, 0.5, 0.0),
113 scale=(2.0, 2.0, 0.08),
114 )
115 )
116
117 self.add_child(Text2D(text="RenderView → overhead security feed", position=(20, 20), font_scale=1.8))
118
119 def on_update(self, dt):
120 if Input.is_action_pressed("quit"):
121 self.app.quit()
122 return
123 # Spin the cube so both views visibly update in the same frame.
124 self.spinner.rotate_y(1.4 * dt)
125 self.spinner.rotate_x(0.7 * dt)
126
127
128def main():
129 App(width=WIDTH, height=HEIGHT, title="SimVX - RenderView").run(RenderViewScene())
130
131
132if __name__ == "__main__":
133 main()