Improve Cesium cruise smoothness

This commit is contained in:
2026-08-03 15:17:51 +08:00
parent a824bee69b
commit 0d2dc2228c
10 changed files with 224 additions and 13 deletions

View File

@@ -121,6 +121,26 @@ catalog.MATERIALS 声明「是什么」 纯 Python无 bpy
**这类"试过、不行、为什么"的记录要保留。** 删掉它,下一个人会重新引入同一个资产。
## 灌木边缘实例有性能预算
`generate_scene.py` 的 scrub 不是只有贴地面:`natural=scrub` 会先生成
`Scrub_<way_id>` 地面覆盖,再沿边界实例化 `assets/models/custom/bush/bush.glb`
`add_scrub_edge_bushes()`),并在大 scrub 面内部稀疏补树。
这些 bush 是共享 mesh 的多 node 实例。共享 mesh 能压 GLB 体积,但 Cesium 近景 Follow
仍要处理每个 node / draw。`nantaizi-lake-innovation-valley` 曾经用
`SCRUB_BUSH_SPACING = 0.82``SCRUB_BUSH_LIMIT_PER_PATCH = 180`,结果一个场景有约
`707` 个 bush nodes车辆 Follow 到灌木密集区域时明显卡顿。当前预算是:
```python
SCRUB_BUSH_SPACING = 1.8
SCRUB_BUSH_LIMIT_PER_PATCH = 60
```
在 nantaizi 上约 `266` 个 bush nodesscrub 地面覆盖仍保留。不要只为了“更满”把间距
调回 1m 以下;那是在把预览流畅度换成近景装饰密度。若确实需要更密的灌木,先换低模
bush 或做显式 LOD / instancing 方案,再跑 Cesium preview 验证。
---
## 确定性:用无理数周期代替 RNG

View File

