跳转至

04 - 核心概念

本章介绍 LESS 的核心概念和工作流程。理解这些概念后,您将能够灵活组合各种功能来完成复杂的遥感模拟任务。

场景生命周期:build、simulate 与 rebuild

LESS 的 Scene 是一个可持续复用的数字场景,并采用 结构—光谱分离 架构。几何和加速结构可以跨多次模拟复用;传感器波段、光照和属性则按当前模拟更新。

Scene 生命周期

import less

# 1. 创建场景
scene = less.Scene()
scene.size = 50.0

# 2. 配置场景元素
scene.terrain = less.Terrain(property=less.Lambertian(reflectance=0.15))
scene.illumination = less.Illumination(source=less.Sun(zenith=30, azimuth=150), atmosphere=less.NoAtmosphere())
tree = less.Object("ash", mesh=less.examples.asset_path("FREX.obj"))
tree.set_property("leaves", less.Prospect(cab=40))
tree.set_property("stem_branch", less.Lambertian(reflectance=0.1))

scene.add(tree, positions=[[25, 25, 0]])

# 3. 模拟 → 必要时自动构建场景并获得产品
image = scene.simulate(less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=[650, 550, 450],
))

# 4. 修改参数后再次模拟(不需要 rebuild)
scene.illumination = less.Illumination(source=less.Sun(zenith=50, azimuth=200), atmosphere=less.NoAtmosphere())
image2 = scene.simulate(less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=[650, 550, 450],
))

# 5. 图像可视化
image.show() # 原始辐亮度可视化
image.to_brf().show() # 转为BRF再可视化

simulate():普通用户的默认入口

scene.simulate(...) 可以自动构建场景。如果场景尚未构建,LESS 会在第一次模拟前自动构建;后续模拟会尽可能复用已有原生场景和加速结构。

image = scene.simulate(sensor)

传感器波段会按本次模拟重新准备,因此不需要为了更换波段而提前调用 build()

build():可选的显式准备步骤

build() 是可选的显式准备步骤。它用于提前验证场景、加载几何、上传资源并构建加速结构,例如需要把构建耗时与模拟耗时分开测量时:

scene.build()
image = scene.simulate(sensor)

build() 具有幂等性:对于已经构建且没有变化的场景,再次调用通常复用已有原生场景,而不是无条件重新构建。动态实例接口 Scene.update_instances() 也要求场景已经显式构建。

rebuild():强制完整重建

rebuild() 用于强制完整重建:它会刷新原生场景表示,重新读取或上传几何,并重建加速结构。

scene.rebuild()

典型用途是外部程序修改了同一路径下的 OBJ 文件,但 LESS 无法从路径变化判断文件内容已经改变。

修改属性或光照通常不需要调用 rebuild():

scene.terrain.set_property(
    less.Lambertian(reflectance=0.2)
)
scene.illumination = less.Illumination(
    source=less.Sun(
        zenith=45,
        azimuth=scene.illumination.source.azimuth,
        spectrum=scene.illumination.source.spectrum,
        wavelengths=scene.illumination.source.wavelengths,
        irradiance=scene.illumination.source.irradiance,
    ),
    atmosphere=scene.illumination.atmosphere,
)

image = scene.simulate(sensor)

LESS 会更新相关参数,并尽可能复用已有几何和 BVH。通过公开 API 修改几何时,LESS 会维护场景 dirty 状态;通常直接再次调用 simulate() 即可。只有必须无条件刷新几何时才使用 rebuild()

操作 含义 推荐使用场景
simulate() 必要时自动构建,然后执行模拟 普通用户的默认入口
build() 按需提前构建,未变化时复用 提前验证、区分构建耗时与模拟耗时、准备动态实例更新
rebuild() 强制完整刷新几何和加速结构 同路径几何文件被外部替换等情况

推荐原则:普通模拟直接使用 simulate();需要提前准备场景时使用 build();确定必须完整刷新几何时使用 rebuild()

重复场景 (scene.repetitive)

遥感中经常需要模拟均质植被冠层(如大面积农田、森林),此时场景应该向四周无限延伸。LESS 通过 scene.repetitive 参数实现这一效果:光线在到达场景边界时会自动"折回",等效于将同一场景平铺成无限大的镶嵌阵列。

场景重复示意图

scene = less.Scene()
scene.size = 50.0          # 场景边长 50m(周期性单元)

