# atmosphere

天空和大气渲染插件，基于 `@takram/three-atmosphere` 的 WebGPU 节点实现。启用后会接入 Viewer 的 `renderer.contextNode`、`scene.backgroundNode`、可选的 `scene.environmentNode` 和 RenderPipeline 的 output effect/MRT 扩展点，提供天空背景、天空环境、太阳/月亮方向、lens flare、tone mapping、temporal antialias、dithering 和大气方向光阴影。

```typescript
import { Atmosphere } from 'u-space/plugins/atmosphere';

const atmosphere = new Atmosphere(viewer);
atmosphere.enable({
  longitude: 120.002269,
  latitude: 30.284849,
  height: 4,
});
```

## 能力

- 使用经纬高将本地场景原点映射到 WGS84/ECEF，用 `date` 计算太阳和月亮方向。
- 写入 `scene.backgroundNode = skyBackground()`；`useSkyEnvironment` 开启时写入 `scene.environmentNode = skyEnvironment()`，关闭插件会恢复启用前的背景和环境节点。
- 通过 RenderPipeline output effect 追加 lens flare、tone mapping、temporal antialias 和 dithering，不占用业务侧的 `setOutputComposer()`。
- 开启 `temporalAntialias` 时注册独立的 `atmosphereVelocity` MRT 通道，并避免和 RenderPipeline 自带 TRAA 重复运行。
- 创建 `AtmosphereLight` 作为太阳方向光，并在每帧跟随相机控制器 target 更新阴影目标。

## API

### `new Atmosphere(viewer)`

创建大气插件实例。

### `enable(options?)`

开启大气效果。重复调用时等同于 `update(options)`。

```typescript
atmosphere.enable({
  longitude: 120.002269,
  latitude: 30.284849,
  height: 4,
  date: new Date(),
  showSun: true,
  showMoon: true,
  showStars: true,
});
```

### `update(options?)`

更新大气参数，不会重新创建插件实例。

```typescript
atmosphere.update({
  date: new Date('2026-06-16T12:00:00+08:00'),
  showStars: false,
});
```

### `setLocation(longitude, latitude, height?)`

更新场景本地原点对应的 WGS84 经纬高。`longitude`、`latitude` 使用角度，`height` 使用米。

### `setDate(date)`

更新太阳和月亮方向。`date` 支持 `Date` 或时间戳。

### `disable()`

关闭大气效果并恢复启用前的 `backgroundNode`、`environmentNode` 和 `renderer.contextNode`。

### `dispose()`

释放插件资源，等同于 `disable()`。

## Options

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `longitude` | `number` | `0` | 本地场景原点的经度，单位为度 |
| `latitude` | `number` | `0` | 本地场景原点的纬度，单位为度 |
| `height` | `number` | `0` | 本地场景原点高度，单位为米 |
| `date` | `Date \| number` | 当天本地时间 08:00 | 用于计算太阳和月亮方向 |
| `showSun` | `boolean` | `true` | 显示太阳 |
| `showMoon` | `boolean` | `true` | 显示月亮 |
| `showStars` | `boolean` | `true` | 显示星空 |
| `showGround` | `boolean` | `false` | 是否渲染大气地面 |
| `useSkyEnvironment` | `boolean` | `true` | 是否使用 `skyEnvironment()` 作为 `scene.environmentNode` |
| `raymarchScattering` | `boolean` | `true` | 是否启用 raymarch scattering |
| `lighting` | `boolean` | `true` | 保留项：当前后处理链不再插入 aerial perspective，暂不生效 |
| `transmittance` | `boolean` | `true` | 保留项：当前后处理链不再插入 aerial perspective，暂不生效 |
| `inscattering` | `boolean` | `true` | 保留项：当前后处理链不再插入 aerial perspective，暂不生效 |
| `moonScattering` | `boolean` | `true` | 是否在天空背景中包含月光散射 |
| `lensFlare` | `boolean` | `true` | 是否在 output effect 链中启用 lens flare |
| `temporalAntialias` | `boolean` | `true` | 是否在大气后处理链中启用 temporal antialias |
| `dithering` | `boolean` | `true` | 是否在 temporal antialias 后追加 dithering |
| `toneMapping` | `ToneMapping \| false` | `AgXPunchyToneMapping` | 大气后处理链使用的 tone mapping；设为 `false` 可跳过 |
| `toneMappingExposure` | `number` | `3` | 大气 tone mapping 曝光值 |
| `castShadow` | `boolean` | `true` | 是否让大气太阳光投射阴影 |
| `shadowMapSize` | `number` | `4096` | 阴影贴图尺寸 |
| `shadowCameraSize` | `number` | `180` | directional shadow 正交相机覆盖范围，越小越清晰但覆盖范围越窄 |
| `shadowCameraNear` | `number` | `10` | shadow camera near |
| `shadowCameraFar` | `number` | `6000` | shadow camera far |
| `shadowBias` | `number` | `-0.00002` | 阴影深度偏移 |
| `shadowNormalBias` | `number` | `0.001` | 法线方向阴影偏移，用于缓解 shadow acne；过大会让底部阴影产生脱离感 |
| `shadowRadius` | `number` | `1.5` | PCF 阴影采样半径，用于柔化轻微锯齿 |
| `shadowDistance` | `number` | `3000` | 大气方向光与 target 的距离 |

## 注意事项

`Atmosphere` 不会占用 `viewer.renderPipeline.setOutputComposer()`。它通过 output effect 追加 lens flare、tone mapping、temporal antialias 和 dithering；开启 `temporalAntialias` 时才会注册独立的 `atmosphereVelocity` MRT 通道，并会避免和 RenderPipeline 自带 TRAA 重复运行。业务设置自定义 composer 时，大气 output effect 和它的输出变换开关都会被 RenderPipeline 跳过，让 composer 保持完整接管输出的原语义。

`scene.backgroundNode` 使用 `skyBackground()`，`scene.environmentNode` 使用独立的 `skyEnvironment()`，两者不是同一个天空节点。`showGround` 默认为 `false`，当相机视线落到天空节点的地面半球时背景可能呈暗色；需要渲染大气地面时传入 `showGround: true`。

插件会在 `beforeRender` 中刷新相机 ECEF 位置、太阳/月亮方向和阴影 target，因此相机移动时的大气状态会跟随每一帧更新。`setDate()` 只更新传入的时间，不会自动推进时钟；需要动态昼夜变化时，由业务侧定时调用 `setDate()` 或 `update({ date })`。

如果看到明显的方形阴影边界，通常是 `shadowCameraSize` 小于当前可见场景范围。可以调大该值，或让相机 target 更贴近需要观察的区域。阴影偏糊时优先减小 `shadowCameraSize` 或增大 `shadowMapSize`；底部阴影有脱离感时优先减小 `shadowNormalBias`；边缘仍有轻微锯齿时可略微增大 `shadowRadius`。