@@ -162,6 +162,21 @@ let baseStatus = "";
相机相关的读数必须跟着渲染循环刷新并**做节流**。用 `setInterval` 会在相机快速移动时
读到过期值,不节流会拖慢帧率。
### 车辆巡航热路径要少分配
车辆巡航是预览层里唯一持续动画的功能。`scripts/lib/cesium-preview.js`
`routeOrientation()``createChaseFollow()` 每帧都会运行,里面的 `JulianDate`
`HeadingPitchRoll``Quaternion``Matrix4` 等临时对象必须在闭包外复用,不要在
`CallbackProperty``viewer.clock.onTick` 回调里 `new`
预览页面加载的是 30MB+ GLB动画时 GPU 已经很忙;巡航辅助线也要避免额外透明合成。
路线 polyline 使用不透明 `Color``arcType: Cesium.ArcType.NONE`,不要为了视觉效果
随手改回半透明 geodesic 线。
不要默认降低 `viewer.resolutionScale` 或关闭 antialias/MSAA 来换取巡航流畅度。
这会让用户误判材质和模型质量。若确实需要性能模式,应做成显式开关,而不是默认牺牲
预览清晰度。
### 单资产 vs 多资产的开关
```js
@@ -207,6 +222,9 @@ python3 -m http.server 8765
| 用定时器代替 `waitForStableFrames` | 遮罩在材质编译完成前就收起,画面是花的 |
| 相机读数用 `setInterval` | 快速移动时读到过期值 |
| 诊断刷新不节流 | 拖慢帧率 |
| 在车辆每帧回调里创建 `JulianDate` / HPR / 矩阵对象 | 巡航时触发 GC 抖动 |
| 默认打开半透明路线辅助线 | 增加透明合成成本,主场景可见时车辆更不顺 |
| 默认降低 `resolutionScale` 或关抗锯齿 | 预览变糊,无法判断材质/模型质量 |
| 再开一个全局变量 | 已有 `window.osmPreview` |
| 用 `file://` 打开 | fetch 被拦,卡在加载中 |

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,67 @@
# Smooth Cesium preview vehicle cruise
## Goal
Improve perceived smoothness of vehicle cruise in the Cesium preview for
`nantaizi-lake-innovation-valley` while the scene model is visible, especially
when Follow passes through scrub-heavy areas.
The initial preview-layer optimization helped but did not solve the root cause:
closing `Scene` helps a lot, and the current nantaizi GLB contains about 707
scrub bush nodes. The root fix is allowed to reduce scrub bush instance density
while preserving the flat scrub ground cover and existing preview controls.
## Requirements
1. Optimize `scripts/lib/cesium-preview.js` so vehicle animation does less
per-frame work during cruise without reducing default visual clarity.
2. Preserve existing controls and behavior:
- Play/Pause still drives `viewer.clock.shouldAnimate`.
- Speed slider still maps m/s to clock multiplier.
- Vehicle selection still chooses the follow target.
- Scene/routes/vehicles/FPS/diagnostics toggles still work.
- Follow mode still tracks the selected vehicle.
3. Keep the preview layer dependency-free and browser-native; no build step,
npm dependency, module system, or hard-coded area filenames.
4. Reduce scrub edge bush instance density in `blender/generate_scene.py` to
lower Cesium render load in scrub-heavy Follow views. Preserve deterministic
sampling, scrub ground polygons, scrub tree sampling, and scene stats fields.
5. Regenerate or copy the updated preview JS into
`outputs/nantaizi-lake-innovation-valley/cesium-preview.js` only if needed
for local manual verification; do not commit generated outputs.
6. Rebuild `nantaizi-lake-innovation-valley` `blender,cesium,preview` outputs
for manual validation. This task intentionally changes generated GLB
structure, so full parity-identical output is not expected.
## Acceptance Criteria
- [ ] `scripts/lib/cesium-preview.js` has a small, reviewable optimization that
reduces per-frame allocations or render overhead for vehicle cruise.
- [ ] `blender/generate_scene.py` reduces nantaizi scrub bush node count by at
least 50% while keeping scrub polygons present.
- [ ] Vehicle controls listed in Requirements continue to work by inspection
and/or local smoke check.
- [ ] Blender/Cesium rebuild for `nantaizi-lake-innovation-valley` completes
and generated metadata still includes scrub counts.
- [ ] `node --check scripts/lib/cesium-preview.js` passes.
- [ ] `python3 -m py_compile blender/generate_scene.py` passes.
- [ ] If local output preview JS is updated for testing, it remains ignored and
is not committed.
## Out Of Scope
- Full scene geometry/material optimization beyond scrub edge bush density.
- Reducing tree count or changing road/building/water assets.
- Replacing Cesium or adding profiling libraries.
- Visual design changes to the preview UI.
## Technical Notes
- User confirmed closing `Scene` improves smoothness, so the scene render load
is a real contributor.
- Current route data is small: `nantaizi-lake-innovation-valley` has 9 route
segments and the preview uses at most 5 vehicles.
- Current nantaizi GLB has 1271 nodes; about 707 are scrub bush nodes. Lowering
scrub edge sampling from `0.82m / 180 per patch` to `1.8m / 60 per patch`
should reduce nantaizi bush samples to about 266 while leaving scrub ground
cover intact.

View File

