Render Layers

choose which objects a camera draws.

▶ Run in browser

Tags: 3d

Every 3D node has a render_layer bitmask saying which of the 32 render layers it belongs to, and every camera has a cull_mask saying which layers it draws. A node is drawn when the two share a bit. A node starts on layer 0 (render_layer=1) and a camera draws every layer, so nothing is hidden until you say so:

MeshInstance3D(mesh=Mesh.box(), render_layer=1 << 2)   # layer 2 only
camera.cull_mask ^= 1 << 2                              # show or hide layer 2

Both are plain integers, so a node can sit on several layers at once: the ground here is on layers 0, 1 and 2 (0b111) and stays visible whatever the camera hides. Layers hide objects from one camera without removing them from the scene, which is how a minimap, a first-person view without the player’s own body, or an editor-only gizmo is built.

The red cubes are on layer 0, the green spheres on layer 1 and the blue cubes on layer 2. Each button is Button(action=name), so it presses the same action as its number key and one @on_input handler serves both; the handler flips the layer’s bit in the camera’s cull_mask and shows the result on the button. The camera is an OrbitCamera3D, which orbits on a drag by itself; on_update adds keyboard orbit.

Controls: 1 / 2 / 3 / buttons - Show or hide the red, green or blue layer A / D, Left / Right - Orbit the camera Drag - Orbit the camera

Source

  1"""Render Layers: choose which objects a camera draws.
  2
  3Every 3D node has a ``render_layer`` bitmask saying which of the 32 render
  4layers it belongs to, and every camera has a ``cull_mask`` saying which layers
  5it draws. A node is drawn when the two share a bit. A node starts on layer 0
  6(``render_layer=1``) and a camera draws every layer, so nothing is hidden until
  7you say so:
  8
  9    MeshInstance3D(mesh=Mesh.box(), render_layer=1 << 2)   # layer 2 only
 10    camera.cull_mask ^= 1 << 2                              # show or hide layer 2
 11
 12Both are plain integers, so a node can sit on several layers at once: the
 13ground here is on layers 0, 1 and 2 (``0b111``) and stays visible whatever the
 14camera hides. Layers hide objects from one camera without removing them from
 15the scene, which is how a minimap, a first-person view without the player's own
 16body, or an editor-only gizmo is built.
 17
 18The red cubes are on layer 0, the green spheres on layer 1 and the blue cubes on
 19layer 2. Each button is ``Button(action=name)``, so it presses the same action
 20as its number key and one ``@on_input`` handler serves both; the handler flips
 21the layer's bit in the camera's ``cull_mask`` and shows the result on the
 22button. The camera is an ``OrbitCamera3D``, which orbits on a drag by itself;
 23``on_update`` adds keyboard orbit.
 24
 25Controls:
 26    1 / 2 / 3 / buttons   - Show or hide the red, green or blue layer
 27    A / D, Left / Right   - Orbit the camera
 28    Drag                  - Orbit the camera
 29"""
 30
 31import math
 32
 33from simvx.core import (
 34    Anchor,
 35    Axis1D,
 36    Button,
 37    CanvasLayer,
 38    DirectionalLight3D,
 39    HBoxContainer,
 40    Input,
 41    Key,
 42    Label,
 43    Material,
 44    Mesh,
 45    MeshInstance3D,
 46    Node3D,
 47    OrbitCamera3D,
 48    on_input,
 49)
 50from simvx.graphics import App
 51
 52# Action name -> (render layer, button label, colour, mesh).
 53LAYERS = {
 54    "red": (0, "Red cubes", (0.9, 0.15, 0.1), Mesh.box(0.8)),
 55    "green": (1, "Green spheres", (0.1, 0.85, 0.2), Mesh.sphere(0.6)),
 56    "blue": (2, "Blue cubes", (0.1, 0.2, 0.9), Mesh.box(0.8)),
 57}
 58
 59
 60class RenderLayers(Node3D):
 61    input_context = {
 62        "red": [Key.KEY_1],
 63        "green": [Key.KEY_2],
 64        "blue": [Key.KEY_3],
 65        "orbit": [Axis1D(low=Key.A, high=Key.D), Axis1D(low=Key.LEFT, high=Key.RIGHT)],
 66    }
 67
 68    def on_ready(self):
 69        self.camera = self.add_child(
 70            OrbitCamera3D(pivot=(0, 0.5, 0), distance=14, yaw=math.radians(30), pitch=math.radians(-32), fov=60)
 71        )
 72        self.add_child(DirectionalLight3D(colour=(1.0, 0.95, 0.9), intensity=1.2, direction=(-0.4, -1.0, -0.6)))
 73        ground = Material(colour=(0.3, 0.3, 0.3))
 74        self.add_child(
 75            MeshInstance3D(mesh=Mesh.box((20, 0.1, 20)), material=ground, position=(0, -0.05, 0), render_layer=0b111)
 76        )
 77
 78        hud = self.add_child(CanvasLayer())
 79        bar = hud.add_child(HBoxContainer(anchor=Anchor.TOP_LEFT, inset=12))
 80        self.buttons = {}
 81        for row, (name, (layer, label, colour, mesh)) in enumerate(LAYERS.items()):
 82            material = Material(colour=colour)
 83            for i in range(3):
 84                position = (-4 + i * 2, 1.0, -2 + row * 2)
 85                self.add_child(MeshInstance3D(mesh=mesh, material=material, position=position, render_layer=1 << layer))
 86            self.buttons[name] = bar.add_child(Button(label, action=name, checked=True))
 87
 88        # Example help overlay: not part of the technique shown.
 89        hud.add_child(Label("A/D or drag: orbit", anchor=Anchor.BOTTOM_LEFT, inset=12, font_size=14))
 90
 91    @on_input(("red", "green", "blue"))
 92    def toggle_layer(self, event):
 93        bit = 1 << LAYERS[event.action][0]
 94        self.camera.cull_mask ^= bit
 95        self.buttons[event.action].checked = bool(self.camera.cull_mask & bit)
 96
 97    def on_update(self, dt):
 98        self.camera.orbit(Input.value("orbit") * math.radians(60) * dt, 0)
 99
100
101if __name__ == "__main__":
102    App(title="Render Layers", width=1280, height=720).run(RenderLayers())