跳转至

14 - 非均匀体数据场

VolumeField 描述密度和温度随位置变化的介质,例如烟羽、霾或火焰。数据可以来自 NumPy 数组,也可以从 OpenVDB 文件导入。

由数组创建

数组的维度顺序为 [z, y, x],数值位于体素中心。density_scale 是无量纲的密度倍数,与光谱吸收系数和散射系数相乘。bounds_min 和 bounds_max 给出数据在组件局部坐标中的范围(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)          # 中心浓、向上变淡
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],      # density_scale = 1 时的值(m⁻¹)
    scattering=[0.22, 0.01],
    g=[0.65, 0.2],
    temperature=300,              # 没有温度场时使用的温度
)

把它赋给一个封闭边界,边界与数据范围使用同一个局部坐标系。之后像普通物体一样放置:

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)])

密度和温度在体素之间三线性插值,介质中的衰减、Henyey–Greenstein 散射、多次散射和热发射都会计算在内。

可见光与热红外成像

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")

含体数据场的场景可以用光学成像和热红外成像模拟。

导入 OpenVDB

在制作场景的 Python 环境中安装 OpenVDB 的 Python 绑定(openvdb 或 pyopenvdb),然后用 less.VolumeField.from_openvdb(path, ...) 读取:

参数 示例
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
  • OpenVDB 网格的坐标变换决定数据的位置和尺度,要求各轴与坐标轴对齐;有旋转或剪切的网格需要先重采样;
  • 温度按 开尔文 = 原值 × temperature_scale + temperature_offset 换算;原数据已是开尔文时使用默认值(1 和 0);
  • 导入后数据保存在 LESS 场景中,之后的模拟和场景加载不再需要 OpenVDB。max_voxels 限制导入的体素总数,避免占满内存。

从 Blender 导出

  1. 烘焙烟火模拟;
  2. 保持物体变换不变,把流体域导出为 OBJ,作为 TurbidBoundary;
  3. 把密度和温度网格导出为 VDB;
  4. 设置单位比例,使 Blender 中的 1 m 对应 LESS 中的 1 m;
  5. Mantaflow 的温度通常是相对值,根据环境温度和火焰温度设置 temperature_scale 与 temperature_offset。

材质的发光颜色不会被当作光源,热发射完全由温度场和吸收系数决定。

内置示例

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

森林火灾示例包括健康和烧焦的树木、地表、散射的烟和高温气体,输出 RGB、3.9 µm 中波红外和 10.5 µm 长波红外影像。

下一步