【Three.js】day112-boss-workshop-v2

作者:mario 发布时间: 2026-09-07 阅读量:5 评论数:0

Day 112 · BOSS 战 — 3D 车间 2.0:十天资产拧成一件作品

第 16 周收官。今天把 Day 106-111 攒下的全部资产——真实模型、贴图质感、阴影描边、关键帧动画、加载降级——拧成"3D 车间 2.0"。上午组装(全效果整合 + 布局 + 交互),下午验收(功能清单 + 性能 + 降级演练 + 边界定稿)。结束时,你拥有阶段 3 的第二件可展示作品,以及"资产工程"这套能在简历上写、面试里讲的能力。


目录


一、BOSS 战定位:组装不是重写

1.1 今天干什么、不干什么

✅ 今天做:
   1. 全效果装配:模型 + 贴图 + 环境 + 阴影 + 描边 + 动画(只接线不重写)
   2. 健壮性:加载进度 + 降级回退 + 释放验证(Day 111 直接入库)
   3. 边界定稿:boundary-three-vue.md v2(补"资产/加载态"节)
   4. 双验收:功能清单 + 性能表,留档进 docs/acceptance/
   5. 交付:45 秒演示 + README + 简历条目

❌ 今天不做(记入 Week 17 待办):
   粒子系统 / LOD 层级细节 / 自定义着色器 —— Week 17 场景与性能进阶
   后处理进阶(辉光/景深/色彩分级)—— Week 17+
   WS 实时数据驱动 3D —— 第 6 个月(Week 26+)孪生主线

1.2 复用清单(不许重写)

零件

出处

今天角色

BufferGeometry + InstancedMesh

Day 106

罐体阵列(实例化)

贴图 + 环境贴图

Day 107

质感材质

GLTF 模型 + 五步体检

Day 108

真实设备

阴影 + OutlinePass

Day 109

氛围 + 选中高亮

AnimationMixer + 关键帧

Day 110

设备动作

AssetManager + 降级

Day 111

加载/健壮性

SceneBridge/SceneEvents

Day 103

边界契约(不动)


二、车间 2.0 整体规划(效果清单)

2.1 平面布局(延续 Day 105,新增罐体阵列)

          X 轴 →
     ┌──────────────────────────────────────────┐
     │  [泵-01]  [泵-02]  [冷却塔]  [罐体阵列×N]   │  ← 上排:真实模型 + 实例化罐体
     │                                          │
     │              ┌─────────┐                 │
     │              │ 地 面   │  阴影投下       │
     │              └─────────┘                 │
     │                                          │
     │  [风机-01]  [风机-02]  [主机]             │  ← 下排:真实模型
     └──────────────────────────────────────────┘
       上排真实模型 + 下排真实模型 + 一侧实例化罐体阵列

2.2 效果清单(验收对照)

□ 真实感:GLTF 模型(五步体检后)替代拼装几何
□ 质感:map/normalMap + 环境贴图(金属不再发黑)
□ 氛围:主光阴影(设备踩地)+ OutlinePass(选中描边)
□ 动画:模型自转 / 叶轮转 / 指示灯闪(mixer + clip)
□ 高效:罐体阵列用 InstancedMesh(N 台 = 1 draw call)
□ 健壮:加载进度条 → 失败自动降级几何版 → 卸载零泄漏

三、组装一:全效果装配(Day 106-110 汇总)

引擎的"装配清单"——把十天能力按序接进 scene-engine(Day 111 整合版再扩展):

// src/core/three/scene-engine.ts(Day 112 全效果版:装配清单)
import * as THREE from "three";
import { AssetManager } from "./asset-manager";
import { createAtmosphere } from "./create-atmosphere";
import { createInstancedTanks } from "./create-instanced-devices";
import { attachMixer } from "./animation-mixer";   // Day 110
import { deviceLayout, MODEL_URLS } from "../config/device-layout";

/**
 * 3D 车间 2.0 引擎:全效果 + 健壮性(Day 103 契约不变)
 * 装配顺序:光照 → 地面 → 环境 → 模型 → 实例化 → 动画 → 氛围 → 交互
 */
