跳转至

10 - BRDF 模拟

本章介绍如何使用 LESS 进行二向反射率 (BRF/BRDF) 模拟,分析地表的方向反射特性。

什么是 BRDF?

双向反射分布函数 (BRDF) 描述了地表对不同入射方向和不同观测方向的反射特性。在遥感中,通常使用 二向反射率因子 (BRF) 来表征——它是特定几何条件下的地表反射率与理想朗伯体的比值。

BRDF 的变化受三维植被结构影响:

  • 热点效应 (Hot Spot):观测方向与太阳方向重合时,反射率显著增大(因为此时看不到阴影)
  • 碗形效应 (Bowl Shape):植被冠层通常在大观测天顶角时反射率更高
  • 前向/后向散射:叶片的散射特性导致前向和后向不对称

BRFSensor —— 多方向一次出结果

BRFSensor 是 BRF 模拟的首选传感器:

  • 传一组方向,单次 simulate 全部跑完(前向光子追踪,所有方向共享同一批光子,效率高)
  • 不出图像,只出每个方向上场景平均的 BRF 值
  • 不依赖 scene.size 的相机布点,对大角度观测也无几何边界问题
import less

sensor = less.BRFSensor(
    bands=[650, 850],
    directions=[(0, 0), (30, 180), (60, 0)],   # (zenith°, azimuth°) 列表
    num_photons=50_000_000,                    # 默认 5e7;噪声大就加
)

返回的是 BRFProduct

属性 形状 说明
.brf [n_dirs, n_bands] 每个方向、每个波段的 BRF
.directions list 输入的 (z, a) 列表
.wavelengths [n_bands] 波段中心波长 (nm)

多角度 BRF 模拟

构建一个观测方向列表(主平面 / 全半球都行),一次 simulate() 就能拿到完整 BRDF 曲面。

import less
import numpy as np

# ── 构建场景 ──────────────────────────────────────────────────
scene = less.Scene()
scene.size = 10.0
scene.repetitive = False  # 有限场景(默认);周期平铺仅限平坦均质地形

scene.terrain = less.Terrain(property=less.Lambertian(reflectance=0.15))
scene.illumination = less.Illumination(source=less.Sun(zenith=30, azimuth=180), atmosphere=less.NoAtmosphere())

maize = less.Object("maize", mesh=less.examples.asset_path("maize.obj"))
maize.set_property(less.Prospect(cab=45, car=10, cw=0.012, cm=0.006, N=1.55))

rng = np.random.RandomState(42)
positions = less.place.grid(scene, spacing=0.75, jitter=0.04)
n = len(positions)
scene.add(maize, positions=positions,
          scales=rng.uniform(0.9, 1.1, n),
          rotations=rng.uniform(0, 360, n))
# 太阳方位角=180°(南),所以主平面是南北向
# - 后向散射:方位角 180°(同侧)
# - 前向散射:方位角 0° / 360°(对侧)
view_zeniths = np.arange(0, 65, 5)
directions = ([(z, 180) for z in view_zeniths]      # backward
            + [(z,   0) for z in view_zeniths[1:]]) # forward (skip z=0 dup)

# ── 单次 simulate 拿到所有方向 ───────────────────────────────
result = scene.simulate(less.BRFSensor(
    bands=[650, 850],
    directions=directions,
    num_photons=50_000_000,
))

print("主平面 BRF (SZA=30°, SAA=180°)")
print(f"{'VZA':>6s}  {'AZ':>4s}  {'Red(650)':>10s}  {'NIR(850)':>10s}")
for (z, a), (r, n) in zip(result.directions, result.brf):
    signed_vza = z if a == 180 else -z          # 主平面常用习惯:负=前向
    mark = " ← hot spot" if a == 180 and abs(z - 30) < 3 else ""
    print(f"{signed_vza:>6d}  {a:>4d}  {r:>10.4f}  {n:>10.4f}{mark}")