# 默认:孤立场景(有限场景,自动启用 AABB silhouette 发射以消除边缘暗化)
scene.repetitive = False

# 当前公共模拟默认有限场景;周期 modifier 已发布(仅限平坦均质地形)
# scene.repetitive = True  # 无限平铺(wrap 上限 100)

两种模式怎么选

有限场景和周期场景的物理含义不同,选择取决于你要模拟的对象:

设置 适用 光子发射策略
scene.repetitive = False(默认) 单木、孤立地块、城市街区等真实有边界的目标 AABB silhouette:顶面 + 迎光侧面均匀发射,场景迎光的侧面也有入射光线
scene.repetitive = True 水平无限场景 已发布:射线跨边界折回(wrap 上限 100,也可显式设置整数)

True 对应光线最多折返 100 次;整数 1–4 被拒绝(原生实现会产生黑色接缝伪影)。DEM/网格地形不能重复,也不能与 terrain_following 组合。

老版本(≤0.3.1)在 repetitive=0 时只从场景顶面撒光子。斜射太阳下,原本应该从场景外"越过上空再进入冠层侧面"的那部分光被丢掉了,场景迎光边缘会出现系统性偏暗。过去我们用 repetitive=100 绕过这个问题——代价是把本来有限的场景当无限周期处理,物理上不准确。

0.3.2 起,有限场景(repetitive=False)会自动在 AABB 的 5 个朝天面(顶 + 4 侧)上采样光子:

  • 太阳光:只用 3 个迎光面(顶 + 迎光 X 侧 + 迎光 Y 侧),按 A_face · |d·n| 加权
  • 天空光:5 个朝天面,按面积加权 + 各面本地半球 cos 采样方向

对平地或场景天然扁平(D ≈ 0)时,侧面权重自动退化为 0,与老版本完全等价;有 3D 结构时,侧面发射把过去丢掉的那部分斜射光补回来。

启用 repetitive = True 后: - 射出场景边界的光线会从对面边界重新进入(周期性边界条件) - 等价于场景被无限复制并拼接 - 所选后端只存储一份场景几何,不会因周期复制而额外占用内存

当前公共规则:默认 False。均质冠层的周期基准使用 True(wrap 100)或 >=5 整数;ALS/LiDAR 飞行覆盖靠传感器布置而不是场景复制。

场景元素

Object —— 三维物体

Object 是场景中的三维物体。一个 Object 由一个或多个 组件 (component)组成,每个 component 包含一种几何表示和若干属性:

component = geometry + properties

组件目前可以使用两种几何表示:

几何表示 描述 常见用途
Mesh 保留 OBJ 中的真实三角面片 树干、枝条、显式叶片、建筑物
TurbidBoundary OBJ 只定义封闭空间,空间内用统计参数(叶面积体密度、叶倾角分布等)描述大量小叶片 大型树冠、连续冠层

两者都是普通 component,可以放在同一个 Object 中,也使用相同的 set_property()scene.add()scene.simulate()

使用 Mesh 组件

当整个 OBJ 都作为一个 Mesh 使用时,可以采用简写:

# 加载内置的欧洲白蜡树模型
tree = less.Object("ash_tree", mesh=less.examples.asset_path("FREX.obj"))

# 查看组件名称(自动从 OBJ 文件中扫描)
print(tree.components)  # ['leaves', 'stem_branch']

# 为不同组件设置不同属性
tree.set_property("leaves", less.Prospect(cab=40, car=10))
tree.set_property("stem_branch", less.Lambertian(reflectance=0.1))

也可以显式添加指定分组:

tree = less.Object("ash_tree")
tree.add_component(
    "leaves",
    less.Mesh("tree.obj", group="leaves"),
)
tree.add_component(
    "stem_branch",
    less.Mesh("tree.obj", group="stem_branch"),
)

一个 OBJ 文件可以包含多个 group。指定 group 时只读取该分组,不会重复添加 整个文件。

使用 TurbidBoundary 组件

当树冠包含大量叶片而不需要保留每片叶子的形状时,可以让 OBJ 只描述树冠的 封闭范围:

tree = less.Object("ash_tree")
tree.add_component(
    "crown",
    less.TurbidBoundary("tree.obj", group="crown"),
)
tree.set_property(
    "crown",
    less.OpticalVegetation(
        leaf=less.Prospect(cab=40, car=10),
        leaf_area_density=1.5,              # m²/m³
        leaf_angle_distribution="spherical",
        leaf_size=(0.08, 0.04),             # 长、宽,单位 m
        leaf_shape="ellipse",
    ),
)

