一张桌子,一台亮着的显示器,一把转过来的椅子,两幅挂在墙上的画。
「办公室」是一个可以用鼠标逛的作品集:进门先是空间,拖拽能转身,滚轮能走近;点击显示器,镜头推进到屏幕贴合的位置,网页从屏幕里展开;按 Escape,退回房间。墙上的《云影》和《晨曦》也能点,单独拉近看。

为什么用空间当封面
作品集的首页通常是网格。网格信息密度高,但它是平的——所有项目的地位一样,读者只能从上往下扫。
我想试试反过来:先给一个具体的房间。房间有尺度、有光、有一张转过来的椅子,暗示「这里刚有人离开」。读者先站在空间里,再决定看什么。这个「进入」的动作比点卡片更慢,但更记得住。

场景怎么搭的
- 桌、椅、键盘鼠标、台灯、笔记本、书,都是 Poly Haven 的 CC0 模型,按真实尺度重新摆放;显示器是参数化建模的,屏幕本身是可点击的网格。
- 木纹、墙面、地板和室内环境反射都来自 Poly Haven 的扫描资产,压成 WebP / HDR 后使用。
- 窗外不是贴图:天空是着色器渐变,云是 Marching Cubes 生成的封闭体积,树是递归长出来的——树干树枝是圆柱,叶子用
InstancedMesh铺。 - 两幅挂画是真实全景摄影裁成 4:3:《云影》来自 Kloofendal 的云天,《晨曦》来自 Kiara 1 Dawn。
技术上的几个取舍
按需渲染。 静止时不跑动画帧——frameloop 平时是 demand,只有进出镜头时才切成 always;阴影贴图只在前三帧计算,之后冻结;像素比封顶 1.5。笔记本上待机不掉风扇。
镜头是算出来的,不是手调的。 进入时相机要贴到屏幕前,距离由屏幕宽高比和 FOV 反算,保证不管窗口多宽,画面都刚好铺满屏幕。过渡时长随距离变化,所以近处快、远处慢。
视角有边界。 拖拽限制在水平 ±20°、俯仰 ±8°,缩放 82%–200%。空间感来自「看得见,但不能乱走」。
分级降级。 WebGL 不可用时直接换成一张静态示意图加「浏览二维内容」;场景运行出错会被错误边界兜住,同样退回二维;prefers-reduced-motion 下跳过所有镜头动画,直接切内容。
数字
| 项目 | 数值 |
|---|---|
| 部署体积 | 7.9MB(HDR 环境 1.5MB,模型 3.5MB) |
| 首屏 JS + CSS | 约 730KB(three.js 占大头) |
| 场景代码 | 懒加载,1.06MB / gzip 342KB |
| 静止时的帧循环 | 0 帧 |
| 部署 | Cloudflare Pages |
现在的状态
空间、镜头、进出内容、挂画、降级路径都通了,但场景里那层网页还是占位稿——三个「演示项目」,文案写着「用于验证交互」。下一步是把真实的作品挪进去,让显示器里装的确实是 NOCTURNE、混凝土与理想这些东西。