# sky-pool 乳房建模管线（Blender → 网页 JSON 几何）

> 版本：2026-09-09。本文档记录 `sky-pool.html`（五女泡澡揉乳交互网页）人体/乳房建模从
> 「three.js 程序化 SphereGeometry」升级为「Blender 参数化建模 + JSON 几何导出」的技术方案与实现。
> 目标：保真 5 种截然不同的巨乳形体（宫宵月 S 型 / 姜凝香 K 杯冰钟 / 影姬半球 / 王姐 H 杯下垂 / 方圆娇小 F 杯），
> 同时保留乳房 `onBeforeCompile` 顶点 shader 的【捏压凹陷 / 乳浪 / 乳尖硬起】动态变形。

---

## 1. 为什么放弃 GLB / GLTF，改用自定义 JSON

`sky-pool.html` 的硬约束：

- **单文件、完全离线**，双击即开，无构建、无网络。
- three.js 是**内联**在 `<script>` 里的（全文件约 715KB），**没有引入 GLTFLoader**。
- `file://` 协议下，外部 `fetch()`（GLB 二进制、纹理）会被 **CORS 拦截**，无法加载。

因此不能用 GLB 在线加载。自定义 JSON（`positions/uvs/indices/meta`）可以内联成一段 JSON 常量，
用 `new THREE.BufferGeometry()` + `setAttribute('position'/'uv')` + `setIndex()` 读入，全程离线、零新依赖。

---

## 2. 管线总览

```
sky-pool.html 的 CHARACTERS(角色乳体参数)
        │  size/droop/spring/damp/wave/nip …
        ▼
generate_all_breasts.py   ← 参数桥接层(同一份 CHARACTERS 驱动两边)
        │  映射为建模参数 baseR/depth/sag/squash/point
        ▼
build_breast.py  (bpy 无头)   参数化软乳放样 → bmesh 重算法线 + 平滑着色
        │  导出 breast_<id>.json (单乳, positions/uvs/indices/meta)
        ▼
make_inline.py  (bpy 无头)   5 乳合并 + 居中性平移 + 去 normals(省体积)
        │  产出 breasts_inline.json (~386KB)
        ▼
sky-pool.html  内联为 JSON 常量 → BufferGeometry → Mesh(乳) + onBeforeCompile 顶点 shader
```

**资产目录**（项目根）：
- `assets/blender/human-base-meshes.blend` —— Blender 官方 Human Base Meshes（**CC-0**），含 `Stylized Female` 女性素体（2.2 万顶点、纯 quad 拓扑、UDIM UV、多分辨率雕刻友好）。作为**躯干/形体地基**（头部/脸仍用程序化卡通脸，素体脸偏写实暂不替换）。
- `assets/models/breast_<id>.json` —— 单个乳几何。
- `assets/models/breasts_inline.json` —— 5 乳合并包，内联进 html。
- `assets/models/all_breast_params.json` —— 5 角色的乳体参数汇总。

**建模脚本**（`scripts/sculpt/`）：
- `build_breast.py` —— 核心生成器。
- `generate_all_breasts.py` —— 读 CHARACTERS 参数批量生成 5 角色。
- `make_inline.py` —— 合并 + 居中 + 去 normals。
- `breast_shape.py` / `probe_mesh.py` / `render_preview.py` —— 探索/诊断/预览。

---

## 3. build_breast.py 详解

### 3.1 坐标约定
在**局部坐标**建模，网页读取后在此基础上 blend 到角色上：

- 乳根（贴胸壁的圆盘）在 **z=0 平面**（用平面圆盖封闭）。
- 乳轴沿 **+Z**，乳尖朝 **+Z**（`deformBreast` 的 `apex=clamp(p.z/baseR)` 乳尖硬起判定因此天然有效）。
- **y 为下垂方向**（`-y`）：中心线按 `y = -sag*depth*sin(t·π/2)` 下沉，越近乳尖越垂，形成乳房的自然重力下垂。

（注：`make_inline.py` 把每个乳的 z 缩放成「乳根 z=0、乳尖 z≈+baseR」，`mesh.position.z` 即乳根贴胸点，乳体朝前凸出。）

### 3.2 网格生成（环形拓扑，shader 友好）
`rings × segs` 的规整环形拓扑，配合以 `uGrabPos` 为圆心的高斯衰减（`exp(-d²/sig²)`）捏压变形正好合适。

- **半径**：`r = baseR · sin(min(1.0, t·1.15)·π/2) · (1 - 0.10t²)` —— 乳根较宽，近根略鼓出，向乳尖收小。
- **乳尖锐度** `point`：在 `t>0.72` 加快收尖（`point>1` → 更尖，`point<1` → 更圆钝）。
- **桃形上扁下圆** `squash`：对上半（`sin(a)>0`，即朝上/朝外的 y 分量）压扁 `× (1-squash)`——软乳受重力上缘摊平、下缘饱满的桃/泪滴形。
- **下垂** `sag`：中心线 + 峰值半径共同作用。

### 3.3 平滑与法线
- 用 `bmesh` 建网格并 `bmesh.ops.recalc_face_normals` 统一面绕序/朝向（避免法线翻转导致的面片感）。
- 平滑着色：**纯数据方式** `for p in mesh.polygons: p.use_smooth=True; mesh.update()`。
  ⚠️ 不要依赖 `bpy.ops.object.shade_smooth()`——多对象选择/active 对象在无头脚本里不可靠，且 Blender 4.0 默认 `use_auto_smooth` 关闭会导致平直着色。