TurbidBoundary 不是单独的模拟模式。创建场景后,仍然使用普通 OpticalImagerBRFSensorPhotosynthesisProcessEnergyBalanceProcess。LESS 会根据 component 的几何表示完成相应计算。

在一个 Object 中混合使用

tree = less.Object("tree")
tree.add_component(
    "crown",
    less.TurbidBoundary("tree.obj", group="crown"),
)
tree.add_component(
    "trunk",
    less.Mesh("tree.obj", group="trunk"),
)

tree.set_property(
    "crown",
    less.OpticalVegetation(
        leaf=less.Prospect(cab=40),
        leaf_area_density=1.5,
        leaf_angle_distribution="spherical",
    ),
)
tree.set_property("trunk", less.Lambertian(reflectance=0.12))

树冠与树干会在同一次模拟中相互遮挡,并共同参与多次散射。统计植被的边界要求、 叶片尺寸、阴阳叶和精度设置参见 统计植被的进阶设置

将物体添加到场景时,需要指定位置,还可以选择性地指定缩放和旋转:

import numpy as np

# 添加多棵树
positions = np.array([
    [10, 10, 0],
    [20, 15, 0],
    [30, 25, 0],
])
scales = [0.8, 1.0, 1.2]         # 每棵树的均匀缩放因子(XYZ 等比)
rotations = [0, 90, 180]          # 绕 Z 轴旋转角度(度)

handle = scene.add(tree, positions=positions, scales=scales, rotations=rotations)

scales 参数支持多种形式:

形式 含义 示例
float 所有实例统一均匀缩放 scales=1.5
[N] 一维数组,长度 = 实例数 每个实例各自的均匀缩放(XYZ 等比) scales=[0.8, 1.0, 1.2](3 棵树)
[N, 3] 二维数组 每个实例各自的非均匀缩放 scales=[[1,1,2],[1,1,1.5],[1,1,3]]

如果需要对所有实例统一做非均匀缩放,也用二维数组:

# 所有树 Z 方向拉伸两倍
scales = [[1.0, 1.0, 2.0]] * len(positions)

scene.add() 返回一个 InstanceHandle,可以在 build 之后用它来修改实例的变换。

Terrain —— 地表

Terrain 表示场景的地面。默认为平坦地面(z = 0),也可以加载 DEM 数据。

# 简单的平坦地面
terrain = less.Terrain()
terrain.set_property(less.Lambertian(reflectance=0.15))
scene.terrain = terrain

# 或者在创建时直接设置
scene.terrain = less.Terrain(property=less.Lambertian(reflectance=0.15))

Illumination —— 光照

Illumination 定义场景的光照条件,包括太阳直射光和天空散射光。

# Hosek–Wilkie 物理天空模型(可见光波段推荐)
scene.illumination = less.Illumination(
    source=less.Sun(zenith=30, azimuth=150),
    atmosphere=less.HosekWilkieAtmosphere(turbidity=2.5),
)

# 简化光谱大气 + 各向同性散射(宽波段快速模拟)
scene.illumination = less.Illumination(
    source=less.Sun(zenith=30, azimuth=150),
    atmosphere=less.SimpleSpectralAtmosphere(turbidity=2.5),
)

# 真空:TOA 太阳直射不衰减,也不产生天空散射
scene.illumination = less.Illumination(
    source=less.Sun(zenith=30, azimuth=150),
    atmosphere=less.NoAtmosphere(),
)

# 可选的 6S 大气模型
scene.illumination = less.Illumination(
    source=less.Sun(zenith=30, azimuth=150),
    atmosphere=less.SixSAtmosphere(
        atmosphere_profile="midlatitude_summer",
        aerosol_type="continental",
        aot550=0.2,
    ),
)

LESS 将外部辐射环境表示为“大气层顶入射源 + 大气模型”。less.Sun 只描述 太阳方向和大气层顶(TOA)法向光谱辐照度;大气模型负责计算到达地表的太阳 直射、天空散射,以及模型支持的上行传播。二者统一设置在 scene.illumination 中:

