跳转至

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 参数保证任意两个点之间的距离不小于该值,避免树木重叠。

网格放置

# 规则网格,带可选抖动
positions = less.place.grid(scene, spacing=5.0, jitter=0.5)

随机缩放和旋转

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 完成!")

配套脚本:scripts/07_forest_scene.py

代码解读

多树种场景的构建策略

关键技巧:为每种叶色创建一个独立的 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 - 核心概念。当前公共工作流默认 FalseTrue 表示无限平铺(wrap 上限 100),整数 >=5 为显式上限,1–4 被拒绝;DEM/网格地形不可重复,且不能与 terrain_following 同时使用。0.3.2 之前习惯用 repetitive=100 绕开的斜照边缘暗化问题,在有限场景下现在已经由 silhouette 发射自动解决,无需再依赖周期边界。

下一步

相关 API

  • less.Sceneless.Objectless.Terrain
  • less.place.poisson_disk()less.place.grid()
  • less.Prospectless.Lambertian
  • less.LAIMeasurementless.Orthographicless.Perspectiveless.OpticalImager