three.js TreeGenerator 完全指南:用确定性种子程序化生成树干骨骼与森林
three.js TreeGenerator 完全指南用确定性种子程序化生成树干骨骼与森林【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsTreeGenerator 是 three.js 官方 examples 中用于程序化生成树木骨架的生成器它递归生长出树干、分枝与细枝每段都被扫掠为带锥度的管状曲面最终烘焙为单个非索引non-indexed的 BufferGeometry仅包含 position 与 normal 两个属性可直接用于实例化渲染整片森林。本文围绕其核心源码与官方示例完整讲解构造方式、全部生成参数、随机系统与底层几何算法并结合真实示例演示「一个生成器 不同种子 → 参差林带」的实战路径。一、它做什么不做什么按官方文档定义TreeGenerator 只负责生长树的骨架——即树干、分枝与细枝并把结果烘焙bake成一份独立的、可反复渲染的网格数据。它只产树枝不含树叶。文档明确说明 It producesbranches only; add foliage as a separate layer叶片需要作为独立的一层另行叠加例如 ParticleSystem 或独立 Mesh输出只含位置与法线每个顶点写入position与normal两类 BufferAttribute没有 UV、没有索引数组每两个相邻顶点组成的三角形都以展开non-indexed形式存储对给定 seed 完全确定同一组参数与同一种子每次build()得到几何结构一致的树木这使「种子的确定性」成为批量造林的根基每次build()都返回全新的、独立的网格网格归调用者所有。因此同一个生成器实例可以被反复改参、反复 build种出一片形态各异的树。从源码注释与实现来看其生成过程融合了几何学上的经典手法**平行传输标架parallel-transport frame**沿管体逐段推进保证管体不会像 Frenet 标架那样自旋扭曲**管道模型pipe model达·芬奇规则**决定子枝半径子枝远细于母枝符合真实树木的横截面积守恒规律黄金角golden angle ≈ 137.5°滚动使同一母枝上侧生的诸子枝沿周向错开不会排成一条直线子枝沿母枝上半段分布、并朝光源方向回拉phototropism趋光性再叠加基部外扩root flare、非线性锥度taper、重力下垂droop从而补全树木的性格。二、快速上手构造、链式设参与 build导入方式取决于构建环境。官方示例页面使用 import map将three/addons/映射到./jsm/因此在浏览器 / 打包器中通常写作import { TreeGenerator } from three/addons/generators/TreeGenerator.js;该模块位于仓库 examples/jsm/generators/TreeGenerator.js并在文件末尾同时导出了TreeGenerator与createTreeMaterial两个符号。构造函数new TreeGenerator( material )material为可选参数默认值为null。见 TreeGenerator 构造函数源码若未传入材质build()会回退调用createTreeMaterial()自动创建一块默认树皮材质。构造函数内部只保存两样东西this.material materialthis.parameters {}—— 存放后续 setter 写入的覆盖项全部默认值在build()时再补齐。最小可用示例const material new THREE.MeshStandardMaterial( { color: 0x6b4a2b } ); const generator new TreeGenerator( material ); const oak generator.setSeed( 1 ).setLevels( 4 ).build(); const pine generator.setSeed( 2 ).setLevels( 5 ).build();第 1 棵树种子 1、递归层级 4是典型的阔叶冠形第 2 棵树种子 2、递归层级 5更深的分支嵌套更接近针叶树。两句都调用同一个generator但每句.build()都返回独立的新 Mesh互不干扰。build()返回的网格会被命名源码中写死了mesh.name Tree见 build() 实现方便在场景图中定位。链式 setter 是自动生成的asetParam()exists for every default (setSeed,setLevels,setChildren, … ), each returningthisfor chaining——这正是源码里的写法在类定义之后脚本遍历TreeGenerator.defaults的所有键用一行循环为每个参数动态挂载set首字母大写驼峰方法并返回this见 自动生成 setter 的源码for ( const key of Object.keys( TreeGenerator.defaults ) ) { TreeGenerator.prototype[ set key[ 0 ].toUpperCase() key.slice( 1 ) ] function ( value ) { this.parameters[ key ] value; return this; }; }因此defaults 里有几个参数就有几个 settersetSeed、setLevels、setChildren、setBranchAngle、setAngleVariance、setLengthRatio、setTrunkLength、setTrunkRadius、setTaper、setTaperCurve、setRootFlare、setFlareFrac、setRadiusExponent、setMinRadius、setMinLength、setDroop、setUpPull、setGnarl、setRadialSegments、setSectionLength、setChildStart、setTrunkClear。这正是参数表即 API 表的设计——维护默认值字典即可自动同步全部配置入口。三、参数总表默认值、含义与取值范围所有默认值统一定义在TreeGenerator.defaults上见 默认值字典源码。下表逐项列出取值与单位以源码注释与实现为准参数默认值含义seed1随机种子同一种子 同一参数 → 完全相同的树形levels4递归深度树干、大枝、小枝、更次级小枝各算一级children[ 3, 12, 8 ]每一级母枝上侧生的子枝数量。密度靠沿母枝散布多个子枝来提升而非靠加深递归branchAngle[ 38, 50, 58 ]子枝相对母枝轴线的倾角角度制按层级取值。层级越深角度越大形成收敛的树冠angleVariance14分支角度的随机抖动幅度角度制用于打破分形的规整感lengthRatio0.62子枝长度 / 母枝长度trunkLength9树干长度世界单位直接决定树高trunkRadius0.42树干基部半径taper0.55枝干沿自身长度收细到基部半径的( 1 - taper )倍taperCurve0.7锥度曲线指数 1保持主干饱满再收细真实树木的树干饱满、近梢收细1则是笔直圆锥rootFlare0.6树干在极基部向外膨起的程度flareFrac0.18外扩作用区占整条树干长度的比例radiusExponent2.3管道模型指数childBase parentBase × ( 1 / n )^( 1 / radiusExponent )minRadius0.05细枝半径下限防止小枝锥度收成发丝minLength0.6长度短于该值的枝条停止递归droop0.05每段枝条的重力下垂量约为 droop × 段长树干不受影响保持竖直upPull0.3趋光性0 裸露的铺展锥形大量分枝朝下1 笔直向上gnarl[ 0.05, 0.16, 0.26, 0.32 ]每级管段的随机节疤摆动幅度按层级取不同值radialSegments6每圈管体的环绕顶点数细枝每加深一级自动减一sectionLength1.3每段管体的世界单位长度值越小管体越平滑高树干也保持顺滑childStart0.12子枝从母枝长度的多少比例处沿母枝向上才开始出现trunkClear0.25树干保持光洁无枝的比例调大可得到更高的干净树干其中children、branchAngle、gnarl是数组型参数与递归层级一一对应。源码在取用时做了防越界钳制p.children[ Math.min( level, p.children.length - 1 ) ]层级超过数组长度时复用末位值见 分支取参源码。四、从树苗到网格build() 内部发生了什么调用build()时build() 完整源码流程分为三步1. 参数合并与随机源初始化const p Object.assign( {}, TreeGenerator.defaults, this.parameters ); const random createRandom( p.seed );先以默认值打底、再用 setter 写入的覆盖项浅合并得到最终参数集p随后以 seed 创建确定性的伪随机数生成器见下文第六节。2. 递归生长骨架growBranchgrowBranch是核心递归函数实现见 examples/jsm/generators/TreeGenerator.js#L134-L219。它从树干基部原点、向上方向出发把一棵树长成一张扁平的管体tube列表依据length / p.sectionLength计算段数并钳制在3 ~ 24之间Math.max( 3, Math.min( 24, ... ) )使环数随长度自适应每圈环绕顶点数按层级递减radial Math.max( 3, p.radialSegments - level )细枝更省顶点沿管体逐段前进每段方向加入gnarl随机摆动非树干层级再叠加droop重力下垂next.normalize()后通过transport()推进标架并平移当前位置即 逐段生长循环每段的半径同时受非线性锥度与根部外扩双重调制let radius baseRadius * ( ( 1 - p.taper ) p.taper * Math.pow( 1 - t, p.taperCurve ) ); if ( level 0 p.rootFlare 0 ) { const flare Math.max( 0, ( p.flareFrac - t ) / p.flareFrac ); radius * 1 p.rootFlare * flare * flare * flare; // 只作用于基部、带锐利的膝盖过渡 }外扩用flare³的三次曲线实现尖锐膝部、紧贴基部的效果见 半径计算源码。若已达最深层级level p.levels - 1或当前枝长小于p.minLength本枝停止递归见 递归终止条件否则分叉fork在该母枝上方按n p.children[level]的数量散布子枝。每个子枝在母枝[start, 1]区间内取出生点比例t树干用trunkClear其它层级用childStart还带有小幅度随机扰动并从ringAt()插值出该处的位姿与半径ringAt 实现计算倾斜基础branchAngle[level]叠加angleVariance的随机抖动再乘以 DEG2RAD 转弧度计算周向滚动roll i * GOLDEN_ANGLE 随机 ±0.4——黄金角让诸子枝如真实树干那样螺旋错开先绕垂直轴倾斜、再绕母枝轴线滚动、最后向 UP 回拉趋光源码特别注释指出这个顺序很重要——若先滚动再倾斜会把一半子枝甩向下方见 子枝方向合成源码const childDir ring.tangent.clone() .applyAxisAngle( ring.normal, tilt ) .applyAxisAngle( ring.tangent, roll ); if ( p.upPull 0 ) childDir.lerp( UP, p.upPull ).normalize();按管道模型收缩子枝半径且既不超过它所离开的木质半径、也不低于 minRadius 下限const pipeDrop Math.pow( 1 / n, 1 / p.radiusExponent ); const childBase Math.max( p.minRadius, Math.min( baseRadius * pipeDrop, ring.radius ) );以childDir、length * p.lengthRatio、childBase递归进入下一层。3. 单趟输出几何emitTube 烘焙骨架长好后几何数据以一次遍历、无逐顶点对象、无数组扩容的方式填充源码注释明言 no per-vertex objects, no array growth先统计总顶点数每根管体的相邻两圈贡献radial × 6个顶点每个四边形拆两个三角形 6 个顶点均为非索引展开即vertexCount ( tube.rings.length - 1 ) * tube.radial * 6一次性分配Float32Array的 position 与 normal 数组emitTube实现见 examples/jsm/generators/TreeGenerator.js#L264-L334为每圈环在( normal, binormal )平面内展开radial个顶点顶点方向即法线方向因此法线天然朝外随后按绕向拼成朝外的三角形复用全局 scratch 数组_ring只增长、不反复分配实测高频调用时能显著减少 GC 压力。五、两种输出默认树皮材质与自定义材质默认材质createTreeMaterial若不传材质build()会调用默认的createTreeMaterial()其 JSDoc 与实现见 材质创建源码。它基于 TSL 节点材质体系构建一块MeshStandardNodeMaterialfunction createTreeMaterial( parameters {} ) { const c parameters.barkColor; const barkColor c undefined ? color( 0x4b3a2b ) : ( c.isColor || typeof c number ? color( c ) : c ); const material new MeshStandardNodeMaterial(); const grain mx_fractal_noise_float( positionLocal.mul( vec3( 2.5, 0.4, 2.5 ) ), 3 ).mul( 0.18 ); material.colorNode barkColor.mul( grain.add( 0.9 ) ); material.roughnessNode float( 0.95 ); material.metalnessNode float( 0 ); return material; }要点默认树皮色为低饱和棕褐0x4b3a2b可通过{ barkColor }传入十六进制数、Color 或 TSL 颜色节点利用 TSL 的mx_fractal_noise_float对positionLocal做非均匀缩放水平 2.5、垂直仅 0.4再叠加 0.18 系数形成竖向拉长的微弱木纹粗糙度 0.95、金属度 0营造哑光树皮质感文档注释特别说明这样的颜色在明亮雾背景下树干接近近黑剪影——这正是官方雾散射示例的用材逻辑。该函数也作为模块的第二导出项公开export { TreeGenerator, createTreeMaterial }你可以在不实例化生成器的情况下单独复用它做树皮。注意默认材质内部使用MeshStandardNodeMaterial与 TSL 噪声节点属于 WebGPU / 节点渲染管线体系build()返回的 Mesh 若用在传统 WebGLRenderer 下请显式传入自定义材质。自定义材质构造函数允许传入任意 Material。示例 webgpu_custom_fog_scattering.html 的做法是传入纯黑的无光照材质用于雾中剪影const material new THREE.MeshBasicMaterial( { color: 0x000000 } ); const generator new TreeGenerator( material );六、确定性随机mulberry32deterministic for a givenseed 的实现是一个经典轻量 PRNG ——mulberry32createRandom 源码function createRandom( seed ) { let s ( seed 0 ) || 1; return function () { s ( s 0x6D2B79F5 ) | 0; let t Math.imul( s ^ ( s 15 ), 1 | s ); t ( t Math.imul( t ^ ( t 7 ), 61 | t ) ) ^ t; return ( ( t ^ ( t 14 ) ) 0 ) / 4294967296; }; }它把 32 位种子压入一个不透明的内部状态机每次调用返回[0, 1)的浮点。因为整个生成过程gnarl 抖动、分支角度抖动、子枝散布扰动、周向滚动扰动全部只消费这一个序列所以只要 seed 与参数一致树形就逐顶点可复现——这是多帧一致、跨端一致乃至服务端预烘焙几何的关键保证。七、实战一个生成器生成整片森林官方文档强调one generator can be re-parametrized and built repeatedly to grow a varied stand。要拼出形态多样又彼此协调的林带推荐组合拳基础结构差异靠参数、个体差异靠 seed、密度靠实例化。官方示例 examples/webgpu_custom_fog_scattering.html场景截屏见文首插图演示了完整流程第一步同一生成器产出多个品种变体const generator new TreeGenerator( material ); for ( let v 0; v 6; v ) { const mesh generator .setSeed( v 1 ) .setTrunkLength( 2.6 v * 0.3 ) // 树冠高低错落浮现在雾中不同高度 .setTrunkRadius( 0.06 ) .setTaper( 0.28 ) // 苗条、缓慢收细苏格兰松造型 .setLevels( 4 ) .setChildren( [ 7, 5, 4 ] ) // 宽阔轮生大枝逐级细分出大量细枝 .setBranchAngle( [ 55, 50, 46 ] ) // 圆润且向上卷的树冠 .setAngleVariance( 22 ) .setLengthRatio( 0.46 ) // 短促的冠枝 .setMinLength( 0.04 ) .setDroop( 0.05 ) .setUpPull( 0.42 ) // 冠枝向光生长 .setGnarl( [ 0, 0.14, 0.24, 0.34 ] ) // 树干笔直、小枝更飘忽 .setSectionLength( 0.34 ) .setRadialSegments( 7 ) .setRadiusExponent( 2.5 ) // 主干纤细、细枝柔韧 .setMinRadius( 0.0023 ) // 极细的小枝 .setTrunkClear( 0.72 ) // 高而干净的树干树冠只在顶端 .build(); variants.push( mesh.geometry ); }这段代码把官方示例中的一组松树配方完整呈现了出来trunkClear拉高干净树干、upPull让冠枝向上、gnarl首位置 0 保证主干笔直仅保存每个变体的geometry备用于实例化。第二步InstancedMesh 批量布点生成好 6 份变体几何后示例用虚拟占位Object3D随机排布几百个位姿矩阵位置 微倾 非均匀缩放再以 InstancedMesh 一次性绘制每个变体variants.forEach( ( geometry, v ) { const mesh new THREE.InstancedMesh( geometry, material, placements[ v ].length ); for ( let k 0; k placements[ v ].length; k ) mesh.setMatrixAt( k, placements[ v ][ k ] ); mesh.instanceMatrix.needsUpdate true; scene.add( mesh ); } );因为build()输出的是仅含 position/normal 的普通 BufferGeometry所以它可以直接喂给InstancedMesh做一批次绘制——这正是官方示例用 6 份几何在13×12格点约 156 个布点上铺满整片雾林的底层原因。此外示例还从最近的一个变体里单独挑出两棵主角树用普通Mesh放到镜头近处强调景深层次。第三步运行示例仓库根目录下执行开发服务器即可本地查看该示例npm run dev该命令会先构建再以node utils/server.js -p 8080启动静态服务脚本见 package.json随后在浏览器中访问对应页面如示例中的webgpu_custom_fog_scattering并结合其中的可调参数面板实时观察雾密度、散射强度对林带观感的影响。八、造树参数调优速查根据参数语义与官方示例配方可总结出几条务实的调参经验改变树的高矮胖瘦调trunkLength与trunkRadius决定主尺度lengthRatio控制分枝相对长度minLength决定树冠能细化到多细。改变树的冠形小branchAngle如 30 出头 高upPull→ 柱状上升冠大branchAngle如 50–60 较高upPull→ 圆润而回翘的冠示例松树配方upPull拉低则枝条下探。改变树干的气质taperCurve 1保主干饱满trunkClear调高得到通直干净树干rootFlare与flareFrac控制根部外扩的幅度与范围gnarl[0] 0可得笔直树干增大后树干也随之虬曲。控制面数预算减少radialSegments每级还会自动再减一、增大sectionLength以降低管体分段、降低levels都会显著减少顶点顶点总数可按Σ (rings.length-1) × radial × 6预估。批量变化固定结构参数、只轮换seed即可在保持风格一致的前提下获得位置、曲率、枝点各异的大量个体要更强的差异则混合多组children/branchAngle/gnarl配置再各自配合多个种子。九、适用边界与注意事项WebGPU / 节点管线关联默认createTreeMaterial依赖MeshStandardNodeMaterial与three/tsl噪声节点它适合 WebGPU 渲染路径传统 WebGL 场景请显式传入自备材质。不含叶片与 LOD文档与源码均明确branches only。成片森林往往需要额外补充叶片层、以及基于树冠包围球的 LOD 策略。确定性有前提确定性只针对单棵树几何而言——同一 seed 给出同一根骨架构型示例中整片森林的随机感来自外层对种子与位姿的编排这部分随机不受生成器保证。导入路径差异本仓库中模块位于examples/jsm/generators/浏览器 import map 常用别名three/addons/。在 npm 安装的 three 发行包中路径可能不同请以实际环境为准。十、延伸阅读生成器核心实现examples/jsm/generators/TreeGenerator.js官方 API 文档页docs/pages/TreeGenerator.html以及 BufferGeometry、Mesh、Material、InstancedMesh 相关类页使用 TreeGenerator 的官方完整示例examples/webgpu_custom_fog_scattering.html运行本地示例在仓库根目录执行npm run dev后访问对应示例页面【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考