export function createSceneEngine(canvas: HTMLCanvasElement) {
  // —— 基础(Day 99-100):场景 / 相机 / 渲染器 / 光照 ——
  // ...(略,同前)...

  // —— 环境贴图(Day 107):金属质感的基础 ——
  const pmrem = new THREE.PMREMGenerator(renderer);
  scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;

  // —— 地面 + 阴影(Day 109)——
  const floor = new THREE.Mesh(
    new THREE.PlaneGeometry(24, 24),
    new THREE.MeshStandardMaterial({ color: 0x16233a, roughness: 0.9 })
  );
  floor.rotation.x = -Math.PI / 2;
  floor.receiveShadow = true;               // 接影端
  scene.add(floor);
  const grid = new THREE.GridHelper(24, 12, 0x00c6ff, 0x16233a);
  grid.position.y = 0.01;
  scene.add(grid);

  // —— 模型资产(Day 108 + Day 111 加载)——
  const asset = new AssetManager();
  let models: THREE.Object3D[] = [];
  let mixers: THREE.AnimationMixer[] = [];

  // —— 氛围装配(Day 109:阴影开关 + 描边后处理)——
  const atmosphere = createAtmosphere(renderer, scene, camera, canvas);

  async function initDevices() {
    asset.onProgress = (pct) => emitLoadState({ state: "loading", pct });
    asset.onReady = () => startLoop();
    asset.onError = () => {
      // 降级:几何拼装版(Day 111)——车间不崩
      models = buildFallbackWorkshop(deviceLayout);
      models.forEach((m) => { scene.add(m); pickables.push(m); });
      emitLoadState({ state: "error", pct: 100 });
      startLoop();
    };

    try {
      models = await Promise.all(
        deviceLayout.map((c) => asset.getModel(MODEL_URLS[c.type]))
      );
      models.forEach((m, i) => {
        m.position.set(...deviceLayout[i].position);
        m.userData.deviceId = deviceLayout[i].id;
        m.traverse((c) => { if (c instanceof THREE.Mesh) c.castShadow = true; });  // 投影端
        scene.add(m); pickables.push(m);
        // 模型自带动画(Day 110)
        const mixer = attachMixer(m);
        if (mixer) mixers.push(mixer);
      });

      // 实例化罐体阵列(Day 106:1 draw call)
      const tanks = createInstancedTanks(scene, TANK_LAYOUT);
      pickables.push(tanks);   // 实例化也参与拾取(Day 106 第六节)

      asset.notifyReadyIfAllDone();
    } catch { asset.triggerError(); }
  }

  // —— 渲染循环(全效果:动画 + 相机 + 后处理)——
  const clock = new THREE.Clock();
  let rafId = 0;
  function startLoop() {
    rafId = requestAnimationFrame(function tick() {
      const delta = clock.getDelta();
      mixers.forEach((m) => m.update(delta));       // 动画(Day 110)
      controls.update();                            // 相机(Day 104)
      atmosphere.composer.render();                 // 后处理(Day 109)替代 renderer.render
      rafId = requestAnimationFrame(tick);
    });
  }

  return {
    bridge: { initDevices, onLoadState(cb) { /* ... */ }, /* resize/setAnimation */ },
    events: { on(cb) { /* Day 104 出向桥 */ } },
    start() { /* 循环 */ },
    dispose() { /* Day 111 全量释放 */ },
  };
}

观察装配顺序的依赖关系:环境贴图要先于模型创建(模型材质要采样它)→ 模型要等加载 → 动画要等模型 → 后处理要等场景。顺序即架构——这就是为什么"只接线不重写"比"从头写一遍"更考验理解。


四、组装二:健壮性装配(Day 111)

Day 111 的健壮性直接入库,验收时演练三件事:

① 正常路径:进度条 → 100% → 真实模型车间出现
② 降级路径:改坏模型 URL → 提示"简化模式" → 几何版车间照常交互
③ 释放路径:反复进出页面 30 次 → 显存不涨(Performance/Memory)

这三条是"工程价值"的证明:能正常跑是能力,能降级不崩是职业素养。面试演示时主动展示降级,比被问到才承认强得多。


五、最终验收:功能清单逐项打勾

对照 week16 总览第四节,逐项执行并留档:

□ 功能:真实模型 + 罐体阵列 + 阴影 + 描边高亮 + 动画,可旋转/缩放/点击
□ 质感:环境贴图生效(金属不发黑),纹理清晰——Day 101 对比截图存档
□ 交互:点击设备描边 + 详情面板;hover 反馈;拖拽不误触
□ 动画:设备自转 + 叶轮/指示灯动,帧率变化不影响速度
□ 健壮:断网/加载失败 → 简化模式提示 + 几何版可用
□ 边界:Vue 组件零 Three 依赖;加载态只走 onLoadState;边界文档 v2 定稿
□ 工程:vue-tsc 零报错、资产目录规范、无 CDN

每个勾都留档(截图/录屏/实测表进 docs/acceptance/)——"有证据地通过"是验收与自嗨的区别(Day 70/105 原话)。


六、性能验收与降级演练

场景

目标

我的实测

全效果静态 10 分钟

≥ 50fps

___

交互旋转/缩放 5 分钟

≥ 50fps

