Initialize Trellis project guidelines
This commit is contained in:
218
.trellis/spec/blender/asset-generation.md
Normal file
218
.trellis/spec/blender/asset-generation.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# 资产生成
|
||||
|
||||
> 适用:改动 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 里需要调色,去 `export_cesium.py` 的四张表加**——
|
||||
见下文,那边按材质名字符串匹配
|
||||
|
||||
### 颜色是线性 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` 摆放。
|
||||
|
||||
### 四张覆盖表(按材质名字符串)
|
||||
|
||||
| 表 | 位置 | 作用 |
|
||||
|---|---|---|
|
||||
| `EXPORT_TINTS` | `:68` | 往某个颜色混合 |
|
||||
| `EXPORT_METALLIC_OVERRIDES` | `:80` | 平铺的金属度覆盖 |
|
||||
| `EXPORT_BASE_COLOR_OVERRIDES` | `:86` | 直接替换基色 |
|
||||
| `EXPORT_EMISSION_OVERRIDES` | `:95` | 自发光兜底 |
|
||||
|
||||
⚠️ `export_cesium.py` **不 import `catalog`**,靠材质名字符串匹配。
|
||||
`catalog.CESIUM_EXPORT` 是**死代码**。改材质名前先读
|
||||
[模块结构](./module-structure.md#已知现状两个入口靠材质名字符串对接)。
|
||||
|
||||
### 为什么新资产总是"发黑"
|
||||
|
||||
`export_cesium.py:38-54` 记录了这个反复出现的问题:
|
||||
|
||||
> Cesium 的默认光照偏白,**场景里每一个材质都被手工提亮过**——草往亮绿混 72%、
|
||||
> 带肋墙面往白混 86%、建筑自发光 0.18。一个没调过的新资产是唯一如实渲染的东西,
|
||||
> 放在旁边就显得发黑。
|
||||
|
||||
所以**加新资产时,"它在 Blender 里看着对"不代表在 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):改完怎么验证产物没变
|
||||
Reference in New Issue
Block a user