一个「不用操作也能看」的 3D 场景。大多数 WebGL 作品把访客当操作员:拖、转、缩放。我想做相反的——进来就有一段正在发生的事:鹿在走,光在斜,萤火虫自己亮起来。

一个刻意的限制:固定侧视
镜头只在水平方向阻尼跟随公鹿,不旋转、不俯仰、不能自由漫游。W / S、鼠标拖动、滚轮都不改变视角。
放弃自由度换来的是可以精确设计的构图:每一棵树、每一道光柱、每一簇草的位置都只为一个镜头负责。自由相机听起来更"3D",但它也意味着画面永远是偶然的;固定机位让这一帧可以被反复调到对为止。
场景是怎么长出来的
- 地形:约 4.8 万顶点的连续起伏网格,长波缓坡、局部浅丘与洼地叠在一起。草径没有被压平,地表高度由一个解析函数统一驱动——地形、植被、岩石、鹿的贴地全部读同一份数据,所以没有谁浮在谁上面。
- 地被:9,600 簇实例化细草,配草地色差与细草/枯草纹理;420 颗半埋的小石子、52 块不规则岩石。
- 森林:多段弯曲树干、二级细枝、透明叶片卡、灌木和倒木。树、草、石头都用
InstancedMesh,不然这个密度撑不住。 - 光:压低环境补光,只留窄而明确的暖色主光束,和背景薄雾分开控制;再加屏幕空间径向 God Rays、轻量暗角和 UnrealBloom。地面有 5 处金色亮斑,逐顶点采样地形高度,所以它们贴着缓坡和洼地,而不是悬浮的水平贴片。
- 萤火虫:254 个固定容量粒子,34 个环境粒子 + 220 个召唤粒子共用同一缓冲区。进场景就自动召唤,一轮淡出后自动补充;按
E可以重新召唤,但永远不会突破粒子池上限。它们停在各自的世界坐标里,不跟着鹿走。

鹿的步态
鹿用的是 CC BY 4.0 的写实模型,63 根原生骨骼、22 段动画,没有重新绑骨。只取 DEERALL_Idle 和 DEERALL_WalkForward 两段,由 AnimationMixer 交叉淡入淡出。
真正花时间的是让脚不打滑:先按四肢支撑相的足部速度估出全速位移,取整成 1.55 场景单位/秒,让人工设定的移动速度正好对应 WalkForward 的 1 倍播放;加减速阶段再按当前速度同步调整播放倍率。速度、相位、播放速率三者对不齐的时候,写实模型会比卡通模型更明显地"飘"。
模型本身也做了一点处理:颜色贴图提亮 1.7 倍并轻度降饱和,法线贴图保留,另加低强度暖色自发光,让阴影里的毛发还能读出来。
白天与月亮
R 键或右上角按钮切换昼夜,过渡约 3 秒。天空、雾、曝光、月光、角色补光和 UI 文本由同一个状态驱动,所以不会出现"天黑了但雾还是白的"这种破绽。
成本
| 项目 | 数值 |
|---|---|
| 首屏 JS | 635KB(gzip 170KB) |
| 鹿模型 GLB | 9.9MB |
| 整站部署体积 | 约 10.5MB |
| 运行时规模 | 约 52–53 次绘制调用、100 万三角形 |
| 部署 | Cloudflare Pages(全球边缘,免费额度内) |
浏览器默认像素比封顶 1.5,并在失焦时暂停自动漫步——一个一直开着 3D 的标签页不该偷偷吃满 GPU。
遗憾
- 9.9MB 的模型仍是首屏最大的成本。Draco 压几何 + KTX2 压纹理应该能压到 3MB 以内,但要改加载链路,暂时没做。
- 没有触屏移动。手机上只能看着鹿走,不能接管——对一个"漫步"主题来说其实是缺了一半。
- 没有环境音。风声、脚步、远处的鸟,本来是这套氛围的另一半,现在只能靠画面撑。
致谢
鹿模型 Deer 来自 KhmerTeach(原模型,CC BY 4.0);地形、植被、纹理、光束、萤火虫和界面全部程序生成,没有一张外部贴图。Three.js 与 Vite 均为 MIT 协议。