IBL¶
Image-based lighting on metallic spheres.
▶ Run in browserTags: 3d
Demonstrates:
Procedural skybox cubemap providing ambient IBL
IBL pass generating irradiance, prefiltered specular, and BRDF LUT
Row of spheres with varying roughness (0.05 to 0.95)
Row of spheres with varying metallic (0.0 to 1.0)
Directional light for direct illumination
Controls: Escape - Quit
Run: uv run python examples/features/3d/ibl.py
Source¶
1"""IBL: Image-based lighting on metallic spheres.
2
3# /// simvx
4# web = { width = 1280, height = 720, reason = "Skybox and image-based lighting not yet supported on web." }
5# ///
6
7Demonstrates:
8 - Procedural skybox cubemap providing ambient IBL
9 - IBL pass generating irradiance, prefiltered specular, and BRDF LUT
10 - Row of spheres with varying roughness (0.05 to 0.95)
11 - Row of spheres with varying metallic (0.0 to 1.0)
12 - Directional light for direct illumination
13
14Controls:
15 Escape - Quit
16
17Run: uv run python examples/features/3d/ibl.py
18"""
19
20import math
21
22from simvx.core import (
23 Camera3D,
24 DirectionalLight3D,
25 Input,
26 InputMap,
27 Key,
28 Material,
29 Mesh,
30 MeshInstance3D,
31 Node,
32 Text2D,
33 Vec3,
34 WorldEnvironment,
35)
36from simvx.graphics import App
37
38SPHERE_COUNT = 7
39SPACING = 2.5
40
41
42class IBLScene(Node):
43 def on_ready(self):
44 InputMap.add_action("quit", [Key.ESCAPE])
45
46 # Procedural deep-blue skybox + IBL, declared on the central
47 # WorldEnvironment node. EnvironmentSync calls load_cubemap and the
48 # renderer's set_skybox auto-runs the IBL precompute (irradiance +
49 # prefiltered specular + BRDF LUT) on first install.
50 self.add_child(WorldEnvironment(
51 environment_map={"colour": (0.15, 0.22, 0.35)},
52 ))
53
54 # Camera looking at the sphere rows. Scene is Z-up (ground at Z=-2.5,
55 # sphere rows at Z=2.0 and Z=-1.5), so pass an explicit ``up=(0,0,1)``
56 # to ``look_at`` or the default +Y up rolls the camera as it orbits.
57 cam = self.add_child(Camera3D(
58 position=(0, -12, 4), fov=55, near=0.1, far=100.0,
59 look_at=Vec3(0, 0, 1.0), up=Vec3(0, 0, 1),
60 ))
61
62 # Directional light (sun) -- moderate intensity so IBL contribution is visible
63 sun = DirectionalLight3D(position=(5, -8, 10))
64 sun.colour = (1.0, 0.98, 0.92)
65 sun.intensity = 1.0
66 sun.look_at(Vec3(0, 0, 0))
67 self.add_child(sun)
68
69 # Higher tessellation keeps the sharp end (low roughness) from aliasing
70 # its specular highlight across individual faces, which reads as a flash.
71 sphere_mesh = Mesh.sphere(0.9, rings=48, segments=64)
72
73 # Top row: varying roughness (metallic = 1.0). Minimum 0.15 so the
74 # leftmost sphere still shows a tight highlight but the specular lobe
75 # is wide enough to sample multiple prefiltered-specular mip taps per
76 # pixel rather than strobing a single mip tap.
77 for i in range(SPHERE_COUNT):
78 t = i / max(SPHERE_COUNT - 1, 1)
79 roughness = 0.15 + t * 0.8
80 mat = Material(colour=(0.9, 0.6, 0.2, 1.0), metallic=1.0, roughness=roughness)
81 x = (i - SPHERE_COUNT // 2) * SPACING
82 self.add_child(MeshInstance3D(mesh=sphere_mesh, material=mat, position=(x, 0, 2.0)))
83
84 # Bottom row: varying metallic (roughness = 0.25)
85 for i in range(SPHERE_COUNT):
86 t = i / max(SPHERE_COUNT - 1, 1)
87 mat = Material(colour=(0.8, 0.1, 0.1, 1.0), metallic=t, roughness=0.25)
88 x = (i - SPHERE_COUNT // 2) * SPACING
89 self.add_child(MeshInstance3D(mesh=sphere_mesh, material=mat, position=(x, 0, -1.5)))
90
91 # Ground plane
92 ground_mat = Material(colour=(0.15, 0.15, 0.18, 1.0), metallic=0.0, roughness=0.9)
93 self.add_child(
94 MeshInstance3D(
95 mesh=Mesh.cube(1.0),
96 material=ground_mat,
97 position=(0, 0, -2.5),
98 scale=Vec3(25, 25, 0.2),
99 )
100 )
101
102 # Labels
103 self.add_child(Text2D(text="IBL Demo: Image-Based Lighting", x=10, y=10, font_scale=1.8))
104 self.add_child(Text2D(text="Top: Gold (metallic=1.0), roughness 0.15 -> 0.95", x=10, y=45, font_scale=1.2))
105 self.add_child(Text2D(text="Bottom: Red (roughness=0.25), metallic 0.0 -> 1.0", x=10, y=70, font_scale=1.2))
106 self.add_child(Text2D(text="Skybox provides ambient environment lighting via IBL", x=10, y=695, font_scale=1.0))
107
108 self._time = 0.0
109 self._cam = cam
110
111 def on_process(self, dt):
112 if Input.is_action_just_pressed("quit"):
113 self.app.quit()
114 return
115 # Gentle horizontal camera orbit in the XY plane (Z stays fixed so the
116 # ground stays at the bottom of the view). Explicit up=(0,0,1)
117 # prevents the default +Y up from rolling the view.
118 self._time += dt * 0.15
119 dist = 14.0
120 self._cam.position = Vec3(
121 math.sin(self._time) * dist,
122 -math.cos(self._time) * dist,
123 4.0,
124 )
125 self._cam.look_at(Vec3(0, 0, 0.5), up=Vec3(0, 0, 1))
126
127
128if __name__ == "__main__":
129 App(title="IBL Demo", width=1280, height=720).run(IBLScene())