terrain op• Datenarten: normalmap × pointmap → normalmap
• Aufruf: import fullseye as fs; fs.ledger.bump_normals_fbm(normals, positions, wavelengths=(0.002, 0.001), amplitudes=(0.0002, 0.00012), *, seed: 'int' = 0, rotation=None, step=None, local_edge=None, nyquist: 'float' = 2.0, fade: 'float' = 1.0) -> 'np.ndarray' (die Implementierung direkt: import render3d; render3d.bump_normals_fbm(normals, positions, wavelengths=(0.002, 0.001), amplitudes=(0.0002, 0.00012), *, seed: 'int' = 0, rotation=None, step=None, local_edge=None, nyquist: 'float' = 2.0, fade: 'float' = 1.0) -> 'np.ndarray'; aus dem Register: ops3d.get("bump_normals_fbm"))
Stört eine Normalenkarte mit dem *Gradienten* eines geseedeten mehroktavigen Höhenfeldes.
Das ist Relief unterhalb der Facettengröße, das die Geometrie nicht verschieben kann → Einheitsnormalen (H, W, 3).
h(x) = Σ_k A_k n_k(x) ist dasselbe Value-Noise-Feld, mit dem mesh_displace_spectrum verschieben würde (gleiches seed ⇒ gleiches Gitter); übergibt man also die Oktaven, die das Band-Gate der Verschiebung abgewiesen hat, setzt die Schattierung *dasselbe* Amplitudenspektrum unterhalb der Facettengröße fort (kein vorgetäuschtes Schleifpapier: eine Oktave der Amplitude A bei Wellenlänge λ kippt die Normale um höchstens etwa 2πA/λ). Die gebumpte Normale ist normalize(n − ∇_t h) (Schattierungsnormale erster Ordnung eines Höhenfelds über der Oberfläche; ∇_t = tangentialer Gradient per zentraler Differenzen mit step = min(λ)/64). positions sind Weltkoordinaten (H, W, 3) (NaN = Hintergrund, bleibt unberührt); ist rotation (3×3, Welt → Normalensystem, z. B. pose[:3,:3]) gegeben, werden die Normalen in diesem System genommen.
local_edge (optionale (H, W)-Karte der lokalen Mesh-Kantenlänge unter jedem Pixel) macht den Bump zum exakten Komplement des Band-Gates der Verschiebung: Oktave k wird mit Gewicht 1 − gate_k gebumpt, wobei gate_k die Regel von displacement_band_weights mit denselben nyquist / fade ist -- eine Oktave, die die Geometrie an einem Pixel bereits trägt, wird also nicht doppelt addiert, und eine, die sie nicht tragen konnte, liefert der Bump vollständig. Ohne sie wird jede Oktave mit voller Amplitude gebumpt. Deterministisch; fail-closed.
Ein- und Ausgabe sind beide (H, W, 3)-Bilder. Pixel, deren positions NaN enthalten oder deren Normalenlänge höchstens 1e-12 beträgt, bleiben als Hintergrund unverändert (leere Pixel von render3d.render_mesh). Gibt es kein einziges gültiges Pixel, wird eine Kopie der Eingabe zurückgegeben.
• wavelengths / amplitudes: positive Folgen gleicher Länge (1 bis 32) in den Einheiten des Meshs (= positions). Abweichung oder negative Werte lösen ValueError aus.
• rotation: 3×3 (Welt → Koordinatensystem der Normalen). Falls gegeben, werden die Normalen mit N @ R in die Welt zurückgeführt, dort der Gradient genommen und mit @ R.T ins ursprüngliche System zurückgebracht. Die Normalen von render_mesh liegen im Kameraraum, daher pose[:3,:3] übergeben. Nicht endlich oder falsche Form löst ValueError aus.
• step: Schrittweite der zentralen Differenzen (Standard min(wavelengths)/64). 0 oder weniger löst ValueError aus.
• local_edge: Kantenlängenkarte (H, W). Weicht die Form von den Normalen ab oder liegen unter gültigen Pixeln nicht positive oder nicht endliche Werte, wird ValueError ausgelöst.
Das Höhenfeld wird per zentraler Differenzen in 3 Achsen differenziert, daher 6 Auswertungen pro 1 Pixel. Die Neigung der Normalen beträgt etwa max |∇h| ≈ 2π A_k / λ_k; je näher die Amplitude an die Wellenlänge kommt, desto stärker kippt die Normale (Standard ist etwa A/λ = 0.1). Das Ergebnis kann direkt an den Normalenkarten-Eingang von phong_shade / brdf_hapke usw. übergeben werden.
• Katalog der Beispieldaten (Download-URLs / Lizenzen) — 2-D nutzt skimage.data (BSD/Public Domain) plus synthetische Bilder, 3-D nennt Download-URLs echter Datenquellen (Stanford, PDS, …).
• Herkunft und Literatur der Operatoren — die Quellen der Forschung/Verfahren, auf denen diese Operatorfamilie beruht.
• itokawa_regolith_hero — py -3.11 examples_3d/itokawa_regolith_hero.py
normalmap als Eingabe)fuse_to_voxel · render_shaded · phong_shade · matcap_shade · brdf_lommel_seeliger · brdf_hapke · integrate_normals · render_lambertian
terrain)mesh_displace_fbm · terrain_region_mask · mesh_scatter_boulders · mesh_edge_lengths · mesh_subdivide · displacement_band_weights · mesh_displace_spectrum
*Provenance: render3d.py — 3D Operator-Registry. Diese Notiz wird von tools/opdocs.py md erzeugt (nicht von Hand bearbeiten).*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.