Noise¶
Perlin, Simplex, Value and Cellular noise side by side.
▶ Run in browserTags: 3d
FastNoiseLite (from simvx.core.noise) generates coherent noise: values in
roughly [-1, 1] that change smoothly from one point to the next, the raw
material for terrain, clouds, marble and dissolve masks. Pick the algorithm
with noise_type, the feature size with frequency, and layer several octaves
of it with a fractal:
gen = FastNoiseLite(seed=42, noise_type=NoiseType.CELLULAR, frequency=0.08)
gen.fractal_type = FractalType.FBM
gen.fractal_octaves = 4
get_noise_3d_array(xs, ys, zs) evaluates a whole batch of points at once and
returns a flat array. This example samples a 128 x 128 grid at depth z and
moves z forward over time, so each panel shows a slice travelling through a
3D noise field: the pattern flows rather than jumping to a new seed.
Each panel is a Mesh.plane with an unlit Material whose albedo_map is a
Texture. The pixels are regenerated into a fresh array and handed to
texture.update(pixels), which re-uploads into the same GPU slot. Cellular
noise is far slower to evaluate than the other three, so one panel is
refreshed per frame, in turn.
Source¶
1# /// simvx
2# web = { width = 1280, height = 720 }
3# ///
4"""Noise: Perlin, Simplex, Value and Cellular noise side by side.
5
6`FastNoiseLite` (from `simvx.core.noise`) generates coherent noise: values in
7roughly [-1, 1] that change smoothly from one point to the next, the raw
8material for terrain, clouds, marble and dissolve masks. Pick the algorithm
9with `noise_type`, the feature size with `frequency`, and layer several octaves
10of it with a fractal:
11
12 gen = FastNoiseLite(seed=42, noise_type=NoiseType.CELLULAR, frequency=0.08)
13 gen.fractal_type = FractalType.FBM
14 gen.fractal_octaves = 4
15
16`get_noise_3d_array(xs, ys, zs)` evaluates a whole batch of points at once and
17returns a flat array. This example samples a 128 x 128 grid at depth `z` and
18moves `z` forward over time, so each panel shows a slice travelling through a
193D noise field: the pattern flows rather than jumping to a new seed.
20
21Each panel is a `Mesh.plane` with an unlit `Material` whose `albedo_map` is a
22`Texture`. The pixels are regenerated into a fresh array and handed to
23`texture.update(pixels)`, which re-uploads into the same GPU slot. Cellular
24noise is far slower to evaluate than the other three, so one panel is
25refreshed per frame, in turn.
26"""
27
28import numpy as np
29
30from simvx.core import (
31 Anchor,
32 Camera3D,
33 CanvasLayer,
34 Label,
35 Material,
36 Mesh,
37 MeshInstance3D,
38 Node3D,
39 Text3D,
40 Texture,
41)
42from simvx.core.noise import FastNoiseLite, FractalType, NoiseType
43from simvx.graphics import App
44
45RESOLUTION = 128
46Z_SPEED = 5.0 # sample-grid units per second along the noise field's third axis
47
48# Every panel samples the same grid; only z moves.
49ROWS, COLS = np.mgrid[:RESOLUTION, :RESOLUTION]
50
51
52def noise_pixels(gen: FastNoiseLite, z: float) -> np.ndarray:
53 """One z slice of the generator's field as greyscale RGBA pixels."""
54 values = gen.get_noise_3d_array(COLS, ROWS, np.full(ROWS.shape, z)).reshape(RESOLUTION, RESOLUTION)
55 grey = ((values + 1.0) * 127.5).clip(0, 255).astype(np.uint8)
56 return np.dstack([grey, grey, grey, np.full_like(grey, 255)])
57
58
59class NoiseDemo(Node3D):
60 def on_ready(self):
61 # Looking straight down at a 2x2 grid of planes, with -Z at the top of the screen.
62 self.add_child(Camera3D(position=(0, 12, 0), look_at=(0, 0, 0), up=(0, 0, -1), fov=55))
63
64 self.z = 0.0
65 self.frame = 0
66 self.panels: list[tuple[FastNoiseLite, Texture]] = []
67 corners = [(-2.75, -2.75), (2.75, -2.75), (-2.75, 2.75), (2.75, 2.75)]
68 for noise_type, (x, z) in zip(NoiseType, corners, strict=True):
69 gen = FastNoiseLite(seed=42, noise_type=noise_type, frequency=0.08)
70 gen.fractal_type = FractalType.FBM
71 gen.fractal_octaves = 4
72 texture = Texture(noise_pixels(gen, self.z))
73 self.panels.append((gen, texture))
74 material = Material(albedo_map=texture, unlit=True)
75 self.add_child(MeshInstance3D(mesh=Mesh.plane(4.4), material=material, position=(x, 0, z)))
76 self.add_child(Text3D(text=noise_type.name.title(), position=(x, 0.1, z + 2.5), font_scale=1.5))
77
78 hud = self.add_child(CanvasLayer())
79 hud.add_child(Label("FastNoiseLite: four noise types, FBM, 4 octaves", anchor=Anchor.TOP_LEFT, inset=16))
80
81 def on_update(self, dt):
82 self.z += dt * Z_SPEED
83 gen, texture = self.panels[self.frame % len(self.panels)]
84 texture.update(noise_pixels(gen, self.z))
85 self.frame += 1
86
87
88if __name__ == "__main__":
89 App(title="Noise", width=1280, height=720).run(NoiseDemo())