scene.illumination = less.Illumination(
    source=less.Sun(
        zenith=30,
        azimuth=150,
    ),
    atmosphere=less.Atmosphere.standard(
        "midlatitude_summer",
        aerosol="continental",
        aot550=0.10,
        ground_altitude_km=0.0,
        streams=8,
        sky_mode="anisotropic",
    ),
)

# 无需手工准备地表直射和散射光谱;simulate() 会按传感器波段求解。
image = scene.simulate(sensor)

也可以独立求解大气边界条件,以便检查光谱:

atmosphere = scene.illumination.atmosphere
result = atmosphere.solve(
    [450, 550, 650, 850],
    sun_zenith=30,
)
direct = result.direct_normal_irradiance
diffuse = result.diffuse_horizontal_irradiance

其中 direct_normal_irradiance 是太阳法向直射光谱辐照度, diffuse_horizontal_irradiance 是水平面天空散射光谱辐照度,单位均为 W m⁻² nm⁻¹less.Atmosphere 还可用于地表至观测高度的大气透过率和路径 辐亮度计算;完整参数和输出见大气模型与地气传输

模型 适用场景 说明
Atmosphere 原生分层大气和地气传输 短波 300–2500 nm;热红外 3000–14000 nm
SixSAtmosphere 6S 参数体系 需要可选依赖 less3d[atmosphere]
HosekWilkieAtmosphere RGB 可视化、可见光研究 320–720 nm 的方向天空模型
SimpleSpectralAtmosphere 快速宽光谱照明 简化直射衰减和各向同性天空光
NoAtmosphere 真空或无大气实验 TOA 直射原样到达场景,漫射为零
PrescribedAtmosphere 已知大气传输系数 用户给定直射与水平漫射传输系数

用户指定的大气传输系数

已知大气传输系数时,可以使用 PrescribedAtmosphere

scene.illumination = less.Illumination(
    source=less.Sun(
        zenith=30,
        azimuth=150,
        wavelengths=[450, 550, 650, 850],
        irradiance=[1.85, 1.88, 1.55, 1.10],  # TOA 法向光谱,W m⁻² nm⁻¹
    ),
    atmosphere=less.PrescribedAtmosphere(
        wavelengths=[450, 550, 650, 850],
        direct_beam_transmittance=[0.62, 0.70, 0.76, 0.82],
        diffuse_horizontal_transmittance=[0.18, 0.14, 0.10, 0.06],
    ),
)

两个参数的参考量不同,不能混用:

  • direct_beam_transmittance 是通常意义上的太阳直射束透过率:地表 束法向直射光谱辐照度除以 TOA 束法向光谱辐照度。
  • diffuse_horizontal_transmittance水平散射传输系数:地表水平面 散射光谱辐照度除以 TOA 太阳光谱在同一水平面上的投影辐照度。它不是 “地表光照中的散射占比”。

对于某个波长,若 TOA 束法向光谱辐照度为 E_toa_normal、太阳天顶角为 θ,则:

E_direct_normal_surface = E_toa_normal × direct_beam_transmittance
E_diffuse_horizontal_surface = E_toa_normal × cos(θ)
                               × diffuse_horizontal_transmittance

二者均为无量纲系数,可以是标量;使用光谱数组时同时提供参考 wavelengths,LESS 会插值到实际求解波长。对无内部短波源的 被动大气中,每个波段的两个系数之和不能大于 1。Sun.irradiance 始终表示 TOA 束法向光谱辐照度,不接受地表总辐照度语义。

属性系统 (Property)

属性描述 component 在不同物理过程中的行为。

  • geometry 描述地物在哪里以及采用哪种空间表示,例如 MeshTurbidBoundary
  • property 描述地物如何反射光、发射热辐射、交换能量或进行光合作用。

属性设置在 component 上。同一个 component 可以同时具有多个物理域的属性:

tree.set_property("leaves", optical_property)
tree.set_property("leaves", thermal_property)
tree.set_property("leaves", biophysical_property)
tree.set_property("leaves", physiological_property)

LESS 根据属性的 domain 自动区分它们,因此不需要在 set_property() 中重复填写属性域名称。

属性域

属性域 描述 常用属性
optical 短波辐射的反射、透射、吸收和散射 LambertianProspectFluspectOpticalVegetation
thermal 地物温度和长波发射率 ThermalProperty
microwave 微波介电、消光和散射特性 MicrowaveSoilMicrowaveVegetationMicrowaveTrunk
biophysical 显热、潜热和地表热通量交换参数 BiophysicalProperty
physiological 光合、气孔导度等植物生理参数 Farquhar

