Planar reflection

a mirror floor reflecting the scene above it.

▶ Run in browser

Tags: 3d planar-reflection renderview render-to-texture

A PlanarReflection3D node re-renders the MAIN scene every frame through a camera mirrored across the node’s plane (its local XZ plane, +Y normal), with an oblique near-clip so nothing below the floor leaks into the reflection. The floor Material samples that capture with a projective UV from the fragment’s clip position: pass the node straight to Material(albedo_map=reflection) and the surface becomes a mirror. The orange cube spins so the reflection visibly tracks the world in the same frame. Contrast with ReflectionProbe3D (a cubemap approximation for glossy PBR): a planar reflection is exact for flat surfaces (mirrors, water, wet floors).

Usage: uv run python examples/features/3d/planar_reflection.py

Source

  1"""Planar reflection: a mirror floor reflecting the scene above it.
  2
  3A PlanarReflection3D node re-renders the MAIN scene every frame through a
  4camera mirrored across the node's plane (its local XZ plane, +Y normal), with
  5an oblique near-clip so nothing below the floor leaks into the reflection.
  6The floor Material samples that capture with a projective UV from the
  7fragment's clip position: pass the node straight to
  8``Material(albedo_map=reflection)`` and the surface becomes a mirror. The
  9orange cube spins so the reflection visibly tracks the world in the same
 10frame. Contrast with ReflectionProbe3D (a cubemap approximation for glossy
 11PBR): a planar reflection is exact for flat surfaces (mirrors, water, wet
 12floors).
 13
 14# /// simvx
 15# tags = ["3d", "planar-reflection", "renderview", "render-to-texture"]
 16# screenshot_frame = 30
 17# ///
 18
 19Usage:
 20    uv run python examples/features/3d/planar_reflection.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    PlanarReflection3D,
 34    Text2D,
 35)
 36from simvx.graphics import App
 37
 38WIDTH, HEIGHT = 1280, 720
 39
 40
 41class PlanarReflectionScene(Node):
 42    def on_ready(self):
 43        InputMap.add_action("quit", [Key.ESCAPE])
 44
 45        # Low camera angle so the mirror floor fills the lower half of the view.
 46        self.add_child(Camera3D(position=(0, 2.0, 7.5), look_at=(0, 0.6, 0), up=(0, 1, 0)))
 47        sun = self.add_child(DirectionalLight3D(intensity=2.5))
 48        sun.direction = (-0.4, -1.0, -0.6)
 49
 50        # ── The mirror floor ───────────────────────────────────────────────
 51        # The PlanarReflection3D sits at y=0 with its +Y up: the reflection
 52        # plane is the slab's top face. The slab samples the live capture with
 53        # a projective UV (automatic for a planar-reflection albedo).
 54        self.reflection = self.add_child(PlanarReflection3D(position=(0, 0, 0)))
 55        self.add_child(
 56            MeshInstance3D(
 57                mesh=Mesh.cube(1.0),
 58                material=Material(colour=(1.0, 1.0, 1.0, 1.0), albedo_map=self.reflection, roughness=0.05),
 59                position=(0, -0.25, 0),
 60                scale=(14.0, 0.5, 14.0),
 61            )
 62        )
 63
 64        # ── Scene content above the mirror ─────────────────────────────────
 65        self.spinner = self.add_child(
 66            MeshInstance3D(
 67                mesh=Mesh.cube(1.2),
 68                material=Material(colour=(0.95, 0.55, 0.15, 1.0), roughness=0.4),
 69                position=(0, 1.1, 0),
 70            )
 71        )
 72        self.add_child(
 73            MeshInstance3D(
 74                mesh=Mesh.sphere(0.55),
 75                material=Material(colour=(0.3, 0.5, 0.95, 1.0), roughness=0.3),
 76                position=(2.4, 0.55, 1.0),
 77            )
 78        )
 79        self.add_child(
 80            MeshInstance3D(  # green pillar: a tall shape reads clearly in the mirror
 81                mesh=Mesh.cube(0.6),
 82                material=Material(colour=(0.3, 0.8, 0.4, 1.0)),
 83                position=(-2.4, 1.5, -0.8),
 84                scale=(1.0, 5.0, 1.0),
 85            )
 86        )
 87        self.add_child(
 88            MeshInstance3D(  # red back wall: fills the mirror's horizon
 89                mesh=Mesh.cube(1.0),
 90                material=Material(colour=(0.75, 0.25, 0.25, 1.0), roughness=0.8),
 91                position=(0, 1.5, -6.5),
 92                scale=(14.0, 3.0, 0.5),
 93            )
 94        )
 95
 96        self.add_child(Text2D(text="PlanarReflection3D → mirror floor", position=(20, 20), font_scale=1.8))
 97
 98    def on_update(self, dt):
 99        if Input.is_action_pressed("quit"):
100            self.app.quit()
101            return
102        # Spin the cube so the mirror visibly tracks the world every frame.
103        self.spinner.rotate_y(1.4 * dt)
104        self.spinner.rotate_x(0.7 * dt)
105
106
107def main():
108    App(width=WIDTH, height=HEIGHT, title="SimVX - Planar Reflection").run(PlanarReflectionScene())
109
110
111if __name__ == "__main__":
112    main()