9.5 KiB
资产生成
适用:改动 Blender 侧的几何构建、材质、实例化,或往场景里加新资产。 贯穿全篇的约束是两条:压低对象数(GLB 要能在浏览器里跑)和 构建必须确定性(parity 校验的前提)。
MeshBatch:几何构建的主力
mesh.py:1-9 说明了它为什么存在:场景的绝大部分是平面多边形和拉伸棱柱,
把它们批进一个 mesh datablock 能同时压低 Blender 对象数和导出 glTF 的节点数。
用法固定为「累积 → 一次 finish()」:
batch = MeshBatch("Lake Surface", water_c, water_mat)
batch.add_polygon(ring, 0.10)
batch.finish() # water.py:12-14
三条内建行为
- 自动去掉重复的闭合点(
mesh.py:41-42, 55-56)。传闭合环或开放环都行, 与geom.py的宽容度一致 - 退化输入静默返回:
len(ring) < 3直接 return,不抛 - 空批次
finish()返回None(mesh.py:66-67),不产生空对象
名字前缀是承重的
# 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 索引的地方。
便捷包装
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, 默认值)——加新的可选字段不要改已有条目。
加一种材质
catalog.MATERIALS末尾追加一个条目(顺序决定 GLB 材质索引)- 若
kind是textured,贴图放assets/textures/(materials.py:14的TEXTURE_ROOT) generate_scene.py里用material_from_spec(catalog.MATERIALS["<key>"])取- 若这个材质在 Cesium 里需要调色,把
cesium子契约写在同一个MATERIALS条目里。materials.from_spec()会把它序列化到material["cesium_export"],导出器优先读这个属性
颜色是线性 RGB
catalog 里的 color 是 Blender 的线性值,不是 sRGB hex,也不从
scripts/lib/scene-layers.js 换算。两套配色独立调过,理由见
图层表。
实例化:树
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:
# 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 校验永久性地红。
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 材质索引整体平移 |