three.js SkyscraperGenerator:从一个材质实例化出整座新古典主义陶土摩天楼
SkyscraperGenerator 是 three.js 仓库中一个程序化建筑生成器:它用一组少量参数(种子、总高度、立面比例等)生成具有三段式结构(拱廊基座、重复窗格塔身、装饰性顶冠)的“Beaux-Arts / Neo-Gothic”风格陶土摩天楼。整栋楼最终被烘焙(bake)成一个带逐顶点 partId 属性的非索引 BufferGeometry,只需一个材质即可完成全部着色。读完本文,你将了解它的参数体系、生成管线(footprint → faces → tiers → floors/bays → bake)、PartId 分区着色机制,以及配套的 TSL 材质 createSkyscraperMaterial 和官方示例的运行方式。
核心 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 |
塔身窗口出现窗式空调机的概率 |
此外还有一批由种子驱动的参数(footprint、tierFractions、pierWidth、pierDepth、windowReveal、stringCourseHeight、archBayWidthRatio、archRise),由 randomStyle() 在 build() 时从种子生成,取值范围如:
footprint.width:26 + random() * 18,footprint.depth:20 + random() * 14;tierFractions.base:0.10 ~ 0.17(基座占总高比例),tierFractions.crown:0.08 ~ 0.16;pierWidth:0.4 ~ 0.8,pierDepth:0.3 ~ 0.6,windowReveal(窗洞深度):0.12 ~ 0.22,stringCourseHeight:0.5 ~ 1.0;archBayWidthRatio:取整于1.5 ~ 3(基座拱宽是 bay 宽度的倍数),archRise:0.4 ~ 0.9(尖拱尖度)。
参数合并的优先级在 build() 中有明确注释:固定默认值 < 种子驱动风格 < 调用方参数,因此任何你显式传入的参数都会覆盖其种子值。
生成管线:footprint → faces → tiers → floors/bays
build() 的几何组织逻辑(build() 主体)可以概括为四层递进:
- footprint(体量):buildFootprint() 在 XZ 平面生成一个中心原点的矩形、并在指定角上以 45° 切掉一个角,返回有序的
Vector2顶点列表。顶冠退台后还有一个内缩的crownFootprint(按setbackDepth × bayWidth向两边内缩,切角同步缩减)。 - faces(立面框架):buildFaces() 为 footprint 每条边构造一个
FaceFrame——正交基(u沿边、v竖直、n朝外)加原点与边长,于是所有立面布局都在扁平的 (u, v) 空间进行,再用一个矩阵烘焙到世界空间。这正是仓库文档页 FaceFrame 对应的内部类:point( u, v, w )把局部坐标映射到世界,matrix( u, v, w )直接产出一个放置“标准局部件(x 横、y 上、z 朝外)”的Matrix4,bays( bayWidth )计算一条边能放下多少个开间、余量如何均分为两端边距。由于每面共享同一套预制件,45° 斜切面也能得到与其他立面一致的窗户和壁柱。 - tiers(三段):塔身被分为基座(高拱廊 + 腰线石封顶)、重复窗格塔身、装饰性顶冠。楼层数按
max( 3, round( totalHeight / floorHeight ) )计算,再按tierFractions拆成整层数,所以totalHeight会被圆整到整层的整数倍(相关代码)。塔身与顶冠是“同一立面、不同 span 和壁柱高度”的两段循环,唯一区别是顶冠的壁柱在厚重的顶冠檐口(stringCourseHeight × 1.6)下停止,且顶冠不放空调机。 - 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 个属性:position、normal、uv,以及三个语义属性:
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(TSLcolor节点)为基色的程序化陶土砖——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/depth、floorHeight(3–6)、bayWidth(1.8–4.5)、chamfer(0–10)、setback(0–4)、timeOfDay(6–18)。
在仓库的另一个示例生成器 CityGenerator 中,SkyscraperGenerator 被用作“每块地块一栋楼”的单元:每个地块独立取种子,totalHeight 用 38 + tall * tall * 114 让塔楼以少量高楼、大量中层的分布出现,切角只留给街区四角的角落地块并朝向各自的外向街角,全部建筑共享同一个 materials.building 材质(CityGenerator.build())。
注意事项与适用边界
- 依赖前提:该模块从
three/webgpu与three/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 负责。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
