14 - Heterogeneous volume fields¶
VolumeField describes a medium whose density and temperature vary in space, such as a smoke plume, haze or flames. The data can come from NumPy arrays or be imported from OpenVDB files.
From arrays¶
Arrays are ordered [z, y, x], with values at voxel centres. density_scale is a dimensionless density factor that multiplies the spectral absorption and scattering coefficients. bounds_min and bounds_max give the extent of the data in the component's local coordinates (m):
import numpy as np
import less
z, y, x = np.meshgrid(np.linspace(0, 1, 16), np.linspace(-1, 1, 16),
np.linspace(-1, 1, 16), indexing="ij")
density = np.exp(-(x ** 2 + y ** 2) / 0.2) * (1 - z) # dense in the centre, thinning upwards
temperature = 300 + 500 * density # K
field = less.VolumeField(
density_scale=density.astype(np.float32),
bounds_min=(-10, -10, 0),
bounds_max=(10, 10, 20),
temperature_field=temperature.astype(np.float32),
wavelengths=[450, 12000],
absorption=[0.08, 0.25], # values at density_scale = 1 (m⁻¹)
scattering=[0.22, 0.01],
g=[0.65, 0.2],
temperature=300, # temperature used where no field is given
)
Assign it to a closed boundary that uses the same local coordinates as the data, then place it like any other object:
scene = less.Scene()
scene.size = 100
scene.terrain = less.Terrain(property=less.Lambertian(reflectance=0.15))
scene.terrain.set_property(less.ThermalProperty(temperature=300, emissivity=0.95))
scene.illumination = less.Illumination(
source=less.Sun(zenith=30), atmosphere=less.NoAtmosphere())
smoke = less.Object("smoke")
smoke.add_component("domain", less.TurbidBoundary(less.asset_path("volume_field_domain.obj")))
smoke.set_property("domain", field)
scene.add(smoke, positions=[(50, 50, 0)])
Density and temperature are interpolated trilinearly between voxels; attenuation, Henyey–Greenstein scattering, multiple scattering and thermal emission in the medium are all computed.
Visible and thermal imaging¶
projection = less.Perspective(image_size=64, fov=35,
position=(70, -20, 25), target=(50, 50, 8))
visible = scene.simulate(less.OpticalImager(projection, bands=[450, 550, 650], quality=32))
thermal = scene.simulate(less.ThermalImager(projection, bands=[3900, 10500], quality=32,
mode="property"))
visible.save("smoke_visible.tif")
thermal.save("smoke_thermal.tif")
Scenes with volume fields can be simulated with optical and thermal imaging.
Importing OpenVDB¶
Install the OpenVDB Python bindings (openvdb or pyopenvdb) in the Python environment where you build scenes, then read a file with less.VolumeField.from_openvdb(path, ...):
| Parameter | Example |
|---|---|
density_grid, temperature_grid |
"density", "temperature" |
density_scale |
0.4 |
temperature_scale, temperature_offset |
900.0, 300.0 |
absorption, scattering, g |
0.25, 0.35, 0.6 |
- The OpenVDB grid transform sets the position and scale of the data and must be aligned with the coordinate axes; rotated or sheared grids need resampling first;
- temperature is converted as
kelvin = value × temperature_scale + temperature_offset; use the defaults (1 and 0) when the data are already in kelvin; - after import the data are stored in the LESS scene, so later simulations and scene loading no longer need OpenVDB.
max_voxelslimits the number of imported voxels to avoid filling memory.
Exporting from Blender¶
- Bake the smoke and fire simulation;
- keeping the object transform unchanged, export the fluid domain as OBJ for the
TurbidBoundary; - export the density and temperature grids as VDB;
- set the unit scale so that 1 m in Blender is 1 m in LESS;
- Mantaflow temperatures are usually relative; choose
temperature_scaleandtemperature_offsetfrom the ambient and flame temperatures.
Emissive material colours are not used as light sources; thermal emission is determined entirely by the temperature field and the absorption coefficient.
Built-in examples¶
python -m less.examples.volume_field --backend embree --image-size 64 --quality 16
python -m less.examples.forest_fire --backend optix --image-size 64 --quality 16 --output-dir forest-fire
The forest-fire example combines healthy and burnt trees, the ground, scattering smoke and hot gas, and writes RGB, 3.9 µm mid-wave infrared and 10.5 µm long-wave infrared images.