### 3.4 导出
`positions`（Float32）、`normals`、`uvs`（`atan2` 按角度展开，z/depth 为 v）、`indices`（`mesh.loop_triangles` 三角化）。
`meta` 里带 `baseR/depth/sag/squash/rings/segs/verts/tris`，供网页端取 `baseR` 喂 shader。

---

## 4. 参数映射（CHARACTERS → 建模参数）

`generate_all_breasts.py` 里 `param_for(c)`：

| 建模参数 | 来源 | 说明 |
|---------|------|------|
| `baseR` | `0.24*(0.86+size*0.7)` | 与 sky-pool.html 原 SphereGeometry 半径公式**完全一致** |
| `depth` | `baseR*(0.90+0.10*size)` | 乳长接近 baseR，避免乳轴过长成「细长棍」 |
| `sag` | `droop>0.3 ? 0.05+(droop-0.3)/0.6*0.22 : 0.05` | 下垂弧量做轻微下垂弧度（0.05~0.28），不拖成长串 |
| `squash` | `0.10+droop*0.22` | 上扁下圆桃形度；下垂重→更泪滴 |
| `point` | `1.35-droop*0.35` | 乳尖锐度；圆钟→偏圆，下垂→收敛 |

当前 5 角色生成结果（`assets/models/all_breast_params.json`）：

| 角色 | baseR | depth | sag | squash | point | 特征 |
|------|-------|-------|-----|--------|-------|------|
| 宫宵月 gxy | 0.374 | 0.374 | 0.204 | 0.258 | 1.098 | 最大、S 型 |
| 姜凝香 jnx | 0.368 | 0.366 | 0.167 | 0.236 | 1.133 | 冰钟、圆润 |
| 影姬 yji | 0.351 | 0.346 | 0.123 | 0.210 | 1.175 | 半球、挺拔 |
| 王姐 wz | 0.354 | 0.350 | 0.263 | 0.294 | 1.042 | 下垂泪滴、H 杯 |
| 方圆 fy | 0.327 | 0.318 | 0.057 | 0.170 | 1.238 | 娇小、最尖饱 |

---

## 5. 网页侧集成（已完成）

1. **内联 JSON**：`breasts_inline.json`（约 383KB）已作为 `const BREASTS_JSON={...}` 常量内联进 sky-pool.html。
   文件体积 ~715KB → ~1.1MB，仍离线可开。
2. **buildBody() 乳房部分**（已完成）：乳房改用 `BREASTS_JSON[c.id]` 构建 `BufferGeometry`：
   ```js
   const j=BREASTS_JSON[c.id];
   const g=new THREE.BufferGeometry();
   g.setAttribute('position',new THREE.Float32BufferAttribute(j.positions,3));
   g.setAttribute('uv',new THREE.Float32BufferAttribute(j.uvs,2));
   g.setIndex(j.indices); g.computeVertexNormals(); g.userData.baseR=j.meta.baseR;
   ```
3. **坐标对齐**（最终约定 `make_inline.py`）：
   - 几何乳根圆盘在 `z=0`（贴胸壁附着点），乳尖在 `z≈+baseR`（zscale=baseR/depth）。
   - `mesh.position` 的 z 为乳根贴胸点；乳体朝前(+z)凸出。
   - `mesh.rotation.x=-0.25` 让乳轴略下垂；`mesh.scale≈1.4` 放大成巨乳。
   - `deformBreast` 的 `apex=clamp(p.z/baseR)`（乳尖硬起）仍成立（乳尖朝 +z）。
   - 乳晕/乳尖挂载点：取几何 z 最大弧面中心的 y 均值（`tipY`）+ 乳尖（`tipZ=zmax`），挂到乳峰朝外凸点。
4. **shader 兼容**：`deformBreast` 的 `apex=clamp(p.z/baseR)`（乳尖硬起）在新几何乳尖朝 +Z 下仍成立。
   注意 `baseR` 要用 `j.meta.baseR` 而非再算一次。
5. **验证**：headless 用 `--enable-unsafe-swiftshader` 只能验证标准材质；要看 PhysicalMaterial（cream/光泽/通透）效果，
   用 `--use-gl=egl` 或真实浏览器。

---

## 6. headless 验证要点（避坑）

- **Blender 渲染**：无显示环境，用 Cycles **CPU**。apt 版 Blender **无 denoiser**（`scene.cycles.use_denoising=False`，
  否则报 `RuntimeError: Build without OpenImageDenoiser`）。
- **网页渲染**：`google-chrome --headless=new --no-sandbox --enable-unsafe-swiftshader`
  （不加会收到 `Automatic fallback to software WebGL has been deprecated` 的废弃回退警告，swiftshader 是受支持的软渲染）。
- **材质差异**：`MeshPhysicalMaterial`（clearcoat/sheen）在 SwiftShader 后端可能**不渲染**（画面空）——
  用 `MeshStandardMaterial` 验证几何，或 `--use-gl=egl` 验证物理材质；真实浏览器有 GPU 无此问题。
- **异步读取**：headless `virtual-time-budget` 下，用 `Promise.all().then()` 有时被 three UMD 干扰报
  `is not a function`，改用 **async/await** 更稳妥。

---

## 7. 体积权衡

`breasts_inline.json` ≈ 386KB（5 乳 × 866 vert × (3+2) float + indices）。内联后 sky-pool.html ≈ 1.1MB。
若需再压缩：可去 `uvs`（乳房用纯色/程序材质，非贴图）或降 `rings/segs`。当前保持离线双击可用前提下先不做激进压缩。
