多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

three.js SidewalkGenerator:程序化生成带花岗岩路缘的城市人行道

three.js SidewalkGenerator:程序化生成带花岗岩路缘的城市人行道 three.js SidewalkGenerator程序化生成带花岗岩路缘的城市人行道【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js本文介绍 three.js 仓库中 SidewalkGenerator 的设计与用法它如何为城市街区的每个地块生成圆角混凝土人行道板与环绕其外的花岗岩路缘石kerbstone如何通过InstancedMesh将整条街区实例化以及如何用 TSLThree Shading Language程序化材质实现混凝土铺砖、伸缩缝刻线与花岗岩骨料等细节。读完本文你可以直接在自己的场景中调用该生成器批量铺设人行道并理解其几何构建与程序化着色的底层实现。功能定位与整体架构SidewalkGenerator 的官方文档描述非常凝练它“为城市街区生成抬高的人行道每个地块一块圆角混凝土板外圈环绕一块独立的花岗岩路缘石路缘石高出步行面并向下延伸至路面。它针对一组放置矩阵进行实例化并自带程序化材质浇筑混凝土铺板、刻划伸缩缝、花岗岩路缘。返回一个包含两个实例化网格的THREE.Group——步行板与路缘。”从源码结构看该生成器有三个关键设计决策双网格结构步行板slab与路缘石curb是两个独立的InstancedMesh因为二者各自携带独立材质见 SidewalkGenerator.js#L47-L49 的注释“the walking slab and the curb are separate meshes so each carries its own material”。材质自持与SkyscraperGenerator建筑生成器由外部传入材质不同人行道生成器“拥有自己的材质”——板与路缘的几何和着色它们的 TSL 代码同处一个文件内SidewalkGenerator.js#L18-L19。懒创建 复用两个程序化材质在首次build()时创建一次之后跨多次重建复用SidewalkGenerator.js#L32-L33 注释“built once and reused across rebuilds”。快速上手官方文档给出的最小调用示例如下对应 SidewalkGenerator.html.md 的 Code Example 一节const sidewalk new SidewalkGenerator( { width: 90, depth: 60, height: 0.5 } ); scene.add( sidewalk.build( placements ) ); // placements: Matrix4[]其中placements是一个THREE.Matrix4数组每个矩阵描述一个街区块的世界变换build()会返回一个THREE.Group名为Sidewalk可直接scene.add()。构造函数与默认参数构造函数签名为new SidewalkGenerator( parameters {} )内部将传入参数与静态默认值SidewalkGenerator.defaults合并SidewalkGenerator.js#L28-L36。完整参数表如下源自 SidewalkGenerator.js#L83-L90 的源码注释参数默认值含义width90每块人行道板覆盖的地块宽度footprintdepth60地块进深height0.5步行面高出路面的高度radius5圆角半径让人行道在路口处圆弧转向而非硬 90° 直角curbWidth0.13花岗岩路缘石顶部宽度约 5 英寸curbLip0.01路缘石高出步行面的量近乎齐平所有单位均为场景世界单位数值上按“米制街区”设计90 × 60 的地块配 0.5 高度是典型的城市街区块比例。APIbuild 与 disposebuild( placements )build()的完整流程SidewalkGenerator.js#L38-L70先调用this.dispose()清理上一次构建的几何体——这使得“重新生成”只需替换placements再调build()若材质尚未创建则惰性创建createSidewalkMaterial()与createCurbMaterial()分别构建 slab 与 curb 几何体并各创建一个InstancedMesh实例数等于placements.length遍历placements把同一矩阵同时写入两个网格slab.setMatrixAt/curb.setMatrixAt保证板与路缘严格对齐调用computeBoundingSphere()更新包围球并设置receiveShadow true人行道只接收阴影不投射打包进名为Sidewalk的Group并返回。dispose()dispose()遍历当前组并对每个几何体调用geometry.dispose()然后把this.mesh置空SidewalkGenerator.js#L72-L79。注意它只释放几何体、不释放材质——这是有意为之的程序化 TSL 材质的构建成本高于重建网格跨重建保留材质可以显著降低重复build()的开销。几何构建圆角矩形 挤出人行道几何完全由 2D 形状挤出而来核心是四个内部函数。roundedRect居中圆角矩形roundedRect( width, depth, radius )SidewalkGenerator.js#L96-L115构造一个以原点为中心的THREE.Shapeconst r Math.min( radius, w, d ); // 圆角半径被夹在边长之内防止自相交 shape.moveTo( - w r, - d ); shape.lineTo( w - r, - d ); shape.quadraticCurveTo( w, - d, w, - d r ); // 右下角圆弧 // …四条边 四个二次贝塞尔角闭合回起点圆角半径会先与半边宽、半边深取最小值夹取保证几何体在极端参数下不会退化。extrudeUp把 Z 挤出转为 Y 高度ExtrudeGeometry默认沿 Z 挤出extrudeUp()通过一次rotateX( - Math.PI / 2 )把挤出方向转为 Y即“高度朝上”SidewalkGenerator.js#L118-L125const geometry new ExtrudeGeometry( shape, { depth: height, bevelEnabled: false, curveSegments: 6 } ); geometry.rotateX( - Math.PI / 2 );curveSegments: 6控制每个圆弧角被离散为 6 段是精度与顶点数之间的折中。slabGeometry内嵌于路缘内的步行板slabGeometry()SidewalkGenerator.js#L129-L134在整体轮廓向内缩进2 * curbWidth的基础上额外放大 0.06return extrudeUp( roundedRect( width - 2 * curbWidth 0.06, depth - 2 * curbWidth 0.06, innerRadius ), height );源码注释点明了这 0.06 的用意“inset to sit inside the curb and overlapping it slightly so the seam is buried”——板与路缘略微重叠让两者的接缝被埋在内部避免渲染出可见的 Z-fighting 缝隙。板底位于 y 0顶面位于height。curbGeometry带内孔的全高路缘curbGeometry()SidewalkGenerator.js#L138-L145则相反取全尺寸圆角矩形向shape.holes推入一个缩进2 * curbWidth的内孔圆角矩形然后挤出到height curbLipconst shape roundedRect( width, depth, radius ); shape.holes.push( roundedRect( width - 2 * curbWidth, depth - 2 * curbWidth, innerRadius ) ); return extrudeUp( shape, height curbLip );由此得到一圈完整的“环形墙”外缘沿地块轮廓、内缘贴着步行面顶部高出步行面curbLip默认 0.01近乎齐平底部一路落到路面 y 0。程序化材质混凝土板与花岗岩路缘两份材质都基于MeshStandardNodeMaterial TSL 节点表达式全部细节以世界坐标为键positionWorld并按相机距离做细节渐隐const p positionWorld; const detail smoothstep( 200, 18, p.distance( cameraPosition ) );detail在相机 200 单位外为 0、18 单位内为 1远处只渲染低频底色近处才解析骨料、刻缝等高频细节。这个门控既省算力也避免了远处高频纹样的摩尔纹。三个共享的 TSL 工具函数1. bumpNormal导数法程序化凹凸SidewalkGenerator.js#L153-L164内置bumpMap依赖 UV 偏移读取高度场而这里的“高度”完全由世界位置程序化算出、没有 UV梯度为零。源码因此采用 Mikkelsen 表面梯度法把高度场的屏幕空间硬件导数dFdx/dFdy喂进法线重建const dpdx positionView.dFdx(); const dpdy positionView.dFdy(); const r1 dpdy.cross( normalView ); const r2 normalView.cross( dpdx ); const det dpdx.dot( r1 ); const grad det.sign().mul( height.dFdx().mul( r1 ).add( height.dFdy().mul( r2 ) ) ); return det.abs().mul( normalView ).sub( grad ).normalize();2. gridLine抗锯齿重复刻线SidewalkGenerator.js#L167-L175伸缩缝和路缘分段缝都由它生成把坐标归一化到周期period用fract求到最近一条线的周期距离再用fwidth屏幕导数给出与像素宽度匹配的抗锯齿边缘const g coord.div( period ); const d float( 0.5 ).sub( fract( g ).sub( 0.5 ).abs() ); // 到最近线的距离以周期为单位 const aa fwidth( g ).max( 0.0001 ); return smoothstep( hw aa, hw - aa, d );3. detailNoise仅在近处求值的噪声SidewalkGenerator.js#L179-L195噪声采样被包在Fn单输出节点里并用If( detail 0 )分支保护注释说明其动机“sampled inside a detail branch (and kept in its own single-output Fn, so it is evaluated only in the output flow that consumes it)”——即只有消费它的输出流、且细节可见时才会真正采样噪声远处像素直接跳过。混凝土材质createSidewalkMaterialcreateSidewalkMaterial()SidewalkGenerator.js#L197-L226模拟“浇筑混凝土铺板poured concrete flags”由四层细节叠加层次实现关键数值铺板分格panel 1.5约 5 英尺纽约人行道铺板尺寸按floor( p.x / panel )等格取fract( sin(...) * 43758.5453 )哈希每板独立色相底色mix( 0x6f6f68, 0x8c8c82, tone )tone为 0.5 倍频噪声低频灰调变化单板色调乘(panelHash - 0.5) * 0.16 1每块板 ±8% 亮度差骨料杂色detailNoise( p, detail, 14, 0.07 )14 倍频、±0.07 幅度伸缩缝gridLine( p.x, panel, 0.045 ).max( gridLine( p.z, panel, 0.045 ) )双向取最大值缝宽约 9 cm法线细节bumpNormal( grain - joints * 0.012 )约 3 mm 颗粒、约 12 mm 深刻缝最终赋值到节点材质的三个通道const material new MeshStandardNodeMaterial(); material.colorNode concrete.mul( joints.mul( 0.45 ).oneMinus() ); // 缝处压暗 45% material.roughnessNode float( 0.92 ).sub( panelHash.mul( 0.05 ) ); // 0.87~0.92 粗糙度微差 material.normalNode bumpNormal( grain.sub( joints.mul( 0.012 ) ).mul( detail ) );花岗岩路缘材质createCurbMaterialcreateCurbMaterial()SidewalkGenerator.js#L228-L251把路缘做成“致密冷灰花岗岩”比混凝土更暗、更光滑底色mix( 0x46463f, 0x5c5c54, tone )0.6 倍频噪声叠加 18 倍频、0.05 幅度的细骨料斑点分段缝路缘石按seg 1.5米分块缝线半宽 0.04路面朝向面更脏用几何法线 y 分量区分顶面与立面——top smoothstep( 0.5, 0.85, normalWorldGeometry.y )顶面为 1、立面为 0立面颜色乘 0.7mix( stone * 0.7, stone, top )模拟道路侧积灰粗糙度0.7 tone * 0.1注释为“flamed granite: matte”火烧面花岗岩哑光法线4 倍频、0.002 幅度的细颗粒bumpNormal。实例化与生命周期管理一次build()产出两个InstancedMeshslab 与 curb共享同一组Matrix4放置矩阵意味着一整片城市的所有人行道板只产生 2 次 draw call与地块数量无关——这正是文档标题中“Instanced across a list of placements”的含义。build()开头的this.dispose() 材质懒创建的组合则让该生成器可以安全地反复重建例如换布局、换种子。与 CityGenerator 的集成仓库中SidewalkGenerator的主要消费者是 CityGenerator它展示了典型的调用方式构造期CityGenerator用街区布局尺寸初始化人行道生成器宽度/深度直接取自网格布局CityGenerator.js#L35-L40this.sidewalk new SidewalkGenerator( { width: this.layout.blockW, depth: this.layout.blockD, height: this.parameters.curbHeight, // 默认 0.15约 6 英寸的标准路缘高差 radius: this.parameters.curbRadius // 默认 5 } );注意这里height用的是城市级默认值curbHeight: 0.15而不是SidewalkGenerator自身的 0.5 默认值——作为路缘curb reveal时实际城市更常用 15 cm 级别。布局期仅当curbHeight 0时为每个街区中心推入一个纯平移矩阵CityGenerator.js#L55-L71slabs.push( new Matrix4().makeTranslation( blockX L.blockW / 2, 0, blockZ L.blockD / 2 ) );组装期if ( slabs.length 0 ) group.add( this.sidewalk.build( slabs ) )CityGenerator.js#L116。同时所有建筑被放置到y curb高度上building.position.set( ..., curb, ... )CityGenerator.js#L102即楼宇恰好坐落在人行道顶面上。清理期CityGenerator.dispose()中显式调用this.sidewalk.dispose()CityGenerator.js#L129与上文的生命周期设计对应。完整示例webgpu_generator_city仓库自带演示 examples/webgpu_generator_city.html 展示了完整管线WebGPURendererCityGeneratorcreateRoadMaterial潮湿沥青、车道线、斑马线与人行道网格共用同一布局对齐SkyMesh天空驱动 IBL 与太阳方向光并用 Inspector 面板暴露seed与timeOfDay参数实时重建城市。该示例的渲染截图即本文开头配图webgpu_generator_city.jpg其中抬高的圆角人行道板与路缘石即由SidewalkGenerator生成。由于SidewalkGenerator依赖three/webgpu入口与three/tsl节点模块示例通过 import map 将three、three/webgpu、three/tsl指向仓库构建产物见 webgpu_generator_city.html#L27-L36本地运行需先构建再使用仓库提供的示例服务器访问该页面。小结SidewalkGenerator是一个“小而完整”的程序化资产生成器范例几何圆角矩形Shape→ 带孔轮廓 → 单次挤出用 0.06 的重叠量消除板/缘接缝性能两个InstancedMesh承载全部地块细节噪声按相机距离门控求值材质TSL 程序化着色含世界坐标哈希分板、导数法 bump、fwidth抗锯齿刻线三个可复用的技术要点bumpNormal/gridLine与 CityGenerator.js#L187-L218 中的同名实现一致可直接移植到其他程序化表面生命周期build()前自动dispose()几何、材质跨重建复用适合反复再生的程序化场景。其上游入口 SidewalkGenerator.html.md 中的构造参数与调用方式均可在 examples/jsm/generators/city/SidewalkGenerator.js 源码中逐行对应验证。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表