___

罐体阵列 draw call(N 台)

≤ 10(实例化 1 次)

___

反复进出页面 30 次

显存不涨

___

降级模式 5 分钟

≥ 50fps(几何版更便宜)

___

性能不够时的排查顺序(本周工具链):draw call(Performance 面板)→ 阴影 mapSize → 贴图尺寸 → 动画 mixer 数量。按此序逐级降,别一上来就砍效果。


七、边界文档定稿(v2)

Day 103 v1 → 今天补"资产/加载态"节,定稿:

# 3D 场景 × Vue 边界决策文档(Day 112 定稿 v2)

## 数据裁决表(Day 103,v2 复核无出入)

## 桥契约(v2 新增加载态)
- SceneBridge 新增 onLoadState(state, pct)——Vue 只消费数字
- AssetManager/模型/贴图全部引擎内部持有,不进 Vue

## 响应式纪律(v2 验证)
- Three 对象普通变量持有;Vue 组件零 Three 依赖(代码扫描确认)

## 反例推演记录(v2 新增)
- "AssetManager 放进 Vue" → 组件里出现 Three 类型,边界破裂
- "资源没等齐就渲染" → 黑屏跳变(Day 111 坑 1)

## 复审触发条件
- 第 6 个月接 WS:updateStates 分频与脏检查
- 设备数 > 500:draw call(实例化/LOD)重审
- 新增模型类型:MODEL_URLS 映射表扩展(与代码解耦)

八、交付资产:演示视频 / README / 简历条目

8.1 45 秒演示脚本

【00:00-00:05】开场:45° 视角车间全景(真实模型 + 阴影 + 环境光)
  旁白:"基于 Vue3 + Three.js 的 3D 智慧车间 2.0。"
【00:05-00:15】质感:放大金属罐体 → 转一个角度反光
  旁白:"GLTF 真实模型 + PBR 材质:贴图、法线与环境光场。"
【00:15-00:25】动画:叶轮转动 + 指示灯闪烁 + 罐体阵列
  旁白:"AnimationMixer 关键帧动画与实例化渲染。"
【00:25-00:35】交互:点击设备 → 描边高亮 + 详情面板
  旁白:"OutlinePass 描边选中,Raycaster 拾取联动面板。"
【00:35-00:40】健壮性:Network 断网刷新 → 简化模式提示
  旁白:"加载失败自动降级几何版,页面不崩。"
【00:40-00:45】收尾淡出
  旁白:"资产工程化:加载、缓存、降级、释放全链路。"

8.2 README 要点

# 3D 智慧车间 2.0 Three.js Workshop v2
一句话:Vue3 + Three.js 的 3D 车间——真实模型、PBR 质感、
阴影描边、关键帧动画、加载降级,引擎与 Vue 边界清晰。
![车间 2.0 截图](docs/workshop-v2.png)

