Planar Reflection¶
a mirror floor reflecting the scene above it.
▶ Run in browserTags: 3d planar-reflection renderview render-to-texture
A PlanarReflection3D turns any flat surface into a mirror. Every frame it
renders the scene again from the active camera mirrored across its plane, and a
material samples that capture as its albedo.
Using one takes two steps:
Place the node on the surface. Its world position is a point on the reflection plane and its local +Y axis is the plane’s normal. Here it sits at the origin, level with the top face of the floor slab.
Pass the node as the surface’s albedo,
Material(albedo_map=reflection). A material fed a planar reflection samples it with a projective UV worked out from each fragment’s screen position rather than the mesh’s own UVs, so the mirror image lines up with the scene at any window size.
The mirrored camera clips at the plane, so nothing below the floor leaks into
the reflection. resolution_scale (half the window by default) sets the size
of the capture, and clip_bias keeps the surface out of its own reflection.
The orange cube spins, and its reflection turns with it in the same frame. A
planar reflection is exact for one flat surface such as a mirror, calm water or
a wet floor; for glossy curved objects use a ReflectionProbe3D cubemap.
Source¶
1# /// simvx
2# tags = ["3d", "planar-reflection", "renderview", "render-to-texture"]
3# screenshot_frame = 30
4# ///
5"""Planar Reflection: a mirror floor reflecting the scene above it.
6
7A ``PlanarReflection3D`` turns any flat surface into a mirror. Every frame it
8renders the scene again from the active camera mirrored across its plane, and a
9material samples that capture as its albedo.
10
11Using one takes two steps:
12
131. **Place the node on the surface.** Its world position is a point on the
14 reflection plane and its local +Y axis is the plane's normal. Here it sits at
15 the origin, level with the top face of the floor slab.
162. **Pass the node as the surface's albedo**, ``Material(albedo_map=reflection)``.
17 A material fed a planar reflection samples it with a projective UV worked out
18 from each fragment's screen position rather than the mesh's own UVs, so the
19 mirror image lines up with the scene at any window size.
20
21The mirrored camera clips at the plane, so nothing below the floor leaks into
22the reflection. ``resolution_scale`` (half the window by default) sets the size
23of the capture, and ``clip_bias`` keeps the surface out of its own reflection.
24
25The orange cube spins, and its reflection turns with it in the same frame. A
26planar reflection is exact for one flat surface such as a mirror, calm water or
27a wet floor; for glossy curved objects use a ``ReflectionProbe3D`` cubemap.
28"""
29
30from simvx.core import (
31 Anchor,
32 Camera3D,
33 CanvasLayer,
34 DirectionalLight3D,
35 Label,
36 Material,
37 Mesh,
38 MeshInstance3D,
39 Node,
40 PlanarReflection3D,
41)
42from simvx.graphics import App
43
44
45class PlanarReflectionScene(Node):
46 def on_ready(self):
47 # A low camera, so the mirror floor fills the lower half of the view.
48 self.add_child(Camera3D(position=(0, 2.0, 7.5), look_at=(0, 0.6, 0)))
49 self.add_child(DirectionalLight3D(intensity=2.5, direction=(-0.4, -1.0, -0.6)))
50
51 # The reflection plane is y = 0, the top face of the floor slab.
52 reflection = self.add_child(PlanarReflection3D())
53 mirror = Material(albedo_map=reflection, roughness=0.05)
54 self.add_child(MeshInstance3D(mesh=Mesh.box((14, 0.5, 14)), material=mirror, position=(0, -0.25, 0)))
55
56 self.cube = self.add_child(
57 MeshInstance3D(
58 mesh=Mesh.box(1.2),
59 material=Material(colour=(0.95, 0.55, 0.15, 1), roughness=0.4),
60 position=(0, 1.1, 0),
61 )
62 )
63 self.add_child(
64 MeshInstance3D(
65 mesh=Mesh.sphere(0.55),
66 material=Material(colour=(0.3, 0.5, 0.95, 1), roughness=0.3),
67 position=(2.4, 0.55, 1.0),
68 )
69 )
70 self.add_child(
71 MeshInstance3D( # a tall pillar reads clearly in the mirror
72 mesh=Mesh.box((0.6, 3, 0.6)),
73 material=Material(colour=(0.3, 0.8, 0.4, 1)),
74 position=(-2.4, 1.5, -0.8),
75 )
76 )
77 self.add_child(
78 MeshInstance3D( # a back wall fills the mirror's horizon
79 mesh=Mesh.box((14, 3, 0.5)),
80 material=Material(colour=(0.75, 0.25, 0.25, 1), roughness=0.8),
81 position=(0, 1.5, -6.5),
82 )
83 )
84
85 hud = self.add_child(CanvasLayer())
86 hud.add_child(Label("PlanarReflection3D as a mirror floor", anchor=Anchor.TOP_LEFT, inset=16, font_size=22))
87
88 def on_update(self, dt):
89 self.cube.rotate_y(1.4 * dt)
90 self.cube.rotate_x(0.7 * dt)
91
92
93if __name__ == "__main__":
94 App(width=1280, height=720, title="Planar Reflection").run(PlanarReflectionScene())