地物不需要同时设置全部属性。模拟开始前,LESS 会检查参与该过程的 component 是否具有所需属性。

波段自动插值

LESS 中所有 Property 均支持任意波段组合,无需在定义属性时预先指定波段数量。模拟时,属性会自动插值到传感器实际使用的波长,因此同一套属性可以直接用于 RGB、多光谱、高光谱等不同传感器。

光学属性

光学属性描述地物对短波辐射的反射、透射、吸收和散射。只有参与光学模拟的 component 才需要设置光学属性。

# Lambertian:标量(全波段统一反射率)
soil = less.Lambertian(reflectance=0.15)

# Lambertian:光谱曲线(自动插值到模拟波段)
import numpy as np
soil_spectral = less.Lambertian(
    reflectance=[0.10, 0.14, 0.18, 0.22, 0.30],
    wavelengths=[400,  550,  670,  800, 1600],   # nm
)

# Lambertian:半透明叶片(朗伯近似)
leaf_simple = less.Lambertian(
    reflectance=0.08,
    transmittance=0.05,
)

# Prospect:叶片光学模型(由生化参数计算光谱,覆盖 400-2500 nm)
leaf = less.Prospect(
    cab=40,      # 叶绿素含量 (μg/cm²)
    car=10,      # 类胡萝卜素 (μg/cm²)
    cw=0.012,    # 等效水厚度 (cm)
    cm=0.008,    # 干物质含量 (g/cm²)
    N=1.5,       # 叶片结构参数
)

# RPV:非朗伯 BRDF 模型
brdf_surface = less.RPV(rho0=0.1, k=0.7, theta=-0.15)

# 从内置光谱库加载
lib = less.SpectrumLibrary()
lib.list_spectra()          # 查看可用光谱
green_leaf = lib.as_property("birch_leaf_green")

对于 Mesh,光学属性作用于三角面片:

tree.add_component(
    "leaves",
    less.Mesh("tree.obj", group="leaves"),
)
tree.set_property("leaves", less.Prospect(cab=40))

对于 TurbidBoundary,封闭边界定义统计植被所在的范围。叶片光学、叶面积密度 和叶倾角分布由 OpticalVegetation 描述:

tree.add_component(
    "crown",
    less.TurbidBoundary("tree.obj", group="crown"),
)
tree.set_property(
    "crown",
    less.OpticalVegetation(
        leaf=less.Prospect(cab=40),
        leaf_area_density=1.5,              # m² leaf / m³ canopy
        leaf_angle_distribution="spherical",
    ),
)

完整的边界要求、叶片尺寸和精度设置参见 统计植被的进阶设置

热属性

热属性描述地物的温度和长波发射率,供热红外、能量平衡以及其他依赖温度的 过程使用。温度单位为 K。

# 固定温度
leaf_thermal = less.ThermalProperty(
    emissivity=0.98,
    temperature=303.15,
)

# 运行时使用空气温度
air_coupled_thermal = less.ThermalProperty(
    emissivity=0.98,
    temperature="air",
)

# 显式设置阳叶和阴叶温度;两项必须同时提供
split_leaf_thermal = less.ThermalProperty(
    emissivity=0.98,
    temperature_sunlit=308.0,
    temperature_shaded=299.0,
)

# 同一个组件可以同时设置光学和热属性
tree.set_property("leaves", leaf)           # 光学
tree.set_property("leaves", leaf_thermal)   # 热

ThermalProperty.temperature 是该 component 的温度状态,也是能量平衡的 初始迭代温度。固定温度过程直接使用它;设置为 "air" 时,运行时从 Microclimate 读取空气温度。能量平衡收敛后,求解结果成为当前面片温度,供 后续光合或热红外模拟使用。温度和发射率只在 ThermalProperty 中设置, BiophysicalProperty 不重复保存这两个量。

temperature_sunlittemperature_shaded 用于已知阴、阳叶温度的热红外 模拟,必须成对设置,单位同样为 K。对 Mesh 叶片,原生传输在每个命中点判断 其是否被太阳直接照亮,再选择相应温度;对 TurbidBoundary,模型使用阳叶 概率对两个条件温度进行处理。temperature 仍作为组件的基础温度和能量平衡 初值;显式阴、阳叶温度不会替代这个基础状态。

