关于 threejs-towers
threejs-towers 是使用 Three.js 程序化构建建筑模型与施工动画的指南,适合制作建筑场景的开发者。它以可复用几何构建器和参数化屋顶、楼层样式组织建模过程。
主要功能
- 01通过共享数组构建几何体
- 02以参数定义多种建筑样式
- 03用函数生成曲面屋顶
- 04用裁剪平面表现施工过程
- 05按材质合并以减少绘制调用
适用场景
- 01程序化制作塔楼与屋顶模型
- 02构建多风格建筑场景
- 03制作带脚手架和阶段说明的施工动画
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mengto/ skills --skill threejs-towers安装后在 Claude Code 中输入/threejs-towers确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamldemo/index.htmlPROMPT.md详细介绍
Three.js 塔楼
程序化生成最能立竿见影地发挥作用的领域,就是建筑:建筑由重复的参数化部件组成;一旦掌握了这套词汇,制作第二种风格只需换一组参数,而不必重新建模。
将它放进 threejs-landscape,再用 threejs-weather 为它增添风霜。
先构建构件,再建建筑
编写十个小型构建器,让它们都向共享的顶点/法线/UV/索引数组追加数据,之后就不要再手写原始几何体:
face(P, uv) quad with a computed normal
box(cx,cy,cz, sx,sy,sz, ry, uvScale, rz)
prism(y0,y1, R0,R1, uv, capTop, capBot) square on plan
prismN(y0,y1, R0,R1, n, uv, capTop, capBot) any n-gon
sweepPlan(plan, y0,y1, steps, profile) an outline scaled up a curve
lathe(cx,cy,cz, y0,y1, profile, steps, seg) surface of revolution
ring / arc annulus on a face plane
fan(points, z, ry, cx, cy) filled polygon on a face
tube(points, r, sides) swept tube
这套构建器涵盖了日式城堡、中华宝塔、奥斯曼穹顶、高棉 prasat 和越南 tháp。每种风格都由一个组装函数和一张层级表构成。
按材质合并,而不是按部件合并。六栋建筑只需 11–18 次绘制调用,这就是 120fps 与 30fps 的差别。
最常遇到的 bug 是绕序。 如果棱柱的侧面朝内绕序,就会被背面剔除,你看到的只会是远处墙面的内侧。先把一个棱柱的绕序弄对,再将它的顶点顺序复制到所有地方。表面消失时,先怀疑绕序,再考虑其他原因。
屋顶形状取决于檐口上的位置
东亚屋顶的整体特色——靠近屋脊处平缓,随后变陡,再在角上向上翘起——来自一个将 (面板、横向位置、纵向位置) 映射为点的函数:
function roofPoint(o, p, u, t) {
const tc = Math.min(1, t);
const g = 1 - Math.pow(1 - tc, o.pow); // the sag of the slope
const corner = Math.pow(Math.abs(u), 2.0); // 0 mid-eave, 1 at hips
const flare = 1 + o.flare * corner * Math.pow(tc, 3.2);
const y = o.yT - (o.yT - o.yE) * g
+ o.lift * corner * Math.pow(tc, 2.6); // corners lift
return [ /* lerp inner edge → outer edge × flare */ ];
}
六个数值就能生成这一系列中的所有屋顶:lift、tip、flare、pow、trunc、ridge。trunc 很容易被忽略——它能让下层屋顶在上方墙体后方自然收住,而不是穿出墙面。
Math.pow(negative, 2.35) 会得到 NaN。 瓦片超出檐口时,t 会大于 1,导致整个屋顶悄无声息地消失。将 tc = Math.min(1, t) 限制在范围内,并将超出部分作为单独的线性项处理。
非方形平面
prismN 可处理八边形和十六边形鼓座。对于带有凹角的形状——高棉 prasat 是一个沿每条轴向外凸出,并在转角前两次回缩的正方形——先构建一次轮廓,再沿高度拉伸:
const oct = [[1+P,0],[1+P,0.30],[1.0,0.30],[1.0,0.45],[0.90,0.45], ... ];
const half = oct.concat(oct.slice(0,-1).reverse().map(q => [q[1], q[0]]));
// mirror across the diagonal, then rotate four times
之后每层都沿用相同轮廓,只是缩小比例,轻松得到连贯的剪影。
铺好檐台。 当某层向内收进上方楼层时,两个轮廓之间会出现空隙。用平坦的环形面填满平面上的空隙,否则塔楼看起来就像一摞悬空的搁板,层间透着天光。
根据所在表面调整细节尺寸
最常见的建模错误不在几何体,而在比例。按面部尺寸设计的门,结果却宽了三分之一,就会溢出到侧面,显得杂乱。先测量立面,再按比例确定门、柱子和山花的尺寸;同时确认山花结束的位置在檐口下方,而不是穿过檐口。
建筑的生成动画只需一个裁剪平面
所有几何体从第一帧起就已存在。一个朝下的平面向上移动,所有结构材质都按此进行裁剪:
renderer.localClippingEnabled = true;
const CLIP = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0);
['stone','plaster','tile','timber', ...].forEach(k => {
MAT[k].clippingPlanes = [CLIP];
MAT[k].clipShadows = true; // or the shadow builds early
});
CLIP.constant = heightAt(t);
裁剪后的外壳是空心的,因此要在平面高度添加一个封口网格,并将其缩放到建筑的占地范围。这样,透视剖面就会呈现为实心的石砌层。
封口必须与被封住的平面形状一致。 把方形封口放进八边形塔楼,会留下四个朝天敞开的楔形缺口。让每种风格声明对应剖面的平面形状——殿堂用四边形,鼓座用八边形,穹顶用十六边形——并在平面从一种形状过渡到另一种形状时,重新构建封口几何体:
caps: [[0, 4.74, 2.56], [4.74, 5.42, 2.02, 8], [5.42, 6.46, 1.86, 16]]
// y0 y1 radius sides
对于内部空间,使用 DoubleSide 深色材质为内侧体积添加封口,从任何角度看都像实心石材,而且几乎不增加开销。
脚手架是平面的例外
它不受剪辑平面的影响,因此始终比已完成的部分领先一步。正是这种关系,让动画看起来像是在施工,而不是在擦除画面。
BoxGeometry 会为每个面分配 0..1 的 UV,无论尺寸大小。 因此,一根三米长的立柱和一根十五厘米长的斜撑会有相同的纹理颗粒,看起来都像塑料。上传前,按世界单位重写 UV:
const dims = [[sz,sy],[sz,sy],[sx,sz],[sx,sz],[sx,sy],[sx,sy]];
for (let f = 0; f < 6; f++) {
const du = dims[f][0], dv = dims[f][1], swap = dv > du;
for (let i = 0; i < 4; i++) { /* scale by real size, offset randomly */ }
}
让每一层以短促的缓动缩放进入——竖向构件沿 Y 轴缩放,横向构件沿 X 轴缩放——并在下一阶段落下前让它消失。
阶段承载时间线
为每种样式提供一个由 [本地语言说明文字, ENGLISH, 目标高度] 组成的列表,并让平面在目标之间缓动。说明文字点明正在发生什么,这在很大程度上决定了施工过程是否清晰易懂:
stages: [['準備','READY',0.00], ['石垣普請','STONEWORK',3.36],
['柱梁組立','TIMBER',6.24], ['白壁塗籠','PLASTER',8.80], ...]
每次阶段切换时播放一次性音效,最后一个阶段播放钟声。让整个搭建过程保持简短——四到五秒——并在画面中较安静的位置放一个较大的百分比数字。速度够快的话,人们会反复重新搭建。
发光材质漏光
如果夜间用发光材质点亮窗户,光芒会从所有未铺设面的开口处漏出:屋檐下方、截断的屋顶处、中空楼层的外侧。为截断的屋顶铺设面,并将封闭体积拆分到非发光材质上。其表现是建筑沿着轮廓发光,像一盏灯笼。
完成前的检查事项
- 在地面高度绕行一整圈。缺失的墙面是绕序反了;从面上飞出的长条是面挂盒体的旋转符号错误。
- 缓慢拖动时间线,逐一经过每个阶段。无论高度如何,顶盖都应始终处于墙内,绝不能从角落凸出。
- 从正上方查看一次屋顶。只有从这个角度才能看到超出边缘的瓦片和 NaN 面板。
- 连续切换样式十次。如果内存不断增加,说明你没有释放上一个组的几何体。
- 统计每种样式的绘制调用次数。如果某种样式是其他样式的三倍,说明有材质没有共享。