分类 / 实验记录 / 2026.09.14
WebGPU 星空渲染的几条工程实践
从相机更新、空间分块到按需遥测,整理浏览器端实时图形项目中的关键实现选择。
最初的目标是在浏览器中生成一片可实时更新的星空。第一版很快就能画出点,但相机移动距离增加后,坐标精度、屏幕边缘的分布和渲染状态管理都出现了问题。
我把实现拆成几层:场景状态负责管理时间与参数,空间数据层提供当前视野所需的恒星,GPU 层负责绘制,界面通过稳定的 API 读取和更新状态。
按时间步长更新相机
动画不能假设每台设备的帧率相同。如果每帧固定移动相同距离,运行在 60 FPS 和 144 FPS 的设备就会表现出不同速度。
更新时使用帧间隔计算位移:
const deltaSeconds = Math.min(deltaMs / 1000, 0.05)
position.z += speed * deltaSeconds
限制单帧的最大时间差,可以避免页面在后台暂停很久后恢复时突然跳过一大段场景。当前值则通过加速度逐渐逼近目标值,参数变化不会造成突兀跳变。
将空间划分为可管理的区块
如果试图一次性生成场景中的全部恒星,数据量会不断增长,也没有必要。每个区块由种子和坐标确定,系统只生成相机附近可能进入视野的区域。
这套方法需要两点:
- 同一个种子和同一个星域坐标必须得到相同结果。
- 离开视野较远的区域应及时释放,避免缓存随着相机移动距离无限增长。
确定性随机生成让调试可以复现。需要检查某个场景时,只需保存种子和区块坐标,不必复制所有顶点数据。
渲染层不承担业务状态
GPU 资源创建、设备丢失和 shader 错误都属于渲染层。页面不应该直接读取渲染器内部字段,因此我提供了一个小型 API:读取快照、更新场景参数、暂停渲染、重置种子和订阅低频遥测。
快照适合低频 UI 更新,渲染循环则继续以设备允许的节奏运行。遥测没有订阅者时,系统不创建额外快照;出现订阅时才按指定间隔汇总 FPS、区块数量和恒星数量。
画面与界面使用同一个场景
渲染场景与调试面板如果各自持有一份状态,就会产生竞态:界面更新了参数,面板仍显示旧值;或者重新初始化时把参数覆盖回默认值。
让两者订阅同一个 API 后,展示界面与调试控制就能共享场景状态。调试面板仍可提供精细参数,但不再持有另一套独立的数据。
设备故障不是空白页面
WebGPU 仍然会遇到浏览器不支持、设备初始化失败或 GPU 重置。即使场景无法绘制,页面也应该显示清楚的状态,文章和项目页面仍需正常工作。
这次实验让我更确定一件事:图形效果负责让网站更有辨识度,清晰的生命周期和失败路径才决定它能不能成为一个可靠的网站功能。