IBL

Image-based lighting on metallic spheres.

▶ Run in browser

Tags: 3d

Image-based lighting (IBL) lights a surface from the whole sky rather than from a handful of lights. The sky is a cubemap, and the renderer turns it into a blurred irradiance map for diffuse light, a prefiltered specular map with one mip per roughness level for reflections, and a BRDF lookup table. You never build those yourself: a WorldEnvironment with a sky does it, and every standard Material in the scene picks it up.

WorldEnvironment(sky_colour_top=(0.15, 0.22, 0.35), sky_colour_bottom=(0.6, 0.5, 0.4))

A colour sky (sky_mode="colour", the default) is a vertical gradient from sky_colour_top to sky_colour_bottom; sky_mode="procedural" gives a sun-driven sky and environment_map takes your own cubemap. How much of it reaches shaded surfaces is ambient_light_energy.

Two rows of spheres show what the material does with that light. The top row is gold metal (metallic=1.0) with roughness rising from 0.15 on the left to 0.95 on the right: the tight highlight and crisp sky reflection spread into a soft sheen. The bottom row is red with roughness=0.25 and metallic rising from 0.0 to 1.0: a plastic that reflects a little of the sky becomes a metal tinted by it. A DirectionalLight3D adds direct light and the highlight on top.

The OrbitCamera3D circles the spheres slowly on its own through camera.orbit(), and takes its own input as well.

Controls: Drag / right stick - Orbit the camera Scroll / pinch / triggers - Zoom

Source

  1# /// simvx
  2# web = { width = 1280, height = 720, reason = "Larger browser canvas; skybox + IBL run cross-backend." }
  3# ///
  4"""IBL: Image-based lighting on metallic spheres.
  5
  6Image-based lighting (IBL) lights a surface from the whole sky rather than from
  7a handful of lights. The sky is a cubemap, and the renderer turns it into a
  8blurred irradiance map for diffuse light, a prefiltered specular map with one
  9mip per roughness level for reflections, and a BRDF lookup table. You never
 10build those yourself: a ``WorldEnvironment`` with a sky does it, and every
 11standard ``Material`` in the scene picks it up.
 12
 13    WorldEnvironment(sky_colour_top=(0.15, 0.22, 0.35), sky_colour_bottom=(0.6, 0.5, 0.4))
 14
 15A colour sky (``sky_mode="colour"``, the default) is a vertical gradient from
 16``sky_colour_top`` to ``sky_colour_bottom``; ``sky_mode="procedural"`` gives a
 17sun-driven sky and ``environment_map`` takes your own cubemap. How much of it
 18reaches shaded surfaces is ``ambient_light_energy``.
 19
 20Two rows of spheres show what the material does with that light. The top row is
 21gold metal (``metallic=1.0``) with ``roughness`` rising from 0.15 on the left to
 220.95 on the right: the tight highlight and crisp sky reflection spread into a soft sheen.
 23The bottom row is red with ``roughness=0.25`` and ``metallic`` rising from 0.0
 24to 1.0: a plastic that reflects a little of the sky becomes a metal tinted by
 25it. A ``DirectionalLight3D`` adds direct light and the highlight on top.
 26
 27The ``OrbitCamera3D`` circles the spheres slowly on its own through
 28``camera.orbit()``, and takes its own input as well.
 29
 30Controls:
 31    Drag / right stick        - Orbit the camera
 32    Scroll / pinch / triggers - Zoom
 33"""
 34
 35import math
 36
 37from simvx.core import (
 38    Anchor,
 39    CanvasLayer,
 40    DirectionalLight3D,
 41    Label,
 42    Material,
 43    Mesh,
 44    MeshInstance3D,
 45    Node,
 46    OrbitCamera3D,
 47    WorldEnvironment,
 48)
 49from simvx.graphics import App
 50
 51SPHERE_COUNT = 7
 52SPACING = 2.5
 53ORBIT_SPEED = 0.15  # radians per second
 54
 55
 56class IBLScene(Node):
 57    def on_ready(self):
 58        # A deep blue zenith over a warm horizon: the two colours the spheres reflect.
 59        self.add_child(WorldEnvironment(sky_colour_top=(0.15, 0.22, 0.35), sky_colour_bottom=(0.6, 0.5, 0.4)))
 60        self.camera = self.add_child(
 61            OrbitCamera3D(
 62                pivot=(0, 3, 0),
 63                distance=14.5,
 64                yaw=0.0,
 65                pitch=math.radians(-14),
 66                fov=55,
 67                # Keep the player above the ground plane and within sight of the rows.
 68                max_pitch=math.radians(-3),
 69                min_distance=6,
 70                max_distance=30,
 71            )
 72        )
 73        self.add_child(DirectionalLight3D(colour=(1.0, 0.98, 0.92), intensity=1.0, direction=(-5, -10, -8)))
 74
 75        # A finely tessellated sphere keeps the tight highlight of a smooth metal from
 76        # jumping between faces as the camera moves.
 77        sphere = Mesh.sphere(0.9, rings=48, segments=64)
 78        for i in range(SPHERE_COUNT):
 79            t = i / (SPHERE_COUNT - 1)
 80            x = (i - SPHERE_COUNT // 2) * SPACING
 81            gold = Material(colour=(0.9, 0.6, 0.2), metallic=1.0, roughness=0.15 + 0.8 * t)
 82            red = Material(colour=(0.8, 0.1, 0.1), metallic=t, roughness=0.25)
 83            self.add_child(MeshInstance3D(mesh=sphere, material=gold, position=(x, 4.5, 0)))
 84            self.add_child(MeshInstance3D(mesh=sphere, material=red, position=(x, 1.0, 0)))
 85
 86        ground = Material(colour=(0.15, 0.15, 0.18), roughness=0.9)
 87        self.add_child(MeshInstance3D(mesh=Mesh.plane(25.0), material=ground))
 88
 89        hud = self.add_child(CanvasLayer())
 90        captions = "Top: gold, metallic 1.0, roughness 0.15 -> 0.95\nBottom: red, roughness 0.25, metallic 0.0 -> 1.0"
 91        hud.add_child(Label(captions, anchor=Anchor.TOP_LEFT, inset=10, font_size=18))
 92
 93        # Example help overlay: not part of the technique shown.
 94        hud.add_child(Label("Drag: orbit   Scroll or pinch: zoom", anchor=Anchor.BOTTOM_LEFT, inset=10, font_size=14))
 95
 96    def on_update(self, dt):
 97        self.camera.orbit(ORBIT_SPEED * dt, 0.0)
 98
 99
100if __name__ == "__main__":
101    App(title="IBL", width=1280, height=720).run(IBLScene())