我用 Three.js 给博客首页造了一个赛博朋克房间

2026年05月20日8 次阅读0 人喜欢
Three.jsReact Three FiberNext.js赛博朋克3D前端博客
所属合集

起因

写博客十年了,首页一直是千篇一律的静态 Banner + 文章列表。说实话看腻了。

我一直想搞点不一样的——不是换个背景图换个配色那种不一样,而是整个首页的第一印象就让人「卧槽」的那种。

所以我把首页 Banner 砸了,用 Three.js 造了一个赛博朋克风格的 3D 小公寓。

想法

核心概念很简单:赛博朋克废土中,一个被你不断修复、扩建的小站点

房间不是冷冰冰的展示台,是有人住着的——桌上有咖啡杯,椅子上搭着外套,地上散落着线缆,角落里趴着一只机械狗。这其实就是我写博客的状态:一堆乱七八糟的东西堆在一起,但就是能产出东西。

远期规划还挺疯的:

  • 合集 = 建筑群
  • 文章 = 可交互的全息面板
  • 标签 = 区域标识灯
  • 博客成长 = 世界扩张

但现阶段只做第一步:3D Banner。

技术栈

选型没怎么犹豫:

  • React Three Fiber (R3F) — React 生态的 3D 渲染器,跟 Next.js 天然集成
  • @react-three/drei — 常用 3D 组件库(OrbitControls、Environment 等)
  • @react-three/postprocessing — 后处理效果(Bloom 霓虹发光 + Vignette 暗角)
  • Zustand — 场景参数状态管理
  • leva — 开发调试面板(生产环境完全不渲染)

没自己建 3D 模型,所有东西都是代码搓出来的基础几何体或者程序化纹理。

房间长什么样

整个场景是一个赛博朋克小公寓,广角展示完整空间:

左侧睡眠区:一张乱糟糟的床,墙上贴着粉色发光海报,床头柜上有一个全息时钟。

中央工作区:工业风桌子,RGB 发光键盘,三台显示器分别显示代码界面、地图监控和系统状态。

右侧存储区:六层金属书架,摆着发光的数据核心和存储模块;旁边是一台 1.8 米高的服务器机柜,16 个 LED 指示灯各自独立闪烁。

后墙落地窗:4.5×3.5 米的大窗,窗外是程序化生成的赛博朋克城市天际线——85 栋建筑剪影、随机闪烁的霓虹窗灯、飞行载具光带。雨滴顺着玻璃往下流。

天花板:暴露的管道系统(8 根横管 + 2 条电缆桥架),嵌着三条霓虹灯带(青色、紫色、粉红)。

生活细节:桌下散落的线缆(TubeGeometry 弯曲线缆)、咖啡杯、椅子上的外套、十二面体生物发光植物、角落里的机械狗。

窗户上方还挂了一个霓虹灯牌——"小破站"。

几个我觉得有意思的技术点

程序化纹理

所有纹理都是 Canvas 2D 画的,没用贴图:

城市天际线(2048×1024):85 栋随机高度的建筑剪影,随机亮灭的霓虹窗灯,广告牌发光,飞行载具光带,多层雾气。每次生成都不一样。

木地板(512×512):木板纹理、缝隙、水渍、磨损痕迹。

混凝土墙(256×256):噪点 + 水渍。

全部通过 useMemo 缓存,只创建一次。

雨滴粒子系统

300 个粒子用 BufferGeometry + Points 实现。其中 20% 是"雨痕"——速度更快、尺寸更大、横向漂移更少,模拟雨水沿玻璃流下的效果。

AdditiveBlending + depthWrite: false 避免深度冲突,雨滴自然叠在窗后城市上。

灯光系统

27 个独立参数通过 Zustand 管理:

  • 1 个 SpotLight 从窗外射入(模拟城市光照)
  • 8 个 PointLight(显示器冷蓝光、服务器紫光、霓虹招牌等)
  • 所有灯光都有独立的呼吸动画,频率各不相同
  • 窗外主光是双频叠加(0.3Hz + 0.7Hz),模拟远处霓虹闪烁

动画

全部通过 useFrame 驱动:

  • 服务器 LED:16 个灯各自随机频率闪烁
  • 显示器 emissiveIntensity 微呼吸
  • 全息时钟脉冲
  • 霓虹海报/灯牌呼吸发光
  • 窗户城市纹理 emissiveIntensity 呼吸

每个动画都很轻量,没有重型物理模拟。

性能策略

这块花了不少心思:

  1. WebGL 检测 + 降级:检测 GPU 能力,低端设备自动回退静态 Banner
  2. dpr 限制在 1~1.5:不做无意义的超采样
  3. 生产/开发严格隔离:生产环境直接读常量,不订阅 Zustand store,不渲染 DevControls / OrbitControls / Grid
  4. Zustand 细粒度 selector:开发环境每个组件只订阅自己需要的参数
  5. leva 调试面板:独立 React Root 挂载,跟主应用完全解耦,提供 8 个预设视角、光源编辑器、元素开关

踩的坑

useState vs useFrame

一开始在组件里用 useState 存动画状态,结果每帧都触发 React 重渲染,性能炸了。后来全部改成直接操作 Three.js 对象的属性,只在需要 React 响应的地方(比如 dev controls)才走 store。

生产环境 store 订阅

开发阶段一切正常,但发到生产后 profiling 发现大量不必要的 store 订阅。解决方案是导出 PRODUCTION_DEFAULTS 常量,生产环境直接引用,完全不触发 Zustand 订阅。

粒子系统性能

初期用了 transparent: true + opacity,粒子多的时候排序开销很大。换成 AdditiveBlending + depthWrite: false 后性能好很多,视觉效果反而更好——雨滴自然发光。

调试体验

这个必须说,leva 这个库太好用了。

通过 createRoot 挂载独立的 React 节点,完全不影响主应用。开发时可以实时调整:

  • 相机位置 / FOV
  • 视差强度 X/Y
  • Bloom 强度 / 阈值
  • 场景元素开关
  • 每个光源的独立参数
  • 8 个预设视角一键切换

调好之后直接导出配置到 console,复制粘贴成生产常量。

效果

最终效果就是现在你看到的首页——打开博客,第一眼是一个沉浸式的赛博朋克房间,鼠标移动有视差效果,滚动时 3D 场景自然淡出到文章列表。

移动端也做了适配,低端设备会自动降级。

下一步

这只是阶段一。后续计划:

  • 阶段二:书架上的书对应博客合集,点击可跳转
  • 阶段三:文章变成墙上的全息面板
  • 阶段四:整个首页变成可自由探索的 3D 空间,多个房间对应不同合集

先不急,先把现在这个打磨好。


代码全在 GitHub 上,有兴趣的可以去看 src/components/cyberpunk/ 目录。有问题评论区聊。

站长

加载评论中...