Planar reflection¶
a mirror floor reflecting the scene above it.
▶ Run in browserTags: 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()