首页
/ three.js SkyscraperGenerator:从一个材质实例化出整座新古典主义陶土摩天楼

three.js SkyscraperGenerator:从一个材质实例化出整座新古典主义陶土摩天楼

2026-09-07 16:55:55作者:冯梦姬Eddie

SkyscraperGenerator 是 three.js 仓库中一个程序化建筑生成器:它用一组少量参数(种子、总高度、立面比例等)生成具有三段式结构(拱廊基座、重复窗格塔身、装饰性顶冠)的“Beaux-Arts / Neo-Gothic”风格陶土摩天楼。整栋楼最终被烘焙(bake)成一个带逐顶点 partId 属性的非索引 BufferGeometry,只需一个材质即可完成全部着色。读完本文,你将了解它的参数体系、生成管线(footprint → faces → tiers → floors/bays → bake)、PartId 分区着色机制,以及配套的 TSL 材质 createSkyscraperMaterial 和官方示例的运行方式。

three.js webgpu_generator_building 示例运行截图:程序化生成的 Neo-Gothic 陶土摩天楼

核心 API 与快速上手

SkyscraperGenerator 位于 examples/jsm/generators/city/SkyscraperGenerator.js,是一个与材质无关(material agnostic)的生成器——它只生产几何体,外观由你传入的单个材质(例如基于 partId 分区的 TSL 节点材质)决定。官方文档给出的最小用法如下:

const generator = new SkyscraperGenerator( { seed: 35, totalHeight: 140 }, material );
scene.add( generator.build() ); // a single Mesh

完整的类接口(对应 SkyscraperGenerator 类定义):

  • new SkyscraperGenerator( parameters = {}, material = null ):构造器。parameters 是调用方覆盖项,其余默认值与种子驱动值在 build() 时补齐;material 是作用于整栋楼的单一材质,若不传,build() 会回退到一个素色的 MeshStandardMaterial( { color: 0xddccaa, roughness: 0.9 } )(见 build() 末尾)。
  • .setParameters( parameters ):用 Object.assign 合并新参数并返回 this,支持链式调用。
  • .build():执行完整生成流程并返回一个 Mesh(名称为 'Skyscraper');内部会先 dispose() 旧网格再重建。
  • .rebuild():等价于 build()
  • .dispose():释放上一次构建的网格几何体(若尚未构建则直接返回)。

默认参数定义在静态属性 SkyscraperGenerator.defaults

参数 默认值 含义
seed 35 随机种子,驱动种子化“风格”(确定性的 mulberry32 PRNG,同一种子永远生成同一栋楼)
totalHeight 140 期望总高(米);实际总高会按整层数圆整
floorHeight 4 层高;构建时会被吸附到砖模块(BRICK.height × 2 = 0.6 m)的整数倍
bayWidth 2.6 开间(bay)宽度;吸附到砖长(0.6 m)的整数倍
stringCourseEvery 6 塔身每隔多少层环绕一道水平腰线石(string course),0 关闭
chamferWidth 4 切角宽度:footprint 有一个 45° 斜切角
chamferCornerX / chamferCornerZ 1 / 1 斜切角指向哪个角(各取 ±1),可把切角对准街角
setbackDepth 1.5 顶冠退台的“深度以 bayWidth 为单位”的倍数
acChance 0.12 塔身窗口出现窗式空调机的概率

此外还有一批由种子驱动的参数(footprinttierFractionspierWidthpierDepthwindowRevealstringCourseHeightarchBayWidthRatioarchRise),由 randomStyle()build() 时从种子生成,取值范围如:

  • footprint.width26 + random() * 18footprint.depth20 + random() * 14
  • tierFractions.base0.10 ~ 0.17(基座占总高比例),tierFractions.crown0.08 ~ 0.16
  • pierWidth0.4 ~ 0.8pierDepth0.3 ~ 0.6windowReveal(窗洞深度):0.12 ~ 0.22stringCourseHeight0.5 ~ 1.0
  • archBayWidthRatio:取整于 1.5 ~ 3(基座拱宽是 bay 宽度的倍数),archRise0.4 ~ 0.9(尖拱尖度)。

参数合并的优先级在 build() 中有明确注释固定默认值 < 种子驱动风格 < 调用方参数,因此任何你显式传入的参数都会覆盖其种子值。

生成管线:footprint → faces → tiers → floors/bays

