← 全部作品

2026 — 设计 / WebGL / 前端

雾光森林

一个固定侧视的实时森林:成年公鹿沿林间小径自动漫步,萤火虫在光柱与雾之间亮起,按一个键就能把白天换成月夜。

一个「不用操作也能看」的 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 协议。

→ 打开实时体验