EMI数字孪生平台 — 速记版
📋 速记表(面试前快速扫)
| 题 | 一句话结论 | 关键词 |
|---|---|---|
| Q1 | ThreeBase+useThree 两层,shallowRef 管对象 | useThree shallowRef |
| Q2 | GLTFLoader 包 Promise + RGBELoader PBR | GLTF RGBE HDR |
| Q3 | Postprocessing+Raycaster,scale 反算坐标 | OutlinePass Raycaster scale |
| Q4 | lil-gui 分 6 文件夹+Stats | lil-gui Stats |
| Q5 | v-scale-screen 等比缩放+scale 坐标 | 等比缩放 全屏 |
| Q6 | clean() 全链 dispose+双释放 WebGL | dispose forceContextLoss |
| Q7 | 工厂模型:ancestors 分组高亮+相机/光照 | ancestors 分组 鸟瞰 |
Q1: useThree 架构
面试官可能问:useThree 这个 Hook 怎么设计的?封装了什么?
🗣️ 口述稿:
拆两层:ThreeBase 静态类把 Scene/Camera/Renderer/Controls 的初始化抽成纯静态方法,和 Vue 响应式解耦;useThree hook 用 shallowRef 管理 20+ 个对象引用,onMounted 按序初始化,requestAnimationFrame 驱动渲染循环统一处理自动旋转、controls、composer、CSS2D 渲染和 Stats。
💡 关键词提示:三层 shallowRef rAF循环
⚠️ 备注:
- 为什么 shallowRef 不 ref:Three 对象嵌套深,ref 会深递归做响应式代理、纯浪费;shallowRef 只追踪 .value 替换。这句最显懂响应式
- 真正要走响应式的是"UI 状态"而非 Three 内部对象——展示层走响应式、Three 内部走 shallowRef
追问①:响应式在哪派上用场?
需要 Vue 响应的是 UI 状态(选中部件、是否显示标签),不是 Three 对象内部。我让展示层状态走响应式、Three 内部走 shallowRef。
追问②:clean() 清什么?
遍历 mesh 逐个 material/geometry.dispose、cancelAnimationFrame、controls.dispose、renderer.dispose+forceContextLoss+WEBGL_lose_context 双释放、引用置空。踩过内存泄漏切几次就卡才系统化补链。
Q2: 模型加载 + HDR
面试官可能问:GLTF/HDR 怎么加载?多个模型并行?
🗣️ 口述稿:
模型用 GLTFLoader,我包一层 Promise 化 loadGLTF 把回调转 async/await,加载完把 gltf.scene 加进场景再挂加标签、绑定控制、注册点击。HDR 用 RGBELoader,mapping=EquirectangularReflectionMapping 后同时赋给 background(天空盒)和 environment(PBR 反射)。
💡 关键词提示:GLTFLoader RGBELoader background/environment
⚠️ 备注:
- 为什么 GLTF:二进制加载快文件小、原生 PBR、Three 支持最好;OBJ 不带材质、FBX 私有格式
- [重要] 仓库只有 GLTFLoader,没有 Draco/glTF 压缩/LOD。被问"轻量化怎么做的"别顺竿爬,诚实说"当时美术给单描边 glb 直接 load,面数上来会加 DRACO decoder 和分级 LOD"
追问①:多个模型并行?
loadModels 内部 Promise.all 并行,但当前场景基本单模型,并行是"为扩展留的 API"。诚实。
Q3: 后处理 + 鼠标交互
面试官可能问:悬浮轮廓光怎么做?拾取延迟 <50ms?
🗣️ 口述稿:
用 Postprocessing 管线:EffectComposer+RenderPass+OutlinePass(描边)+OutputPass+FXAAShader(抗锯齿)。拾取用 Raycaster 在 pointermove 事件 hit test,命中用 CSS2DObject 浮出标签。难点在坐标:外层 v-scale-screen 缩放后 clientX/Y 到 NDC 要先按 scale 反算+减 header 高度。
💡 关键词提示:OutlinePass Raycaster scale反算坐标
⚠️ 备注:
- [需自行确认][重要]:"交互响应≤50ms"代码没有埋点,别当实测。可说"单模型 pointermove 里射线 <1ms 体感跟手",被追问复杂模型承认没上 BVH
- 真正的坑:取 scale 矩阵再反算、减 header(header 也在缩放容器要乘 scale)——细节最能证真调过
追问①:Raycaster 复杂模型会卡吗?
intersectObject(scene,true) 递归,瓶颈是高面模型×每帧全量 intersect;我没上 BVH,单中等模型够用,多高面要加 BVH。/per-pixel picking。承认没做。
追问②:为什么只在 pointermove 做不在每帧?
事件驱动,鼠标不动不检测省 CPU;射线命中率远低于帧率,没必要每帧。
Q4: lil-gui 调试面板
面试官可能问:那个调参面板怎么做的?
🗣️ 口述稿:
用 lil-gui(Three 官方示例里的轻量 GUI)分 6 个文件夹:摄像机位置、轨道控制器、坐标轴辅助、模型标签、模型移动说明(WASD/旋转轴/自动旋转)、轮廓光效果(edgeStrength/edgeGlow/edgeThickness 等),叠 Stats 看 FPS。
💡 关键词提示:lil-gui 六文件夹 Stats FPS
⚠️ 备注:
- 提出"面板是开发调试+客户演示双用":演示时拖滑块就能让客户看想要机位、非技术也能调轮廓光——让调试库有业务价值
- 为什么 lil-gui 不 dat.gui:dat.gui 现代替代、体积小、ESM、Tree Shaking 友好
追问①:为什么 lil-gui 不是 dat.gui?
lil-gui 是 dat.gui 现代替代,API 几乎一样但体积小、支持 ESM、对 Tree Shaking 友好。
Q5: 屏幕适配 + 全屏
面试官可能问:不同屏怎么适配?
🗣️ 口述稿:
v-scale-screen 按设计稿 2560×1440 等比缩放,监听 resize 取宽高比例较小值做 transform:scale(),保证不溢出(4K 或 1920 完整展示)。自封 Fullscreen API 兼容层(webkit/moz/ms 前缀)+监听 fullscreenchange 同步图标。
💡 关键词提示:等比缩放 取小值防溢出 全屏兼容
⚠️ 备注:
- 同款坑:transform scale 后鼠标坐标反算——getComputedStyle matrix[0] 取 scaleX,
(clientX-rect.left)/scale,Y 轴减 headerHeight*scale。与安灯/v-zoom-up 同思路 - [需自行确认]"F11 快捷键/全屏图标"是否自己实现按仓库确认,有些是浏览器原生别当亮点
追问①:全屏怎么兼容?
自己封 Fullscreen API 兼容(requestFullscreen/webkit/moz/ms 前缀),监听 fullscreenchange 同步图标;按仓库确认哪些是自己做的。
Q6: 内存管理与清理
面试官可能问:Three 场景销毁怎么清内存?
🗣️ 口述稿:
Three 不自动回收 GPU 资源必须手动 dispose。clean() 全链:model.traverse 逐个 material/geometry.dispose→cancelAnimationFrame→controls.dispose→renderer.dispose+forceContextLoss→loseContext()强制同步释放→引用置空;事件 in onBeforeUnmount remove。
💡 关键词提示:dispose forceContextLoss loseContext
⚠️ 备注:为什么双释放——forceContextLoss() 是 Three 的异步释放渲染器资源、loseContext() 是 WebGL 原生同步释放,双重保险防泄漏。
追问①:模型切换怎么触发清理?
clean()→v-if 销毁整个 ModelLoader 组件刷新。答出"销毁重建 vs 原地复用"说明懂组件生命周期。
追问②:什么情况会漏?
CanvasTexture 要显式 dispose 否则泄漏,早期漏了后来靠 renderer.info+about:gpu 逐个查。踩过漏会自查加分。
Q7: 工厂模型特殊处理
面试官可能问:工厂场景和单模型有什么不同?
🗣️ 口述稿:
工厂是大型化料厂多模块(货车/树/仓库/储罐)。四点不同:不打 HDR 改手写 DirectionalLight+AmbientLight;相机 FOV 45°→30°、far 放大、鸟瞰坐标;用 ancestors 模式给每个 mesh 挂父级 Group 引用,拾取高亮整辆货车非单零件;储罐两层嵌套特判内层。
💡 关键词提示:ancestors分组 鸟瞰相机 手写光照
⚠️ 备注:
- ancestors 最值钱:单模型高亮单部件、工厂要高亮逻辑单元(货车),给 mesh 挂 child.ancestors=obj 父组,outlinePass.selectedObjects=ancestors 实现"点零件亮整模块"
- 为什么 factory.vue 单独写不复用:差异大,直接复制一份比硬用 props/if-else 合并好——"什么时候该复制"的判断别强求复用
追问①:为什么 factory.vue 单独写不复用 index.vue?
光照/相机/预处理差异太大,直接复制一份比硬合并清晰。两三百行组件,清晰>过度复用,这是我务实取舍。
🎙️ 行为面试
为什么做这个项目?
公司做纺织智造,需要把回潮率检测仪、化料厂用 3D 展示给客户——网页上旋转缩放看部件比翻说明书直观。我从 0 搭 Three.js 方案。
💡 关键词:客户展示 3D直观
⚠️ 备注:数字孪生"给客户看"比"炫技"更打动。
最大难点?
- v-scale-screen 缩放和 Three 坐标系适配——scale 后 clientX/Y 到 NDC 全乱、尤其 Y 减 header,调试很久才找到正确公式
- 内存泄漏——早期没 dispose 切几次就卡,后系统化加 clean() 清理链
💡 关键词:scale坐标反算 内存泄漏clean
⚠️ 备注:这两个坑跨 06/07/05 前后呼应,讲出来有体系。
遗憾/弯路?
- 没有模型动画(转的风扇/流动液体),设备运行时状态未能动态呈现
- 没接实时 IoT 数据——该把设备数据映射到 3D(温度高变红)没做,纯展示
- factory.vue 和 index.vue 重复代码——当时务实复制没抽公共逻辑,迭代必重构
💡 关键词:无动画 未接IoT 代码重复
⚠️ 备注:第 1、2 是"数字孪生真正的价值没做满",说漏比吹完整强。
跟其它项目什么关联?
都在纺织智造产品线下,技术栈统一 Vue3+TS+Vite;v-scale-screen 方案在安灯、数字孪生、云数据平台都复用。
💡 关键词:统一技术栈 v-scale-screen复用到验证
⚠️ 备注:答"不是孤立项目"体现体系观。
🃏 极简速记卡(面试前5分钟看)
- Q1 架构:ThreeBase+useThree两层,shallowRef
- Q2 加载:GLTFLoader+RGBELoader PBR
- Q3 交互:OutlinePass+Raycaster,scale反算
- Q4 面板:lil-gui六文件夹+Stats
- Q5 适配:等比缩放+全屏兼容
- Q6 内存:clean全链dispose双释放
- Q7 工厂:ancestors分组高亮+鸟瞰
- 行为 动机:客户3D展示;难点:scale坐标+内存泄漏;遗憾:无动画+未接IoT+代码重复
上一篇:05-EMI智慧SOP看板(安灯系统) 下一篇:07-EMI协同管控平台