build() 的几何组织逻辑(build() 主体)可以概括为四层递进:

  1. footprint(体量)buildFootprint() 在 XZ 平面生成一个中心原点的矩形、并在指定角上以 45° 切掉一个角,返回有序的 Vector2 顶点列表。顶冠退台后还有一个内缩的 crownFootprint(按 setbackDepth × bayWidth 向两边内缩,切角同步缩减)。
  2. faces(立面框架)buildFaces() 为 footprint 每条边构造一个 FaceFrame——正交基(u 沿边、v 竖直、n 朝外)加原点与边长,于是所有立面布局都在扁平的 (u, v) 空间进行,再用一个矩阵烘焙到世界空间。这正是仓库文档页 FaceFrame 对应的内部类:point( u, v, w ) 把局部坐标映射到世界,matrix( u, v, w ) 直接产出一个放置“标准局部件(x 横、y 上、z 朝外)”的 Matrix4bays( bayWidth ) 计算一条边能放下多少个开间、余量如何均分为两端边距。由于每面共享同一套预制件,45° 斜切面也能得到与其他立面一致的窗户和壁柱。
  3. tiers(三段):塔身被分为基座(高拱廊 + 腰线石封顶)、重复窗格塔身、装饰性顶冠。楼层数按 max( 3, round( totalHeight / floorHeight ) ) 计算,再按 tierFractions 拆成整层数,所以 totalHeight 会被圆整到整层的整数倍(相关代码)。塔身与顶冠是“同一立面、不同 span 和壁柱高度”的两段循环,唯一区别是顶冠的壁柱在厚重的顶冠檐口(stringCourseHeight × 1.6)下停止,且顶冠不放空调机。
  4. floors / bays(构件落位):每个立面帧上按开间布置窗户(窗框 + 玻璃 + 空调机)、横带(spandrel bands)、壁柱;基座放哥特式拱廊(addArcade(),用 ExtrudeGeometry 挤出带尖拱洞口的厚墙,洞口读作深凹);顶冠放檐口、女儿墙与尖塔(buildFinialGeometry() 用 8 段旋转成型的锥状尖顶)。退台平台与屋顶是沿 footprint 轮廓内缩挤出的薄板(slab()),切角不会被矩形板“撑破”。

一个容易忽视但重要的细节:楼体尺寸会被吸附到砖模块上。模块常量 BRICK = { height: 0.3, length: 0.6 }L48)同时驱动几何(每层整数道砖、每开间整数块砖)与材质里的程序化砖纹(错缝砌筑),两者因此严格对齐。窗洞占层高比例为 WINDOW_HEIGHT_RATIO = 0.62,窗框平直边带宽 WINDOW_BORDER = 0.1,窗框与玻璃共享这些常量以保证玻璃永远嵌在框内。

PartId 烘焙:一个网格、一次绘制、一个材质

所有构件按“基几何体 + 一组 Matrix4 实例矩阵 + partId”分组累积,最后由 bakeGroups() 一次性烘焙成一个非索引 BufferGeometry。这一步刻意绕开了 mergeGeometries 的逐实例分配:直接把顶点变换写入预分配的 typed array,并顺手在变换时收集 AABB 算出包围球(省掉 computeBoundingSphere 的第二遍遍历)。最终几何体携带 6 个属性:positionnormaluv,以及三个语义属性:

  • partId:逐顶点分区码,供材质分支着色;
  • roomCenter / roomSize:仅玻璃顶点写入的“室内映射房间”中心与尺寸,其余顶点为零——材质据此对每扇玻璃片做程序化室内 raymarch(见下文),且无需全局 uniform,每栋楼读各自的房间尺寸。

分区码定义在 PartId

码值 分区 说明
WALL: 0 墙体 横带、檐口/女儿墙等盒体、基座拱廊、退台/屋顶板、玻璃背后的封体积体墙
PIER: 1 壁柱 带附柱(pilaster)的连续垂直肋,同一层级的等高壁柱共享一个几何体
FRAME: 2 窗框 矩形框面 + 四壁窗洞侧墙 + 一道横向玻璃压条(transom),合并为单一实例件
ORNAMENT: 3 装饰 顶冠的旋转成型尖塔
GLASS: 4 玻璃 嵌在窗框内的单片平面,带 roomCenter/roomSize
AC: 5 空调机 随机落在部分塔身窗台上的小型空调外机

由于单网格无法让渲染器排序,烘焙顺序即绘制顺序(groups 组装处):立面由前到后,玻璃背后的封体墙最后烘焙,其被遮挡的片段永远不会参与着色,从而控制 overdraw。对纯旋转+平移的组标记 rigid: true 后,法线矩阵直接取矩阵的旋转部分,跳过逐实例 3×3 求逆。

配套 TSL 材质:createSkyscraperMaterial