@@ -0,0 +1,57 @@
# Smooth Cesium preview vehicle cruise report
## Root Cause
The first preview-only optimization helped but did not address the main load.
User testing showed Follow still stuttered in scrub-heavy areas, while disabling
`Scene` improved smoothness substantially.
Current nantaizi GLB before the scrub-density change:
- GLB nodes: `1271`
- Scrub bush nodes: `707`
- Scrub polygons: `5`
The route data is small (`9` route segments, preview uses at most `5` vehicles),
so route interpolation is not the primary bottleneck.
## Changes
- `scripts/lib/cesium-preview.js`
- Route helper polyline is now opaque instead of alpha blended.
- Route helper polyline uses `arcType: Cesium.ArcType.NONE`.
- Vehicle orientation and chase-follow camera callbacks reuse scratch
`JulianDate`, `HeadingPitchRoll`, `Quaternion`, and `Matrix4` objects.
- Default Cesium resolution and antialiasing are preserved; no blurry
performance mode is enabled by default.
- `blender/generate_scene.py`
- `SCRUB_BUSH_SPACING`: `0.82 -> 1.8`
- `SCRUB_BUSH_LIMIT_PER_PATCH`: `180 -> 60`
- Scrub ground cover remains unchanged.
## Validation
Commands run:
```bash
node --check scripts/lib/cesium-preview.js
python3 -m py_compile blender/generate_scene.py
python3 -m unittest blender/tests/test_pure.py
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,preview
node scripts/glb-digest.js outputs/nantaizi-lake-innovation-valley/nantaizi-lake-innovation-valley.glb --out /tmp/nantaizi-after-scrub-glb-digest.json
```
Results after rebuild:
- `SCENE_DONE` reports `scrub_bushes: 266`.
- GLB nodes: `830`
- Scrub bush nodes: `266`
- Scrub polygons in metadata: `5`
- Route segments in preview route JSON: `9`
- GLB size: about `31.96 MB`
## Deferred
If Follow still stutters after this density reduction, the next root-level work
should target the bush asset itself: lower-poly bush mesh, explicit LOD, or a
proper instancing path supported by Cesium/glTF.

View File

