Day 105 · BOSS 战 — 第一个 3D 车间:把 7 天零件拧成一件作品
第 15 周收官。今天不做新知识点,做组装与验收:把 Day 99-104 攒下的零件——场景、循环、光照、几何、边界、交互——拧成第一个"会转、会亮、可点、边界清晰"的 3D 车间。上午组装(车间布局 + 多设备 + 交互),下午验收(功能清单 + 性能 + 边界文档定稿)。结束时,你拥有阶段 3 的第一件作品,和第 16 周建模学习的起点。
目录
一、BOSS 战定位:组装不是堆砌
1.1 今天干什么、不干什么
✅ 今天做:
1. 车间布局:地面网格 + 多设备就位(复用 Day 102 的 buildFanDevice)
2. 交互闭环:hover 高亮 + 点击详情面板(Day 104 引擎)
3. 边界定稿:docs/boundary-three-vue.md 从初稿到可交付
4. 双验收:功能清单 + 性能表,留档进 docs/acceptance/
5. 交付资产:30 秒演示 + README 条目 + 简历条目
❌ 今天不做(记入第 16 周待办):
阴影/后处理/动画曲线 —— Week 16 建模与资产
WS 实时数据驱动 3D —— 第 6 个月(Week 26+)孪生主线
draw call 深度优化 —— Day 106+ 性能主线
红线第 1 条的精神:视觉先于优化。今天的目标是"看得出、能演示、可验收",不是"最优化"。
1.2 复用清单(不许重写)
二、车间整体规划(先画图再动手)
2.1 车间平面图(俯视图)
X 轴 →
┌─────────────────────────────┐
│ (-6,2) (-2,2) (2,2) │ ← 上排:3 台
│ [泵1] [泵2] [冷却塔] │
│ │
│ ┌─────┐ │
│ │ 地 │ │
│ │ 面 │ (0,0) │
│ └─────┘ │
│ │
│ (-6,-2) (-2,-2) (2,-2) │ ← 下排:3 台
│ [风机1] [风机2] [主机] │
└─────────────────────────────┘
设备间距 4 单位,绕中心对称,方便旋转观察
2.2 布局配置(Vue 侧数据,入向桥喂入)
// src/config/device-layout.ts(Day 103 契约的实装输入)
export interface DeviceLayout {
id: string; // 设备 id(出向桥传的就是它)
type: "pump" | "fan" | "tower" | "main";
position: [number, number, number]; // 世界坐标
}
// 6 台设备:3 种类型,车间对称布局
export const deviceLayout: DeviceLayout[] = [
{ id: "pump-01", type: "pump", position: [-6, 0, 2] },
{ id: "pump-02", type: "pump", position: [-2, 0, 2] },
{ id: "tower-01", type: "tower", position: [ 2, 0, 2] },
{ id: "fan-01", type: "fan", position: [-6, 0,-2] },
{ id: "fan-02", type: "fan", position: [-2, 0,-2] },
{ id: "main-01", type: "main", position: [ 2, 0,-2] },
];
2.3 地面对齐(小细节,大观感)
所有设备 position.y = 0,Group 内部零件从 y=0 往上摆(Day 102 的"地面中心"定位技巧)——保证设备"站"在地面上,不悬空不陷地。
三、组装一:车间布局与设备就位
把布局配置变成场景里的 Group,给每个设备挂上 userData.deviceId(Day 104 拾取的基础):
// src/core/three/build-workshop.ts(组装核心:布局配置 → 场景树)
import * as THREE from "three";
import { buildFanDevice } from "./build-fan-device";
import type { DeviceLayout } from "../config/device-layout";
import { threeTheme as T } from "./theme";
/**
* 根据布局配置组装 3D 车间
* 产出:
* groups —— 设备 Group 列表(可拾取对象,带 userData.deviceId)
* fanGroups —— 风扇 Group 列表(循环里驱动自转)
*/
export function buildWorkshop(layout: DeviceLayout[]) {
const groups: THREE.Group[] = [];
const fanGroups: THREE.Group[] = [];
layout.forEach((cfg) => {
// 根据类型建不同设备(本周先统一用风扇设备,Week 16 换模型资产)
const { device, fanGroup } = buildFanDevice();
device.position.set(cfg.position[0], cfg.position[1], cfg.position[2]);
device.userData.deviceId = cfg.id; // ← 拾取要用的 id(Day 104)
groups.push(device);
fanGroups.push(fanGroup);
});
return { groups, fanGroups };
}
// 在地面加网格线(视觉锚点,让空间有尺度感)
const grid = new THREE.GridHelper(24, 12, 0x00c6ff, 0x16233a);
grid.position.y = 0.01; // 略微抬高,避免与地面 z-fighting(坑 1)
scene.add(grid);
GridHelper是 Three 自带的网格辅助线——工业场景的"地面标尺",一屏就能看出设备间距与空间尺度。留意y=0.01的微调:这是防 z-fighting(两个面重合时闪烁)的标准姿势。
四、组装二:交互闭环(hover + 点击 + 面板)
4.1 状态设计(边界纪律:Vue 只存 id)
<!-- src/views/WorkshopView.vue(车间页:Vue 层组装交互) -->
<script setup lang="ts">
/**
* 车间视图:3D 引擎 + 详情面板
* 边界:hover/selected 只存 id(string),引擎实例普通变量持有
*/
import { onMounted, onUnmounted, ref } from "vue";
import { createInteractiveEngine } from "../core/three/interactive-engine";
import { buildWorkshop } from "../core/three/build-workshop";
import { deviceLayout } from "../config/device-layout";
const canvas = ref<HTMLCanvasElement>();
const hoverId = ref<string | null>(null); // hover 面板数据源
const selectedId = ref<string | null>(null); // 详情面板数据源
const detail = ref<Record<string, unknown> | null>(null); // 设备详情(本周静态)
let dispose: (() => void) | null = null;
onMounted(() => {
const engine = createInteractiveEngine(canvas.value!);
// 布局配置 → 场景树(入向桥的实装:本周静态,第 6 个月换 store 快照)
const { groups, fanGroups } = buildWorkshop(deviceLayout);
engine.setDevices(groups, fanGroups);
// 出向桥:引擎报 id → Vue 更新状态(边界:只传 id)
engine.events.on((e) => {
if (e.type === "hover") hoverId.value = e.deviceId;
if (e.type === "select") {
selectedId.value = e.deviceId;
detail.value = getDeviceDetail(e.deviceId); // 静态详情(本周)
}
});
const stop = engine.start();
dispose = () => { stop(); engine.dispose(); };
});
onUnmounted(() => dispose?.());
</script>
<template>
<div class="workshop">
<canvas ref="canvas" class="workshop__canvas" />
<!-- 详情面板:id 驱动,纯 Vue 组件,零 Three 依赖 -->
<div v-if="selectedId" class="workshop__panel">
<h3>{{ selectedId }}</h3>
<p>类型:{{ detail?.type }}</p>
<p>状态:<span class="dot ok" /> 运行正常</p>
<p>温度:{{ detail?.temp }}℃</p>
<button @click="selectedId = null">关闭</button>
</div>
</div>
</template>
收尾前自查一遍边界:Vue 侧没有 import 任何 Three 类型(除引擎返回的结构)、没有持有任何 Mesh/Group 引用、交互数据全是 string。这就是 Day 103 契约的验收方式。
五、边界文档定稿(Day 103 初稿 → 今天定稿)
Day 103 写了初稿,今天组装完对照实做补全:
# 3D 场景 × Vue 边界决策文档(Day 105 定稿)
## 数据裁决表
(Day 103 表格,实做后确认无出入)
## 桥契约
(SceneBridge / SceneEvents 实装版,含方法注释)
## 响应式纪律(实做验证)
- Three 对象只用普通变量持有(引擎内部),确认未进 ref/reactive
- 出向桥只传 deviceId(string),确认 Vue 组件零 Three 依赖
## 反例推演记录
- "场景树进 ref" → 掉帧(Day 103 实验 T4 实测数据贴这里)
## 复审触发条件
- 第 6 个月接 WS:updateStates 分频与脏检查
- 设备数 > 500:draw call 与可见性剔除
- 新增设备配置面板:光照/布局配置归属重审
边界文档是这个项目最有含金量的交付物之一——它把"为什么这么分"写成了可复审的决策记录。面试被问"Three 和 Vue 怎么协作"时,这张表就是答案的骨架。
六、最终验收:功能清单逐项打勾
对照 week15 总览第五节,今天逐项执行并留档:
□ 场景:深色背景 + 网格地面 + 6 台设备对称就位(俯视/45° 各截一张)
□ 动画:设备缓慢自转 + 风扇独立旋转,转速与帧率无关(改 FPS 不转速)
□ 光照:三灯法则生效——设备有明暗立体感,指示灯恒亮
□ 交互:hover 设备变亮(蓝)、离开还原;点击弹详情面板(橙选中态)
□ 相机:OrbitControls 旋转/平移/缩放正常,拖拽不误触点击
□ 边界:Vue 组件零 Three 依赖;出向桥只传 id;边界文档定稿
□ 工程:vue-tsc 零报错;无 CDN 依赖;README 一键启动两条命令
🎯 每个勾都要留档证据(截图/录屏/实测表进
docs/acceptance/)——"我说通过了"和"有证据地通过了"是两回事(Day 70 原话)。
七、性能验收:FPS 与显存
如果 fps 不达标:先查 Day 100 坑 4(循环里 new 对象)与 Day 102 坑 4(每设备 new 几何体)。本周不追求极致 draw call——那在第 16 周。
八、交付资产:演示视频 / README / 简历条目
8.1 30 秒演示脚本(本周小而精)
【00:00-00:05】开场:45° 视角车间全景(地面网格 + 6 设备)
旁白:"基于 Three.js + Vue3 的第一个 3D 车间。"
【00:05-00:15】旋转观察:拖动视角绕车间一圈 → 滚轮缩放
旁白:"OrbitControls 相机控制,俯视到平视自由切换。"
【00:15-00:25】交互高光:hover 设备变亮 → 点击弹出详情面板
旁白:"Raycaster 射线拾取:点击设备高亮并联动信息面板。"
【00:25-00:30】收尾:风扇特写 + 淡出
旁白:"边界清晰:场景图归引擎,业务状态归 Vue,走桥通信。"
8.2 README 要点(沿 Day 70 模板)
# 3D 智慧车间 Three.js Workshop
一句话:Vue3 + Three.js 的 3D 车间雏形——可旋转缩放的工业场景,
设备可点击查看详情,引擎与 Vue 边界清晰。

