Skip to content

EMI数字孪生平台 — 速记版

📋 速记表(面试前快速扫)

一句话结论关键词
Q1ThreeBase+useThree 两层,shallowRef 管对象useThree shallowRef
Q2GLTFLoader 包 Promise + RGBELoader PBRGLTF RGBE HDR
Q3Postprocessing+Raycaster,scale 反算坐标OutlinePass Raycaster scale
Q4lil-gui 分 6 文件夹+Statslil-gui Stats
Q5v-scale-screen 等比缩放+scale 坐标等比缩放 全屏
Q6clean() 全链 dispose+双释放 WebGLdispose 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直观

⚠️ 备注:数字孪生"给客户看"比"炫技"更打动。


最大难点?

  1. v-scale-screen 缩放和 Three 坐标系适配——scale 后 clientX/Y 到 NDC 全乱、尤其 Y 减 header,调试很久才找到正确公式
  2. 内存泄漏——早期没 dispose 切几次就卡,后系统化加 clean() 清理链

💡 关键词scale坐标反算 内存泄漏clean

⚠️ 备注:这两个坑跨 06/07/05 前后呼应,讲出来有体系。


遗憾/弯路?

  1. 没有模型动画(转的风扇/流动液体),设备运行时状态未能动态呈现
  2. 没接实时 IoT 数据——该把设备数据映射到 3D(温度高变红)没做,纯展示
  3. 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协同管控平台

前端开发工程师 · 面试准备