@@ -0,0 +1,26 @@
{
"id": "smooth-cesium-preview-vehicle-cruise",
"name": "smooth-cesium-preview-vehicle-cruise",
"title": "Smooth Cesium preview vehicle cruise",
"description": "Optimize Cesium preview vehicle cruise smoothness without changing Blender or GLB output.",
"status": "in_progress",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-03",
"completedAt": null,
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -90,9 +90,9 @@ TUFT_TINT = (0.15, 0.52, 0.09)
TUFT_TINT_FACTOR = 0.66
SCRUB_BUSH_MODEL = os.path.join(CUSTOM_MODEL_ROOT, "bush", "bush.glb")
SCRUB_BUSH_SPACING = 0.82
SCRUB_BUSH_SPACING = 1.8
SCRUB_BUSH_INSET = 0.38
SCRUB_BUSH_LIMIT_PER_PATCH = 180
SCRUB_BUSH_LIMIT_PER_PATCH = 60
SCRUB_BUSH_HEIGHT = 1.575
SCRUB_BUSH_SCALE_JITTER = 0.16
SCRUB_BUSH_LEAF_TINT = (0.075, 0.31, 0.055)

View File

@@ -36,6 +36,14 @@
- 清理 D3 过期注释债:`tuft_density_wave()` 的说明不再引用已删除的 hedge banding
对比,`.trellis/spec/guides/artifact-parity-guide.md` 也不再把 D3 列为待修缺陷;
只改注释 / spec / changelog未改 Blender 场景生成逻辑。D1 / D2 继续保留为已知约束。
- 优化 Cesium 预览车辆巡航流畅度:路线辅助线改为不透明非测地线,并复用车辆朝向与
跟车相机热路径里的 `JulianDate` / HPR / 矩阵对象,减少巡航时的透明合成和 GC 抖动。
默认保留 Cesium 原分辨率和抗锯齿,避免预览变糊;只影响
`scripts/lib/cesium-preview.js`,不改变 Blender / GLB / metadata 产物。
- 从根因降低灌木密集区 Follow 卡顿:`SCRUB_BUSH_SPACING``0.82m` 调到 `1.8m`
`SCRUB_BUSH_LIMIT_PER_PATCH``180` 降到 `60`nantaizi 的 scrub bush nodes
`707 -> 266`GLB 总 nodes `1271 -> 830`scrub 地面覆盖仍保留 5 个面,默认画质
不降级。
## 2026-07-31远看发黑的真正原因反照率没被提亮

View File

@@ -365,7 +365,8 @@
polyline: {
positions: Cesium.Cartesian3.fromDegreesArrayHeights(flat),
width: 2,
material: routeColor.withAlpha(0.75),
material: routeColor,
arcType: Cesium.ArcType.NONE,
clampToGround: false
}
});
@@ -434,9 +435,12 @@
const up = new Cesium.Cartesian3();
const east = new Cesium.Cartesian3();
const north = new Cesium.Cartesian3();
const aheadTime = new Cesium.JulianDate();
const hpr = new Cesium.HeadingPitchRoll(0.0, 0.0, 0.0);
const base = new Cesium.Quaternion();
return new Cesium.CallbackProperty((time, result) => {
routePosition(route, start, time, speed, current);
const aheadTime = Cesium.JulianDate.addSeconds(time, 0.8, new Cesium.JulianDate());
Cesium.JulianDate.addSeconds(time, 0.8, aheadTime);
routePosition(route, start, aheadTime, speed, ahead);
Cesium.Cartesian3.subtract(ahead, current, direction);
if (Cesium.Cartesian3.magnitudeSquared(direction) < 0.0001) {
@@ -454,10 +458,13 @@
Cesium.Cartesian3.normalize(north, north);
const eastComponent = Cesium.Cartesian3.dot(direction, east);
const northComponent = Cesium.Cartesian3.dot(direction, north);
const heading = Math.atan2(eastComponent, northComponent);
const base = Cesium.Transforms.headingPitchRollQuaternion(
hpr.heading = Math.atan2(eastComponent, northComponent);
Cesium.Transforms.headingPitchRollQuaternion(
current,
new Cesium.HeadingPitchRoll(heading, 0.0, 0.0)
hpr,
undefined,
undefined,
base
);
return Cesium.Quaternion.multiply(base, correction, result || new Cesium.Quaternion());
}, false);
@@ -470,6 +477,9 @@
const scratchEast = new Cesium.Cartesian3();
const scratchNorth = new Cesium.Cartesian3();
const scratchUp = new Cesium.Cartesian3();
const scratchPreviousTime = new Cesium.JulianDate();
const scratchHpr = new Cesium.HeadingPitchRoll(0.0, 0.0, 0.0);
const scratchTransform = new Cesium.Matrix4();
const offset = new Cesium.Cartesian3();
const state = { enabled: false, distance: 36.0, height: 18.0 };
@@ -478,8 +488,8 @@
const positions = positionsProvider();
const position = positions.getValue(time, scratchPosition);
if (!position) return;
const previousTime = Cesium.JulianDate.addSeconds(time, -0.8, new Cesium.JulianDate());
const previous = positions.getValue(previousTime, scratchPrevious);
Cesium.JulianDate.addSeconds(time, -0.8, scratchPreviousTime);
const previous = positions.getValue(scratchPreviousTime, scratchPrevious);
if (previous) {
Cesium.Cartesian3.subtract(position, previous, scratchDirection);
} else {
@@ -501,13 +511,16 @@
const eastComponent = Cesium.Cartesian3.dot(scratchDirection, scratchEast);
const northComponent = Cesium.Cartesian3.dot(scratchDirection, scratchNorth);
const heading = Math.atan2(eastComponent, northComponent);
scratchHpr.heading = Math.atan2(eastComponent, northComponent);
Cesium.Cartesian3.fromElements(0.0, -state.distance, state.height, offset);
const transform = Cesium.Transforms.headingPitchRollToFixedFrame(
Cesium.Transforms.headingPitchRollToFixedFrame(
position,
new Cesium.HeadingPitchRoll(heading, 0.0, 0.0)
scratchHpr,
undefined,
undefined,
scratchTransform
);
viewer.camera.lookAtTransform(transform, offset);
viewer.camera.lookAtTransform(scratchTransform, offset);
}
function onWheel(event) {