生成器本身不产出材质,但同一模块导出了与几何体严格配套的 createSkyscraperMaterial( buildingBase ),返回一个 MeshStandardNodeMaterial,读取烘焙好的 partId(FLAT 插值,避免分区码被插值后 equal() 失配)并分支复现每个分区的外观:

  • 石材质区(WALL / PIER / ORNAMENT):以 buildingBase(TSL color 节点)为基色的程序化陶土砖——running bond 错缝、抗锯齿灰缝(“pristine grid”技巧:画线宽度不小于像素足迹且透明度补偿保持能量恒定)、逐砖暖/冷色偏移,以及世界空间风化(低频色调漂移、细粒陶土斑驳、低处积聚的烟灰竖纹)。PIER/ORNAMENT 相对基色提亮,FRAME 用暗一档的平滑石材。砖面还有基于屏幕导数的凸 relief(bumpNormal()dFdx/dFdy 走 Mikkelsen 表面梯度法,因为内建 bumpMap 按 UV 偏移采样,对世界空间高度场会退化为零梯度)。
  • GLASS:玻璃片不是空的洞,而是片元着色器里 raymarch 出来的“房间”(interior)——把视线射入每扇窗烘焙好的房间盒体,程序化着墙壁、地板、天花、门、画、桌椅沙发和窗帘,约 20% 的房间亮灯(暖光为主、约 12% 为冷光),玻璃再叠一层污渍薄膜与窗台下沿积灰,emissiveNode 让亮灯房间发光。
  • AC:白色塑料外壳 + 内凹格栅百叶,随高度与个体噪声产生脏污条纹。
  • 全部分区 metalness = 0(均为电介质),玻璃保持低粗糙度以便天空反射叠加在室内之上。

模块还导出 NYC 砌体配色表 buildingPalette(石灰岩为主、陶土点缀的一组 hex 色)和按种子取色的 pickBuildingColor( seed ),供单塔示例与整城生成器共享。

官方示例:webgpu_generator_building

仓库内置示例 examples/webgpu_generator_building.html(即上文截图来源)演示了完整用法。关键点:

  • 示例在 import map 中使用 three/webgpu 构建(../build/three.webgpu.js),因为 createSkyscraperMaterial 依赖 MeshStandardNodeMaterial 与 TSL 节点;几何生成本身只依赖核心 three。
  • 楼体基色是一个 TSL uniform,种子变化时通过 pickBuildingColor( seed ) 换色而无需重编译材质:
material = createSkyscraperMaterial( baseColor ); // baseColor = uniform( new THREE.Color( 0xc6c0b2 ) )

generator = new SkyscraperGenerator( {
    seed: parameters.seed,
    totalHeight: parameters.height,
    footprint: { width: parameters.width, depth: parameters.depth },
    floorHeight: parameters.floorHeight,
    bayWidth: parameters.bayWidth,
    chamferWidth: parameters.chamfer,
    setbackDepth: parameters.setback
}, material );
building = generator.build();
building.castShadow = building.receiveShadow = true;
scene.add( building );
  • 重建前先移除旧网格并 building.geometry.dispose()(本例直接操作网格;若走生成器生命周期则用 generator.dispose())。
  • 示例用 SkyMesh 物理天空同时驱动背景与 IBL 环境贴图,太阳方向映射“一天时刻”(6 点日出、12 点正午、18 点日落),并据此拟合方向光阴影相机的视锥,避免低太阳时长影被裁掉。
  • 参数面板暴露 seed(0–100)、height(60–200)、width/depthfloorHeight(3–6)、bayWidth(1.8–4.5)、chamfer(0–10)、setback(0–4)、timeOfDay(6–18)。

在仓库的另一个示例生成器 CityGenerator 中,SkyscraperGenerator 被用作“每块地块一栋楼”的单元:每个地块独立取种子,totalHeight38 + tall * tall * 114 让塔楼以少量高楼、大量中层的分布出现,切角只留给街区四角的角落地块并朝向各自的外向街角,全部建筑共享同一个 materials.building 材质(CityGenerator.build())。

注意事项与适用边界

  • 依赖前提:该模块从 three/webgputhree/tsl 导入材质相关符号,完整的“几何 + TSL 材质”方案需要 WebGPU 构建(见示例 import map)。若只需几何,build() 产物是标准 Mesh + BufferGeometry,可配合任意常规材质(如默认的素色 MeshStandardMaterial)使用。
  • 生命周期build()/rebuild() 会自动释放上一次构建的几何体;若你自行把网格移出场景或跨生成器复用,请按官方示例显式 geometry.dispose() 防止显存累积。
  • 确定性:所有随机性(风格、空调机、房间亮灯、室内布置)都由 seed 派生(mulberry32 + 逐楼层稳定 hash),同一 seed 跨运行生成完全相同的楼,便于调试与分享“某栋楼”的参数。
  • 文档入口:本生成器的 API 文档页为 docs/pages/SkyscraperGenerator.html.md,内部类 FaceFrame 有独立页面 docs/pages/FaceFrame.html.md;整城装配逻辑参见 CityGenerator 文档CityGenerator 源码,人行道板由同目录的 SidewalkGenerator 负责。
登录后查看全文
热门项目推荐
相关项目推荐