# 极坐标 / 主平面曲线一行搞定
result.show(plane="principal", style="line")    # matplotlib 弹图
result.save("plot_brf.csv")                     # 也能导出 CSV / JSON / NPY

配套脚本:scripts/10_brdf.py

代码解读

方向列表的两种常用模式

# 主平面(VZA 扫描)
directions = [(z, 180) for z in range(0, 65, 5)] \
           + [(z,   0) for z in range(5, 65, 5)]

# 全半球(VZA × VAA 网格)—— 用于极坐标图
import itertools
directions = list(itertools.product(
    range(0, 75, 10),     # zeniths
    range(0, 360, 30),    # azimuths
))

主平面 (Principal Plane)

主平面是包含太阳方向和法线方向的平面。在这个平面内:

  • 后向散射方向:观测者位于太阳同侧(观测方位角 = 太阳方位角)
  • 前向散射方向:观测者位于太阳对侧(观测方位角 = 太阳方位角 + 180°)
  • 热点:观测天顶角 = 太阳天顶角,且在后向散射方向

场景重复的重要性

周期 BRF 基准通常使用 scene.repetitive=True 表示无限平铺的平坦均质样地。本教程使用有限场景,因此结果包含地块边缘效应;比较观测或其他模型时应保持相同的边界条件。

BRFSensor vs OpticalImager

想要什么 用谁
一组方向上的 BRF 标量值(典型 BRF / BRDF 曲线) BRFSensor
某一方向的高分辨率二维图像(关心空间纹理 / 阴影) OpticalImager + Orthographic(view_zenith=…)

BRFSensor 通过前向光子追踪,所有方向共享同一批光子,跑 N 个方向 ≈ 单方向耗时;OpticalImager 每方向都得重跑一次。

RPV BRDF 模型

除了通过三维场景模拟 BRF,LESS 也支持直接使用参数化 BRDF 模型。RPV 模型可以用于地面 BRDF:

# RPV 模型参数
scene.terrain = less.Terrain(
    property=less.RPV(rho0=0.08, k=0.7, theta=-0.15)
)

参数含义:

参数 含义 范围
rho0 反射率量级 0-1
k 形状参数:<1 碗形(大角度更亮),>1 钟形(垂直更亮) 0-2
theta 散射不对称:<0 后向散射增强,>0 前向散射增强 -1 ~ 1

自定义可视化

result.show() 默认出主平面线图。要画半球极坐标图(VZA × VAA 网格),需要自己组织数据:

import matplotlib.pyplot as plt
import numpy as np

# directions = [(z, a), ...],假设是规则网格
zeniths  = sorted({z for z, _ in result.directions})
azimuths = sorted({a for _, a in result.directions})
brf_grid = np.zeros((len(azimuths), len(zeniths)))
for (z, a), val in zip(result.directions, result.brf[:, 0]):   # 第 0 个波段
    brf_grid[azimuths.index(a), zeniths.index(z)] = val

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(7, 7))
ax.set_theta_zero_location('N'); ax.set_theta_direction(-1)
theta = np.deg2rad(azimuths)
r = zeniths
ax.contourf(theta, r, brf_grid.T, levels=20, cmap='RdYlGn')
ax.set_title(f"BRF @ {result.wavelengths[0]:.0f} nm")
plt.savefig("brf_polar.png", dpi=150, bbox_inches='tight')

应用场景

应用 说明
反照率估算 积分 BRF 获得半球反照率
多角度遥感 模拟 MISR、POLDER 等多角度传感器
BRDF 校正 评估 BRDF 参数化模型精度
结构参数反演 利用 BRF 各向异性反演冠层结构

下一步

相关 API

  • less.BRFSensorless.Scene.simulate()
  • less.RPVless.Prospectless.Lambertian
  • less.place.grid()less.place.poisson_disk()
  • less.BRFProduct.save()less.BRFProduct.plot_principal_plane()