RenderView

the main scene from a second camera, shown on an in-world monitor.

▶ Run in browser

Tags: 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()