用 Three.js 拆解一台运动相机:AI 协作开发 DJI Action 4 3D 可视化
一次 AI 工具协作开发实录:纯代码建模 + 程序化贴图,把 DJI Osmo Action 4 拆成 15 个可交互零件;菲涅尔边缘光、线性色彩管线与五个真实的渲染踩坑。

本文是一篇 AI 工具使用分享:如何借助 AI 编程助手,把「给 DJI Osmo Action 4 做一个 3D 结构拆解页面」从一个想法变成一个纯前端可运行的作品。项目本身用 Three.js 实现,零外部资源、双击即开。

项目背景
运动相机的内部结构一直是个"黑盒"——官网只会给你一张参数表。我想知道一台 Action 4 里到底有什么:镜头模组、传感器、主板、散热片、电池是怎么叠起来的。市面上的拆解内容要么是视频(没法交互),要么是 2D 结构图(没有空间感)。
于是有了这个项目:用 Three.js 在浏览器里 1:1 重建一台 Osmo Action 4,把 15 个核心部件做成可交互的拆解视图——点「拆解」,整机沿轴向散开,每个部件带序号标注、参数表与描述;点任意部件,相机飞过去聚焦。
整个项目由我与 AI 编程助手协作完成:我负责产品定义、拆解结构与视觉验收,AI 负责把"菲涅尔边缘光""程序化 HDRI""双层无倒角挤出"这些需求翻译成能跑的代码,再一起逐个攻克渲染 bug。这也是本文想分享的重点——AI 时代的 3D 小项目,一个人 + 一个助手就能做完。
功能特性
- 15 个部件的完整拆解:前壳/前屏/镜头/传感器/主板/散热/电池/中框/后壳/后屏/顶部按键/左侧盖/电池仓盖/扬声器麦克风/磁吸底座,每个部件有独立的拆解方向、距离、参数表与文案;
- 四种视角预设:轴测 / 前视 / 后视 / 俯视,一键切换带缓动动画;
- 部件聚焦与单独查看:点击定位、悬停高亮(边缘发光)、支持 isolate 模式隐藏其他部件;
- 实时标注系统:拆解状态下序号引线分上下两带布局,带防重叠算法;
- 完整 UI:左侧部件清单、右侧参数面板、底部控制 dock、FPS 显示、加载器,移动端自动切换为抽屉布局;
- 零资源依赖:没有任何模型文件和贴图图片,双击
index.html就能跑(file:// 协议可用)。
技术实现
1. 纯代码建模 + 程序化贴图
整个项目最"轴"的决定:不要任何外部资源。所有贴图——镜头镀膜的彩虹渐变、前后屏 UI、PCB 走线、电池标签、滚花纹理、环境 HDRI、粗糙度噪声——全部由 Canvas 2D 程序化绘制后喂给 Three.js。好处是整个项目只有 1.5MB(其中 1.3MB 是 three.js 本体),且永不出现资源 404。
建模方面,机身、屏幕、按键用 ExtrudeGeometry 挤出成型,圆角部件用 RoundedBoxGeometry,镜片用带孔挤出 + 透明材质。部件间用锚点(anchor)+ 拆解向量描述爆炸方向,拆解进度用一个 0~1 的插值统一驱动:
flowchart LR
T["textures.js<br/>Canvas 程序化贴图"] --> M["model.js<br/>15 部件建模 + 锚点"]
M --> S["main.js 场景组装<br/>PMREM 环境 + 四灯布光"]
S --> P["后期链<br/>UnrealBloom + Grade"]
P --> U["交互与 UI<br/>拾取/标注/相机动画"]
2. 边缘光:给材质打补丁而不是换材质
悬停高亮没有用常见的"整体 emissive 拉高"——那会让整个部件泛白发亮,塑料感十足。方案是用 onBeforeCompile 在标准材质的 output_fragment 后注入一段菲涅尔代码:视线与表面夹角越掠射,边缘越亮。
shader.fragmentShader = shader.fragmentShader.replace(
'#include <output_fragment>',
`#include <output_fragment>
float fres = pow(1.0 - saturate(dot(normalize(vViewPosition), normal)), 3.0);
gl_FragColor.rgb += uRim * fres;` // uRim: hover 时缓动到目标强度
);
配合 UnrealBloomPass 泛光,hover 时部件像被"描了一圈冷光",这是整个项目质感的关键一笔。
3. 色彩管线:把 sRGB 留到最后一刻
早期版本机身总是"发白",排查后发现是双重 gamma:材质在 sRGB 空间计算,输出又被当 linear 处理。最终方案是全程线性工作流——
THREE.ColorManagement.legacyMode = false; // 颜色常量按 sRGB 转线性
renderer.outputEncoding = THREE.LinearEncoding; // 渲染器不做 sRGB 输出
// sRGB 编码统一放在最后的自定义 Grade pass 里(顺带做 ACES、暗角、颗粒)
灯光强度也随之全部按线性空间重新标定:key 2.0 / rim 0.78 / fill 0.2 / top 0.32 / ambient 0.16。这套参数看着"偏暗",但成片的对比度是对的。
踩坑记录(都是真实交过的学费)
ExtrudeGeometry带倒角 + 孔洞会出现斜条纹伪影——bevel 的四边形面被扭曲。前面板和后壳最终改成"双层无倒角挤出板"(外层薄板 + 内层厚板错位叠放),用台阶造型代替倒角;- 聚焦相机的取景必须用部件的精确包围盒,不能用整体 radius 外扩,否则镜头怼到 40+ 单位外、模型缩成一个点;
- hover 拾取的
moved标志必须在 pointerdown 时才置位,否则点击的微小抖动会被判成拖拽、点击永远无效; RoundedBoxGeometry的圆角半径必须远小于厚度的一半,否则几何退化出锯齿(前屏边框曾花屏);- 线性色彩空间下灯光强度不能沿用旧直觉,3~5 倍强度直接过曝。
AI 协作心得
这个项目让我对"AI 做工程"有了几点具体体感:
- AI 擅长把"感觉"翻译成"参数"。「我要那种高级的边缘轮廓光,不是整体发光」这种模糊需求,AI 能直接给出菲涅尔 +
onBeforeCompile的标准解法; - 但视觉验收只能人来。发白、条纹伪影、锯齿这些渲染 bug,代码逻辑上完全"正确",只有看到画面才知道不对——AI 没有眼睛,人有;
- 踩坑记录是最好的 Prompt。把每个 bug 的成因与修法写进项目笔记后,后续同类问题 AI 一次就绕开了。这个项目的 memory 文件,比任何教程都好用。
页面已随本站一起交付,后续会挂到线上可直接访问;技术栈:Three.js r147 + OrbitControls + EffectComposer(全部本地化),纯静态、无构建、无外部依赖。