如果温度需要由环境条件和叶片生理过程动态决定,则不必预先猜测两个温度, 而是运行能量平衡:

microclimate = less.Microclimate(
    air_temperature=25.0,  # °C
    humidity=60.0,         # %
    wind_speed=1.5,
)
energy = scene.simulate(
    less.EnergyBalanceProcess(),
    microclimate=microclimate,
)

sunlit_temperature = energy.result.temperature_sunlit
shaded_temperature = energy.result.temperature_shaded
sunlit_fraction = energy.result.sunlit_fraction

能量平衡结果中的 temperature_sunlittemperature_shaded 是分别在阳叶与 阴叶辐射条件下求得的条件温度;sunlit_fraction 描述两者在当前空间单元中的 权重。热红外和后续耦合过程可直接使用该求解结果。

微波属性

微波属性描述地物在微波波段的介电和散射特性。只有微波模拟需要设置该属性。 同一个 component 可以同时具有光学、热学和微波属性。

tree.set_property(
    "crown",
    less.MicrowaveVegetation(
        components=[
            less.MicrowaveLeaf(
                radius=0.03,
                thickness=0.0002,
                density=100.0,
                moisture=0.5,
            ),
        ],
        lad="spherical",
    ),
)

微波属性描述散射和介电行为;需要物理温度的微波过程同时读取 ThermalProperty。如果 component 设置了 ThermalProperty,它的当前温度 会用于微波植被和树干;未设置时使用微波散射体中的温度。土壤仍可在 MicrowaveSoil 中保留表层—深层温度梯度。

被动微波的 polarization 可以是 "H""V""HV""full""full" 返回 I、Q、U、V 四个 Stokes 分量,同时通过 Tb_HTb_V 提供线偏振亮温。主动微波的 "full" 返回 HH、HV、VH、VV,两者不要混淆。

生物物理属性

BiophysicalProperty 描述地物与环境交换能量的方式,例如叶片宽度、换热面、 气孔所在面和地表热通量。它主要用于能量平衡和动态叶温计算。

tree.set_property(
    "leaves",
    less.BiophysicalProperty(
        two_sided=True,
        stomata_side="bottom",
        leaf_width=0.05,
    ),
)

树干、土壤和墙面可以使用不同的配置:

tree.set_property(
    "trunk",
    less.BiophysicalProperty(
        two_sided=False,
        stomata_side="none",
    ),
)

生理属性

生理属性描述植物如何利用吸收的光合有效辐射。Farquhar 提供 C3 植物光合和 Ball–Berry 气孔导度参数。

tree.set_property(
    "leaves",
    less.Farquhar(
        Vcmax25=60,
        Jmax25=120,
        Rd25_ratio=0.015,
        BallBerrySlope=9.0,
        BallBerryIntercept=0.01,
    ),
)

没有生理属性的 component 不参与光合计算。例如,树干可以具有光学属性和热 属性,但通常不设置 Farquhar

不同模拟过程需要哪些属性

下表中的“必须”只针对参与相应过程的 component。例如,计算树冠光合时, 树冠必须具有生理属性,但不参与光合的建筑物不需要设置 Farquhar

模拟过程 optical thermal microwave biophysical physiological
光学成像、BRF 必须
LiDAR 点云与波形 必须
短波辐射场、APAR 必须
静态光合 必须 必须 可选 必须
动态光合 必须 必须 必须 必须
能量平衡 必须 必须 必须 可选
热红外成像 必须
SIF 必须 按温度模式 按温度模式 必须
被动微波 必须 必须
主动微波 可选 必须

“可选”表示过程可以使用该属性提供的附加参数,但没有该属性时仍可采用简化 计算。“按温度模式”表示固定温度计算只需要热属性,动态温度计算还需要生物 物理属性。

完整的叶片 component

下面的叶片 component 可以用于光学成像、静态光合和动态能量平衡:

tree.add_component(
    "leaves",
    less.Mesh("tree.obj", group="leaves"),
)

tree.set_property(
    "leaves",
    less.Prospect(cab=40, car=10, cw=0.012, cm=0.008),
)
tree.set_property(
    "leaves",
    less.ThermalProperty(emissivity=0.98, temperature=303.15),
)
tree.set_property(
    "leaves",
    less.BiophysicalProperty(
        two_sided=True,
        stomata_side="bottom",
        leaf_width=0.05,
    ),
)
tree.set_property(
    "leaves",
    less.Farquhar(Vcmax25=60, Jmax25=120),
)