## 快速启动
```bash
npm install
npm run dev # 打开 http://localhost:5173
亮点
[x] 三灯法则光照 + 工业 token 配色(与 2D 大屏视觉一致)
[x] Group 层级组装(机身/风扇独立旋转)+ 设备复用
[x] Raycaster 拾取 + 事件桥(引擎报 id,Vue 渲染面板)
[x] 边界文档(docs/boundary-three-vue.md)
### 8.3 简历条目(150 字内)
> **3D 智慧车间**(Vue3 + Three.js,个人项目)
> 构建可交互的 3D 工业车间:OrbitControls 相机控制、三灯法则光照、Group 层级组装设备;基于 Raycaster 实现设备拾取与详情面板联动;设计引擎与 Vue 的边界契约(SceneBridge/SceneEvents),场景图归引擎、业务状态归 Vue,以事件桥单向通信、仅传设备 id,组件零 Three 依赖。稳定 50fps+。
> 简历自检(Day 70 纪律):每句话都能被追问 10 分钟——"三灯法则""边界契约""只传 id"都是你本周亲手做的,弹药充足。
---
## 九、第 15 周毕业总结与知识图谱
### 9.1 七天上台阶
Day 99 场景基础 → 第一个 3D 物体(Scene/Camera/Renderer)
Day 100 渲染循环 → 会自转的场景(rAF + Clock + FPS 纪律)
Day 101 光照材质 → 有质感的场景(三灯法则 + Standard)
Day 102 几何变换 → 会转风扇的设备(Geometry + Group 层级)
Day 103 Three × Vue → 边界图纸(3D 版三问法则 + 桥契约)★ 本周灵魂
Day 104 交互 → 可点击的场景(Raycaster + OrbitControls)
Day 105 BOSS 战 → 第一个 3D 车间(组装 + 验收 + 交付)
### 9.2 能力对照:第 15 周前 vs 后
| 能力 | 一周前 | 现在 |
|------|--------|------|
| 3D 渲染 | 无 | 场景/相机/渲染器/循环/光照/材质 |
| 场景组织 | 无 | Group 层级组装 + 复用纪律 |
| 架构 | 2D 边界经验 | 3D 版三问法则 + 双向桥契约 |
| 交互 | 2D 命中检测 | Raycaster 拾取 + 事件桥 |
| 交付 | 2D 大屏 | 第一个 3D 作品 + 边界文档 |
### 9.3 本周最有价值的三个认知
1. **3D 是整帧渲染**(Day 100):性能模型从"局部重绘"变成"每帧预算 16.7ms"——思考方式完全不同于 2D。
2. **边界是 3D × 框架的地基**(Day 103):场景树是自己的响应式系统,两个系统叠加 = 灾难。"传数据不传对象"这一条就值回本周全部时间。
3. **封装要知道它替你做了什么**(对照表):不手写矩阵/光照,但能讲清 Phong 三要素与 NDC 换算——这就是"会用"和"会讲"的分水岭。
### 9.4 待办清单(Week 16 输入)
阴影与后处理(OutlinePass 描边高亮——替换今天的 clone 材质方案)
模型资产(GLTF 导入真实设备模型,替代几何体拼装)
材质系统深化(贴图/纹理/环境贴图——解决 metalness=1 发黑)
draw call 优化与实例化(设备数上量时)
WS 实时数据驱动 3D 状态(第 6 个月主线,边界已预留)
---
## 十、常见坑点(本周汇总)
### 坑 1:z-fighting(面闪烁)
地面与 GridHelper 同高度时疯狂闪烁。修法:网格线抬高 `y=0.01`,或地面用不同的 y 值。**两个重合面必闪烁——3D 里的"高度必须错开"纪律**。
### 坑 2:设备悬空/陷地
布局配置的 y 设成了 0 但 Group 内部零件从 y=0 摆(底边在 0 之上)→ 设备"浮着"。修法:统一"地面中心"定位,Group 的 y 恒为 0。
### 坑 3:点击永远选不中
排查顺序(Day 104 坑 1 的完整版):NDC y 取反?→ canvas 尺寸?→ pickables 包含 Group 且 `true` 递归?→ 相机 near/far?→ userData.deviceId 挂上了吗?
### 坑 4:交付日加需求
"再加个旋转动画""加点粒子"——组装日最容易失控。修法:红线纪律,需求进 Week 16 待办,今天只验收。
### 坑 5:边界悄悄破洞
验收时发现某个组件 import 了 Three 类型。修法:**用"Vue 组件零 Three 依赖"作为边界验收项**——不过关就重构到只走桥。
---
## 十一、自测挑战
### T1 · 车间组装(80 分钟)
完成第二至四节:6 台设备对称布局 + 网格地面 + 交互闭环(hover/点击/面板)。俯视与 45° 各截图留档。
### T2 · 双验收(40 分钟)
完成第六、七节:功能清单逐项打勾留档 + 性能表填数。任何不达标项定位并修复(优先查 Day 100/102 的经典坑)。
### T3 · 边界自检 + 文档定稿(30 分钟)
用第五节模板定稿 `boundary-three-vue.md`,并全项目扫描确认"Vue 组件零 Three 依赖"。
### T4 · 交付资产(40 分钟)
按第八节录 30 秒演示 + 写 README 条目 + 简历条目(150 字内)。**给"未来的自己"和面试官各留一份**。
---
## 十二、总结
| 环节 | 要点 |
|------|------|
| 组装 | 复用 Day 99-104 零件,只接不写(红线) |
| 布局 | 先画平面图 → 配置数据 → 入向桥喂入 |
| 交互闭环 | 引擎报 id → Vue 存 id → Vue 渲染面板 |
| 边界定稿 | 组件零 Three 依赖 = 边界的验收方式 |
| 双验收 | 功能清单 + 性能表,逐项留档 |
| 交付 | 30s 演示 + README + 简历条目(每句能追问) |
| 待办 | 阴影/模型/材质/优化/WS → Week 16 输入 |
---
**🎉 第 15 周毕业。** 你现在拥有:一个会转、会亮、可点、边界清晰的 3D 车间 Demo,和一周沉淀的"3D 版边界方法论"——这是阶段 3(数字孪生)的第一块地基。下周进入 Week 16:**建模与资产**——GLTF 模型导入、几何进阶、材质系统深化。开始第 16 周。