terrain op• Data kinds: normalmap × pointmap → normalmap
• Call: 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' (implementation को सीधे call करना हो तो 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', registry से लेना हो तो ops3d.get("bump_normals_fbm"))
seeded multi-octave height field के *gradient* से normal map को perturb करता है।
यह sub-facet relief है जिसे geometry displace नहीं कर सकती → unit normals (H, W, 3)।
h(x) = Σ_k A_k n_k(x) वही value-noise field है जिससे mesh_displace_spectrum displace करेगा (वही seed ⇒ वही lattice), इसलिए displacement के band gate ने जिन octaves को अस्वीकार किया उन्हें देने पर shading facet size से नीचे भी *वही* amplitude spectrum जारी रखती है (नकली sandpaper नहीं: wavelength λ पर amplitude A वाला octave normal को अधिकतम लगभग 2πA/λ तक झुकाता है)। bumped normal normalize(n − ∇_t h) है (surface पर height field का first-order shading normal; ∇_t = central differences से tangential gradient, step = min(λ)/64)। positions world coordinates (H, W, 3) हैं (NaN = background, जिसे छुआ नहीं जाता); अगर rotation (3×3, world → normal frame, जैसे pose[:3,:3]) दिया गया हो तो normals उसी frame में लिए जाते हैं।
local_edge (optional, हर pixel के नीचे mesh की local edge length का (H, W) map) bump को displacement के band gate का ठीक complement बनाता है: octave k को weight 1 − gate_k से bump किया जाता है, जहाँ gate_k उन्हीं nyquist / fade के साथ displacement_band_weights का नियम है — इसलिए जिस octave को geometry किसी pixel पर पहले से उठा चुकी है वह दो बार नहीं जुड़ता, और जिसे वह नहीं उठा सकी उसे bump पूरी तरह देता है। इसके बिना हर octave पूरे amplitude पर bump होता है। deterministic; fail-closed।
input और output दोनों (H, W, 3) images हैं। जिन pixels के positions में NaN है, या जिनके normal की length 1e-12 या उससे कम है, उन्हें background मानकर मूल value वैसी ही रखी जाती है (render3d.render_mesh के खाली pixels)। अगर एक भी valid pixel न हो तो input की copy लौटाई जाती है।
• wavelengths / amplitudes: mesh (= positions) की units में एक ही length (1 से 32) की positive sequences। mismatch या negative पर ValueError।
• rotation: 3×3 (world → normal का coordinate frame)। देने पर normals को N @ R से world में लौटाकर gradient लिया जाता है, फिर @ R.T से मूल frame में लौटाया जाता है। render_mesh के normals camera space में हैं, इसलिए pose[:3,:3] दें। non-finite या गलत shape पर ValueError।
• step: central difference का step (default min(wavelengths)/64)। 0 या उससे कम पर ValueError।
• local_edge: (H, W) का edge-length map। shape normals से अलग हो, या valid pixels के नीचे non-positive या non-finite मान हों तो ValueError।
height field को 3 axes के central differences से differentiate किया जाता है, इसलिए हर 1 pixel पर 6 evaluations होते हैं। normal का झुकाव लगभग max |∇h| ≈ 2π A_k / λ_k होता है, इसलिए amplitude जितना wavelength के करीब आता है normal उतना ज़्यादा लेटता है (default A/λ = 0.1 के आसपास)। result को phong_shade / brdf_hapke आदि के normal map input में सीधे दिया जा सकता है।
• Sample data catalog (DL URL / license) — 2-D के लिए skimage.data (BSD/public) + synthetic, 3-D के लिए real data sources (Stanford/PDS आदि) के DL URL।
• Operators का स्रोत और संदर्भ — इस op family के मूल research/methods के स्रोत।
• itokawa_regolith_hero — py -3.11 examples_3d/itokawa_regolith_hero.py
normalmap को input के रूप में लेते हैं)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. यह per-op note tools/opdocs.py md से अपने-आप generate होता है (हाथ से edit न करें)।*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.