07 - 构建森林场景¶
本章介绍如何使用 LESS 构建一个逼真的森林场景,包括随机放置树木、多树种配置、以及 LAI 测量。
场景设计¶
我们将构建一个 50m × 50m 的落叶林场景,使用内置的欧洲白蜡树 (FREX) 模型。场景包含:
- 60 棵树,随机分布
- 5 种秋季叶色(模拟不同个体的物候差异)
- 随机的大小和朝向
放置工具 (less.place)¶
LESS 提供了一系列放置工具,方便生成自然分布的植物位置:
随机放¶
import less
scene = less.Scene()
scene.size = 50.0
# Poisson 盘采样:保证最小间距
positions = less.place.random(scene, n=60, min_dist=3.0, seed=42)
print(f"生成 {len(positions)} 个位置")
min_dist 参数保证任意两个点之间的距离不小于该值,避免树木重叠。
网格放置¶
随机缩放和旋转¶
n = len(positions)
# 均匀缩放:0.6x ~ 1.5x
scales = less.place.uniform_scale(n, lo=0.6, hi=1.5, seed=42)
# 随机旋转:0° ~ 360°
rotations = less.place.random_rotation(n, seed=42)
完整示例:秋季森林¶
import less
import numpy as np
# ── 创建场景 ──────────────────────────────────────────────────
scene = less.Scene()
scene.size = 50.0
# 地面
scene.terrain = less.Terrain(
property=less.Lambertian(reflectance=0.08)
)
# 光照
scene.illumination = less.Illumination(source=less.Sun(zenith=40, azimuth=150), atmosphere=less.NoAtmosphere())
# ── 定义 5 种秋季叶色 ────────────────────────────────────────
autumn_colors = {
"golden_yellow": less.Prospect(cab=10, car=20, canth=0, cbrown=0.15, cw=0.012, cm=0.009, N=1.8),
"orange": less.Prospect(cab=3, car=12, canth=8, cbrown=0.25, cw=0.010, cm=0.008, N=1.7),
"red": less.Prospect(cab=2, car=5, canth=30, cbrown=0.15, cw=0.011, cm=0.009, N=1.8),
"dark_red": less.Prospect(cab=1, car=3, canth=40, cbrown=0.35, cw=0.009, cm=0.010, N=2.0),
"yellow_green": less.Prospect(cab=20, car=15, canth=0, cbrown=0.05, cw=0.013, cm=0.009, N=1.7),
}
# ── 为每种颜色创建一组树木 ────────────────────────────────────
mesh_path = less.examples.asset_path("FREX.obj")
rng = np.random.RandomState(42)
trees_per_color = 12
for color_name, leaf_prop in autumn_colors.items():
# 创建物体并设置属性
tree = less.Object(f"ash_{color_name}", mesh=mesh_path)
tree.set_property("leaves", leaf_prop)
tree.set_property("stem_branch", less.Lambertian(reflectance=0.06))
# 随机位置、大小、朝向
n = trees_per_color + rng.randint(-2, 3)
px = rng.uniform(2, 48, n)
py = rng.uniform(2, 48, n)
positions = np.column_stack([px, py, np.zeros(n)])
scales = rng.uniform(0.6, 1.5, n)
rotations = rng.uniform(0, 360, n)
scene.add(tree, positions=positions, scales=scales, rotations=rotations)
# ── 构建 ─────────────────────────────────────────────────────
lai = scene.measure(less.LAIMeasurement())
print(f"场景 LAI = {lai:.2f}")
# 按组件分组
lai_detail = scene.measure(less.LAIMeasurement(group_by='component'))
print(f"叶片 LAI = {lai_detail.get('leaves', 0):.2f}")
print(f"枝干 LAI = {lai_detail.get('stem_branch', 0):.2f}")
# ── 正射 RGB 图像 ────────────────────────────────────────────
sensor = less.OpticalImager(
less.Orthographic(image_size=512),
bands=[630, 532, 465], # 秋季 RGB
quality=128,
)
image = scene.simulate(sensor)
image.save("07_autumn_forest_nadir.png")
# ── 透视相机(斜视角度)────────────────────────────────────
sensor_persp = less.OpticalImager(
less.Perspective(
resolution=1024, fov=35,
position=(25, 60, 45), # 南侧上方
target=(25, 25, 6), # 看向场景中心稍高处
),
bands=[630, 532, 465],
quality=128,
)
image_persp = scene.simulate(sensor_persp)
image_persp.save("07_autumn_forest_perspective.png")
print("Tutorial 07 完成!")
代码解读¶
多树种场景的构建策略¶
关键技巧:为每种叶色创建一个独立的 Object,然后批量添加。LESS 内部会高效管理实例化——相同几何但不同属性的物体共享网格数据。
for color_name, leaf_prop in autumn_colors.items():
tree = less.Object(f"ash_{color_name}", mesh=mesh_path)
tree.set_property("leaves", leaf_prop)
# ... 添加到场景
Perspective 透视相机¶
透视投影模拟人眼或相机的成像方式,具有近大远小的效果:
less.Perspective(
resolution=1024, # 输出图像像素数
fov=35, # 视场角(度)
position=(25, 60, 45), # 相机位置 (x, y, z)
target=(25, 25, 6), # 观察目标点
)
position:相机在场景中的 3D 位置target:相机看向的目标点fov:视场角,越大视野越广
LAI 测量¶
LAIMeasurement 通过分析场景中所有三角面片的面积来计算叶面积指数:
# 总 LAI
lai = scene.measure(less.LAIMeasurement())
# 按组件分组(区分叶片和枝干)
lai = scene.measure(less.LAIMeasurement(group_by='component'))
# 按物体分组(区分不同树种)
lai = scene.measure(less.LAIMeasurement(group_by='object'))
场景重复 (Repetitive)¶
对于平坦、均质并且需要表示无限平铺的样地,可以启用周期边界:
# 孤立有限场景(默认):AABB silhouette 发射自动处理斜射边缘
scene.repetitive = False
# scene.repetitive = True # 无限平铺(wrap 上限 100,仅限平坦均质地形)
两种模式的物理区别参见 04 - 核心概念。当前公共工作流默认 False;True 表示无限平铺(wrap 上限 100),整数 >=5 为显式上限,1–4 被拒绝;DEM/网格地形不可重复,且不能与 terrain_following 同时使用。0.3.2 之前习惯用 repetitive=100 绕开的斜照边缘暗化问题,在有限场景下现在已经由 silhouette 发射自动解决,无需再依赖周期边界。
下一步¶
相关 API¶
less.Scene、less.Object、less.Terrainless.place.poisson_disk()、less.place.grid()less.Prospect、less.Lambertianless.LAIMeasurement、less.Orthographic、less.Perspective、less.OpticalImager