code-built-3d · git:20261102.47f8439 · 2026-11-02 · sha256 03489f377115609d
code-built-3d git:20261102.47f8439B
Immutable. This exact content never changes and is served at /api/v1/blob/03489f377115609d.
---
name: code-built-3d
description: "用代码从零件搭 3D 场景并能反过来拆开:每个物件是一个往 Assembly 里加零件的函数(盒子、圆柱、球、线),渲染成技术插画(白灰三档面、细黑描边、白雾),正交等距相机在镜头之间连续运镜,外壳可以降下去露出内部,选中的部分能爆炸成零件并列出零件清单,场景按块无限生成。零图片、零外部资源,three.js 已内置。演示是「无尽楼」:向上无尽的公寓楼,电梯一直往上走,点任意一层进去看、拆开。当用户说「用代码搭一个 3D 场景」「等距 3D 城市」「做成 Train of Thoughts 那种」「全程代码生成不要图片」「能剖开 / 去壳 / 爆炸的 3D」「无限生成的场景」「three.js 做构造」时使用。只在明确请求时触发,忽略顺带提及。"
---
# 代码搭 3D 场景
这个 skill 做两件事:从零件把一个东西搭出来,再把它拆开。两件事用同一套零件数据。
| 环节 | 怎么做 | 在哪 |
|---|---|---|
| 零件 | 每个物件是一个函数,往 `Assembly` 里加 `box / cyl / cone / ball / line`。每个零件带名字、角色(`shell` 是能降下去的外壳)、尺寸,可选爆炸方向 | `assets/kit.js` 的 `Assembly` |
| 画法 | 面的明暗按朝向分三档(顶面最亮,朝镜头的两面依次暗一档),每条边描细黑线;离焦点越远(竖向距离或深度)越融进白色 | `createLook()`,一个着色器 |
| 镜头 | 正交相机。镜头 = 目标点、缩放、方位角、仰角,换镜头时用 smootherstep 连续过渡,缩放按对数插值。不硬切 | `Director` |
| 去壳 | 外壳按 y 方向缩到 0,像墙沉进地板。看房间时,上面的楼层整体抬起 | `setShell()`,demo 里的 `LIFT` |
| 拆开 | 把选中的部分改成逐零件建模,零件分层往上升,水平方向不超出给定范围;`manifest()` 给出零件清单(名字、尺寸、位置) | `exploder()`、`Assembly.manifest()` |
| 交互 | 悬停显示包围框和标签,点击切到对应镜头;DOM 气泡钉在 3D 点上,被雾吞掉时自动隐藏 | `picker()`、`outline()`、`labels()` |
| 无限 | 以一个移动的坐标为中心,只保留前后若干块,出范围的块回收;同一个种子永远生成同一座城 | `streamer()`、`rng(seed)` |
## 文件
- `assets/kit.js`:引擎(ES 模块)。
- `assets/tower.js`:演示用的零件库,包括人、椅子、桌子、灯、植物、空调外机、7 种房间、楼层、电梯轿厢、邻楼分段(5 种立面)、连廊、电缆。
- `assets/demo.html`:无尽楼。一份 import map 指向 `vendor/`。
- `assets/vendor/`:three.js r184 的 `three.module.min.js`、`three.core.min.js`、`BufferGeometryUtils.js`,以及 MIT 许可证 `THREE-LICENSE`。不依赖 CDN。
- `scripts/serve.mjs`:静态服务器。ES 模块不能从 `file://` 加载,必须用它打开。
- `scripts/check.mjs`:验收脚本,通过页面上的 `window.__scene` 驱动。
本地打开演示:
```bash
node scripts/serve.mjs assets 8765 # 在本 skill 目录下运行
```
然后打开 `http://127.0.0.1:8765/demo.html`。
## 步骤
1. **先列零件,再写代码**。把要搭的东西拆成层级:场景 → 块 → 物件 → 零件。每个零件写清名字、大概尺寸、属于外壳还是内部。这份清单就是拆解线的部件清单,`manifest()` 会原样输出。
2. **写零件函数**。一个物件一个函数,参数放位置、朝向和随机数。照着 `tower.js` 的 `person / chair / table / acUnit` 写。尺寸用真实比例,比如人约 1.7、门约 2、层高 3.4,画面才会显得可信。
3. **决定哪些合并、哪些单独建模**。远处和不交互的整块合并,只占 2 次绘制。要拆、要点选的部分用 `build({ separate: true })`,等需要时再改成单独建模,用完合并回去。
4. **定镜头**。至少要有:远景、跟拍主角、进入内部、换一个角度。巡游按时长轮换;用户点了以后停止巡游,按 Esc 回到巡游。
5. **加去壳和拆开**。外壳零件统一标 `role: 'shell'`。拆开时给 `bounds`,防止零件穿出墙外。
6. **接无限生成**。块的大小要大于一屏的高度或宽度;前后保留的范围要覆盖远景能看到的范围,否则会看见楼顶或楼底。
7. **暴露 `window.__scene`**(形状见 demo),然后验收:`node scripts/check.mjs <目录> --out <截图目录>`,全部 ✓ 后再看截图。
## 硬规则
- **零图片**:不加载任何贴图或图片文件。窗户、文字以外的细节全部用几何体表现。气泡里的表情用内联 SVG 画。验收会拦截图片请求,以及任何离开所在目录的请求。
- **只学手法**:参考作品的题材、角色设定和文案不照搬。
- **颜色克制**:只有白、灰两档和黑色点缀,不加任何彩色。描边只用一种墨色。
- **镜头不硬切**:所有换景都要连续过渡。减少动态效果时不自动巡游,换镜头直接跳到位。
- **逻辑和画面分开**:楼层、焦点、是否已拆开这些状态在 JS 里,画面跟着状态走;`advance(sec)` 能不等真实时间直接推进模拟,测试靠它。
- **性能**:远处的东西合并成大块;单独建模只给当前在看的那一块。
## 常见坑
- 往下看房间时,上一层的楼板和外墙会挡住房间深处。解决办法是把上面的楼层抬起来,或者降低仰角。光调雾不管用。
- 用屏幕坐标点选时,要瞄准物体朝外那面墙的中点,不要瞄准物体的中心。从上往下看,中心对应的屏幕位置往往落在上一层上。
- 深度雾必须按相机距离计算:相机距目标 400,所以 `uDNear/uDFar` 设在 404–452。设成绝对的 150–330,整个画面会全被雾盖住。
- CSS 里 `display:flex` 会覆盖 `hidden` 属性,必须加 `[hidden]{display:none!important}`。
- 相邻楼层的房型用"序号 ×3 + 0 或 1,再对 7 取余"来决定,可以保证相邻两层永远不重复。
## 验收记录
2026-10-27,`assets/demo.html`,Playwright Chromium 156 软件渲染,`check.mjs` 16/16 通过:
- 画面:远景墨线占比 8%。
- 巡游:相机确实在镜头之间移动。
- 点选:点第 7 层外墙进入第 7 层;该层外壳降到 0,上一层抬起 3.6。
- 拆开:点"拆开"后爆炸到 1,零件清单 24 行;点"装回去"能复原。
- 无限:模拟 120 秒从 8.8 层升到 37 层,块数一直是 35、物体数一直是 416。
- 资源:没有报错,没有图片请求,没有任何离开所在目录的请求。
- 手机:390 宽无横向溢出,按钮都在屏幕内。
- 减少动态效果:画面静止。
- 帧率:软件渲染约 25fps,只作参考。
没验的:真实显卡的帧率;真机 Safari 和 Firefox;触屏操作。
和原作的差距(用户看过截图前算待验证):
- 房间内部比原作稀疏。
- 没有地面、街道和高低错落的屋顶。
- 人物动作是静止的。