## 快速启动
```bash
npm install
npm run dev

亮点

  • [x] GLTF 真实模型 + 五步体检(单位/方向/材质兜底)

  • [x] PBR 材质:贴图 + 法线 + 环境贴图(解决金属发黑)

  • [x] 阴影 + OutlinePass 描边选中

  • [x] AnimationMixer 关键帧动画(叶轮/指示灯)

  • [x] InstancedMesh 罐体阵列(N 台 1 draw call)

  • [x] 加载进度 + 失败降级 + 零泄漏(边界文档 v2)


### 8.3 简历条目(150 字内)

> **3D 智慧车间**(Vue3 + Three.js,个人项目)
> 构建可交互 3D 工业车间:GLTF 真实模型导入与资产工程化(统一加载管理器、缓存克隆、失败降级);PBR 材质体系(贴图/法线/环境光场);阴影与 OutlinePass 描边高亮;AnimationMixer 关键帧动画;InstancedMesh 实现罐体阵列单次 draw call。设计引擎与 Vue 的边界契约(SceneBridge/SceneEvents),Vue 侧仅持有 id 与加载进度数字,组件零 Three 依赖。全效果稳定 50fps+,断网自动降级不崩溃。

> 简历自检(Day 70/105 纪律):每句都能追问 10 分钟——"资产工程化""失败降级""边界契约"都是你亲手做的,弹药充足。

---

## 九、第 16 周毕业总结与知识图谱

### 9.1 七天上台阶

Day 106 几何进阶 → BufferGeometry 原理 + InstancedMesh(50→1 draw call)
Day 107 材质贴图 → PBR 材质 + 环境贴图(金属不发黑)
Day 108 模型导入 → GLTF/GLB + 五步体检(真实设备进车间)
Day 109 阴影描边 → Shadow + OutlinePass(氛围 + 选中高亮)
Day 110 动画系统 → AnimationMixer(设备动起来)
Day 111 资产集成 → 加载态 + 资源管理 + 降级(健壮性)
Day 112 BOSS 战 → 3D 车间 2.0(全效果 + 双验收 + 交付)


### 9.2 能力对照:Week 15 末 vs Week 16 末

| 能力 | 第 15 周末 | 第 16 周末 |
|------|-----------|-----------|
| 几何 | 内置几何拼装 | BufferGeometry 原理 + 自定义 + 实例化 |
| 材质 | 纯色 Standard | PBR 贴图 + 法线 + 环境光场 |
| 资产 | 无 | GLTF 导入 + 五步体检 + 缓存克隆 |
| 效果 | 三灯照明 | 阴影 + 后处理描边 |
| 动画 | 直接改属性 | AnimationMixer 关键帧 |
| 工程 | 无加载概念 | 加载/进度/降级/释放全链路 |

### 9.3 本周最有价值的三个认知

1. **封装要"知其成本"**(Day 106/108):BoxGeometry 背后是 24 顶点 36 索引,glTF 解析器是几千行——不重写,但要能讲清它替你做了什么。
2. **质感 = 光 + 材质 + 环境**(Day 107/109):环境贴图救金属、阴影让设备踩地——两个"一行配置"是质感的胜负手。
3. **健壮性是最贵的工程能力**(Day 111):加载、降级、释放——这些"不炫但保命"的活儿,是工业现场最看重的品质。

### 9.4 待办清单(Week 17 输入)

  • 粒子系统(扫描线/粉尘/火花——环境氛围)

  • LOD 层级细节(远景降模,配合上量)

  • 自定义着色器入门(ShaderMaterial——性能与特效的钥匙)

  • 后处理进阶(辉光/景深/色彩分级)

  • WS 实时数据驱动 3D(第 6 个月主线,边界已预留)


---

## 十、常见坑点(本周汇总)

### 坑 1:效果全开性能崩

排查顺序(第六节):draw call → 阴影 mapSize → 贴图尺寸 → mixer 数量。**逐级降,别一上来砍效果**。

### 坑 2:装配顺序错(环境贴图在模型之后)

症状:模型金属发黑。原因:环境贴图创建晚于模型材质采样。**修法:环境贴图先于模型创建**(第三节依赖关系)。

### 坑 3:阴影 + 描边 + 动画叠加后"哪里不对"

先隔离:关掉描边看阴影,关掉动画看描边——**逐项开关定位问题**,别在叠加态里猜。

### 坑 4:交付日加效果

"再加个辉光""加点雾"——组装日最容易失控。**修法:进 Week 17 待办,今天只验收**(Day 70/105 同款纪律)。

### 坑 5:边界悄悄破洞

验收时发现 Vue 组件 import 了 Three 类型。**修法:"组件零 Three 依赖"是边界验收项,不过关就重构到只走桥**。

---

## 十一、自测挑战

### T1 · 全效果组装(80 分钟)

完成第三、四节:真实模型 + 罐体阵列 + 阴影描边 + 动画 + 加载降级,全效果跑通。俯视/45°/近景各截图留档。

### T2 · 双验收(40 分钟)

完成第五、六节:功能清单逐项打勾 + 性能表填数 + 降级演练。不达标项按第六节排查顺序修复。

### T3 · 边界定稿 + 自检(30 分钟)

定稿 `boundary-three-vue.md` v2,全项目扫描"Vue 组件零 Three 依赖"。

### T4 · 交付资产(40 分钟)

按第八节录 45 秒演示 + README + 简历条目(150 字内)。**给"未来的自己"和面试官各留一份**。

---

## 十二、总结

| 环节 | 要点 |
|------|------|
| 组装 | 只接线不重写:模型→贴图→阴影→动画→健壮性 |
| 装配顺序 | 环境贴图→模型→动画→后处理(依赖即顺序) |
| 双验收 | 功能清单 + 性能表 + 降级演练,逐项留档 |
| 健壮性 | 进度 / 降级 / 释放三件套——工程价值的证明 |
| 边界 v2 | 资产与加载态归引擎,Vue 只拿数字 |
| 交付 | 45s 演示 + README + 简历条目(每句能追问) |
| 待办 | 粒子 / LOD / 着色器 / 后处理进阶 → Week 17 输入 |

---

**🎉 第 16 周毕业。** 你现在拥有:一台有真实模型、PBR 质感、阴影描边、会转会闪、能扛断网的 3D 车间 2.0——这是阶段 3 的核心可展示资产。下一周进入 Week 17:**场景与性能进阶**——粒子系统、LOD、自定义着色器入门。开始第 17 周。

评论