属性按物理域独立设置,因此可以只修改温度而不改变光学参数,也可以只修改 叶绿素含量而不改变能量交换参数。

传感器 (Sensor / Imager)

传感器由两部分组成:投影方式光谱波段

投影方式

# 正射投影(默认垂直观测)
ortho = less.Orthographic(image_size=512)

# 透视投影(相机)
persp = less.Perspective(
    resolution=1024, fov=35,
    position=(25, 60, 20),     # 相机位置
    target=(25, 25, 5),        # 观察目标点
)

# 鱼眼投影(半球观测)
fisheye = less.Fisheye(image_size=512, fov=180)

光谱波段

# 自定义离散波段
bands = [475, 560, 668, 842]

# 预定义卫星传感器及其 SRF 内部采样间隔
sentinel2 = less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=less.Sentinel2A(),
    spectral_resolution=5,
)
landsat8 = less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=less.Landsat8_OLI(),
    spectral_resolution=5,
)

# 高光谱
bands = less.Hyperspectral(start=400, stop=2500, step=10)

组合为 Imager

# 光学成像仪 = 投影 + 波段
sensor = less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=[650, 550, 450],      # RGB
    quality=256,                 # 光子数(越大越准,越慢)
)

# 热红外成像仪
sensor = less.ThermalImager(
    less.Orthographic(image_size=512),
    bands=[10600],              # 10.6 μm
)

输出产品 (Product)

所有 simulate() 调用都返回一个 Product 对象:

image = scene.simulate(sensor)

# 保存为文件
image.save("output.png")      # PNG(RGB 自动色调映射)
image.save("output.tif")      # GeoTIFF(保留原始辐射值)

# 获取原始数据(NumPy 数组)
data = image.data              # shape: (height, width, bands)
print(data.shape, data.dtype)

不同传感器返回不同类型的产品:

传感器 产品类型 数据内容
OpticalImager ImageProduct 各波段辐亮度/反射率
ThermalImager ImageProduct 热辐射亮温
BRFSensor BRFProduct 多角度反射率
LiDARSurvey PointCloud xyz 坐标 + 强度
RadiationFieldSensor RadiationFieldProduct 逐叶片辐照度

LAI 测量

LESS 可以直接从三维场景计算叶面积指数 (LAI):

lai = scene.measure(less.LAIMeasurement())
print(f"场景 LAI = {lai:.2f}")

# 按组件分组
lai_detail = scene.measure(less.LAIMeasurement(group_by='component'))
print(lai_detail)  # {'leaves': 3.2, 'stem_branch': 0.8}

同一个场景可以同时包含 MeshTurbidBoundary。显式面元按三角形的单面面积计算; 浑浊介质按“场景范围内的有效体积 × leaf_area_density”计算。边界壳本身不属于叶面积。 如果浑浊介质跨出有限场景,只统计裁剪后留在场景内的部分;周期场景则先把跨边界部分 折回基础单元。多个介质发生空间重叠时,各自代表的叶面积相加。

property_filter 对两种表示使用相同语义。例如 property_filter=less.Prospect 既匹配显式的 PROSPECT 叶片,也匹配 OpticalVegetation(leaf=less.Prospect(...)) 中的代表叶片。

概念总结

LESS 核心类关系总览

下一步

掌握了这些核心概念,让我们开始实际模拟:

相关 API

  • less.Scene.simulate():按需自动构建并执行模拟
  • less.Scene.build():可选的提前准备步骤
  • less.Scene.rebuild():强制刷新几何和加速结构
  • less.Scene.update_instances():已构建场景中的动态实例更新
  • less.Sceneless.Objectless.Terrain
  • less.Meshless.TurbidBoundary
  • less.Lambertianless.Prospectless.RPV
  • less.Illuminationless.Sunless.NoAtmosphere
  • less.Atmosphereless.AtmosphereResultless.SixSAtmosphere
  • less.PrescribedAtmosphereless.SimpleSpectralAtmosphereless.HosekWilkieAtmosphere
  • less.ThermalPropertyless.Microclimateless.EnergyBalanceProcess
  • less.OpticalImagerless.ThermalImagerless.LAIMeasurement