Files
osmWorkflow/.trellis/spec/blender/asset-generation.md

226 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 资产生成
> 适用:改动 Blender 侧的几何构建、材质、实例化,或往场景里加新资产。
> 贯穿全篇的约束是两条:**压低对象数**GLB 要能在浏览器里跑)和
> **构建必须确定性**parity 校验的前提)。
---
## MeshBatch几何构建的主力
`mesh.py:1-9` 说明了它为什么存在:场景的绝大部分是平面多边形和拉伸棱柱,
**把它们批进一个 mesh datablock 能同时压低 Blender 对象数和导出 glTF 的节点数**
用法固定为「累积 → 一次 `finish()`」:
```python
batch = MeshBatch("Lake Surface", water_c, water_mat)
batch.add_polygon(ring, 0.10)
batch.finish() # water.py:12-14
```
### 三条内建行为
1. **自动去掉重复的闭合点**`mesh.py:41-42, 55-56`)。传闭合环或开放环都行,
`geom.py` 的宽容度一致
2. **退化输入静默返回**`len(ring) < 3` 直接 return不抛
3. **空批次 `finish()` 返回 `None`**`mesh.py:66-67`),不产生空对象
### 名字前缀是承重的
```python
# Foliage reads as blobby volume, so it wants smooth normals; the built
# environment wants its facets. The name prefix is the discriminator.
if self.name.startswith("Tree_") or self.name.startswith("Scrub_"):
for polygon in mesh.polygons:
polygon.use_smooth = True # mesh.py:72-77
```
**改植被对象的命名前缀会静默改变着色**。加新的植被类资产时要么沿用
`Tree_` / `Scrub_` 前缀,要么显式扩展这个判断。
### 单材质约束
一个 `MeshBatch` 只挂一个材质(`mesh.py:64`)。需要多材质就开多个 batch——
这也正是[材质顺序决定 GLB 索引](../pipeline/layer-registry.md#顺序是承重的)的地方。
### 便捷包装
`make_prism` / `add_roof``mesh.py:83, 89`)是「单个形体」的一次性包装,内部就是
`MeshBatch` + `finish()`。只放一个形体时用它们,批量累积时直接用 `MeshBatch`
---
## 材质:声明与构建分离
```
catalog.MATERIALS 声明「是什么」 纯 Python无 bpy
▼ materials.from_spec(spec) materials.py:198
真实的 bpy.types.Material 只在 Blender 内
```
这个拆分让 catalog 能被任何不启动 Blender 的工具读取(`materials.py:3-7`)。
### `kind` 选构建器
| `kind` | 走哪条路 | 必填字段 |
|---|---|---|
| `solid` | `make_material()` | `name``color` |
| `textured` | `make_textured_material()` | `name``diffuse``normal``scale` |
`solid` 可以再叠 `procedural`(噪声驱动的基色和凹凸,`from_spec` 里判断)。
可选字段一律 `spec.get(key, 默认值)`——**加新的可选字段不要改已有条目**。
### 加一种材质
1. `catalog.MATERIALS` **末尾追加**一个条目(顺序决定 GLB 材质索引)
2.`kind``textured`,贴图放 `assets/textures/``materials.py:14`
`TEXTURE_ROOT`
3. `generate_scene.py` 里用 `material_from_spec(catalog.MATERIALS["<key>"])`
4. **若这个材质在 Cesium 里需要调色,把 `cesium` 子契约写在同一个
`MATERIALS` 条目里**。`materials.from_spec()` 会把它序列化到
`material["cesium_export"]`,导出器优先读这个属性
### 颜色是线性 RGB
`catalog` 里的 `color` 是 Blender 的线性值,**不是** sRGB hex也**不**从
`scripts/lib/scene-layers.js` 换算。两套配色独立调过,理由见
[图层表](../pipeline/layer-registry.md#为什么颜色刻意不同步)。
---
## 实例化:树
`tree.py:1-8` 的模式——**import 一次 → bake 朝向 → 每棵树只 link 一个轻对象复用
同一个 datablock**
> Nothing is duplicated per tree, so the .blend and the exported GLB carry each
> mesh and each texture exactly once no matter how many trees are planted.
`TreeVariant``(meshes, height, base_z)` 三元组(`tree.py:62-65`
- `height` — 变体自身的高度,目标高度除以它得到缩放系数
- `base_z` — 变体自身的地面线,**取负乘以缩放**就能把树干落到 `z=0`
不管源文件把原点放在哪(`tree.py:301-303`
`assemble()` 返回种植数量,**0 表示模型缺失或 style 未知**,调用方据此回退到程序化
树(`tree.py:277-279`)。
### 材质在本地重建,不沿用源文件
`tree.py:12-16`:两个 vendored 模型的材质都不能直接用。apple 的贴图 57% 是透明的
(那是叶片卡),没有 alpha-clip 设置的话整个树冠会渲染成一块。
**vendored 资产的材质一律重建**,不要 `append` 源文件的材质。
### 已删除的第三种 style 有记录
`tree.py:18-25` 记着 `polyhaven` style 被删的原因LOD1 对象不是整棵树,是给几何节点
散布用的树枝和叶簇,直接种出来是一地树枝,连同 78MB 资产一起删了)。
**这类"试过、不行、为什么"的记录要保留。** 删掉它,下一个人会重新引入同一个资产。
---
## 确定性:用无理数周期代替 RNG
这是全仓最容易被无意破坏的约定。`tree.py:290-292`
```python
# Irrational periods stand in for an RNG: no repeat over any realistic
# tree count, and a pure function of the index, so rebuilding an area
# plants the identical forest.
scale_wobble = 1.0 + SCALE_JITTER * math.sin(index * 2.399963)
yaw = ((index * GOLDEN_TURN) % 1.0) * math.tau
tilt_x = TILT_JITTER * math.sin(index * 1.114517)
tilt_y = TILT_JITTER * math.cos(index * 0.927295)
```
黄金角 `GOLDEN_TURN``tree.py:47-49`)让相邻的树朝向永不重复也永不成规律——
一排树看起来像种的,不像盖章盖的。
**规则**:需要"随机"外观时,用 `index` 的纯函数(无理数周期 / 黄金角),
或者收一个显式 `seed``geom.sample_polygon_interior` 就是这么做的)。
**绝不要**用无种子的 `random` 或任何时间相关的量——
重建同一片区域必须得到逐字节相同的结构,否则
[parity 校验](../guides/artifact-parity-guide.md)永久性地红。
---
## Cesium 导出:一层独立的调色
`export_cesium.py:1-9`:创作用的场景刻意使用了一些 Blender 专有节点
(草地 tint、程序化树冠变化而 glTF 的材质词汇小得多。所以导出器
**新建临时的、仅供导出的 PBR 材质**,展 UV把引用到的图片全部内嵌进 GLB。
导出材质带 `EXPORT_PREFIX = "Cesium "` 前缀(`export_cesium.py:30`
这样第二遍扫到实例化网格的共享材质槽时能认出自己的产物、跳过不重复处理。
模型保持在**局部 ENU 坐标系**X 东、Y 北、Z 上),靠伴生 JSON 配合
`Cesium.Transforms.eastNorthUpToFixedFrame` 摆放。
### Cesium contract
新生成场景的 Cesium 导出调色写在 `catalog.MATERIALS[*]["cesium"]`,由
`materials.from_spec()` 保存成材质自定义属性 `material["cesium_export"]`。导出器打开
`.blend` 后优先读这个属性,不 import `catalog`
`cesium` 子契约字段:
| 字段 | 作用 |
|---|---|
| `tint` | diffuse 贴图导出前往目标颜色混合 |
| `metallic` | 覆盖导出 PBR metallic |
| `base_color` | 直接替换导出基色,并禁用 diffuse/normal 图 |
| `emission` | 自发光兜底 |
`export_cesium.py` 仍保留 `EXPORT_TINTS``EXPORT_METALLIC_OVERRIDES`
`EXPORT_BASE_COLOR_OVERRIDES``EXPORT_EMISSION_OVERRIDES` 四张按材质名字符串匹配的表,
但它们只是旧 `.blend` 兼容回退。新材质不要只写旧表。
### 为什么新资产总是"发黑"
`export_cesium.py:38-54` 记录了这个反复出现的问题:
> Cesium 的默认光照偏白,**场景里每一个材质都被手工提亮过**——草往亮绿混 72%、
> 带肋墙面往白混 86%、建筑自发光 0.18。一个没调过的新资产是唯一如实渲染的东西,
> 放在旁边就显得发黑。
所以**加新资产时,"它在 Blender 里看着对"不代表在 Cesium 里对**,必须在
`catalog.MATERIALS[*]["cesium"]` 里给它配一份调校。
抠图植被走的是另一套(`FOLIAGE_ALBEDO_GAIN = 2.1` + `FOLIAGE_SATURATION = 1.75`
`:55, 66`),用**增益**而不是 tint——因为那是一张同时装着叶片、树皮、果实的图集
往绿色混会把树干也染绿。增益保留色相关系,只把整体曝光抬到和邻居一致。
`FOLIAGE_EMISSION = 0.25` 的职责只是给背光面兜底,**不是主要提亮手段**
`:32-36`)。想让植被更亮就调增益,别调自发光。
---
## 反模式
| 反模式 | 后果 |
|---|---|
| 每个形体建一个对象而不用 `MeshBatch` | 对象数与 glTF 节点数爆炸 |
| 改 `Tree_` / `Scrub_` 命名前缀 | 平滑着色静默失效 |
| 用无种子 `random` 或时间量做抖动 | parity 校验永久红 |
| 每棵树复制一份 mesh/贴图 | .blend 与 GLB 体积按棵数线性膨胀 |
| 直接 append vendored 资产的材质 | alpha-clip 缺失,树冠渲染成一块 |
| 删掉"试过不行"的注释 | 下一个人重新踩同一个坑 |
| 从 `scene-layers.js` 的 hex 换算 Blender 颜色 | 抹掉独立调过的配色 |
| 加新资产不配 Cesium 调色 | Cesium 里显得发黑 |
| 靠调 `FOLIAGE_EMISSION` 提亮植被 | 用错了旋钮,该调 albedo gain |
| 在 `MATERIALS` 中间插入条目 | GLB 材质索引整体平移 |
---
## 相关
- [模块结构](./module-structure.md):往哪儿放新代码
- [测试](./testing.md):纯几何部分怎么测
- [图层表](../pipeline/layer-registry.md):道路九层的材质从哪来
- [产物一致性指南](../guides/artifact-parity-guide.md):改完怎么验证产物没变