国风乡村 3D 地图生成器 · 技术分享文档单文件 HTML Three.js r128 实现的程序化国风古村 3D 地图生成器无构建工具、无本地依赖双击即可运行1. 项目概览项说明技术栈原生 JavaScript (ES5) Three.js r128部署形态单个 HTML 文件约 1300 行CDN 加载引擎核心玩法一键程序化生成随机古村地图支持种子复现交互左键旋转 / 右键平移 / 滚轮缩放 / 空格重新生成 / 移动端单指旋转 双指捏合渲染特性PCF 软阴影、ACES 色调映射、sRGB 输出、雾效、实例化渲染1.1 三层容错的引擎加载依次尝试 jsDelivr → unpkg → cdnjs 三个 CDN全部失败则显示友好错误页WebGL 创建失败也有独立提示scriptsrchttps://cdn.jsdelivr.net/npm/three0.128.0/build/three.min.js/scriptscriptif(typeofTHREEundefined){document.write(script srchttps://unpkg.com/...\/script);}/script2. 架构设计整体流程是经典的“数据 → 逻辑网格 → 3D 场景”三段式点击生成 / 空格 │ ▼ generate(seed) ← 纯逻辑层 ├── mulberry32(seed) 确定性随机数 ├── 42×42 Int8Array 网格 逐步铺设 河流→道路→房屋→广场→农田→宝塔→装饰 └── 收集摆放数据 (houses, farms, trees, ...) │ ▼ buildScene(data) ← 渲染层 ├── InstancedMesh 批量绘制 地块 / 水面 / 庄稼 / 花朵 / 远景森林 ├── 工厂函数生成组合体 makeHouse / makePagoda / makeBridge ... └── addPop() 注册入场动画 │ ▼ animate() ← 每帧循环 ├── 云朵漂移 / 水面色相波动 / 小船起伏 └── easeOutBack 弹出动画队列2.1 逻辑网格Tile Map地图核心是一个42×42的Int8Array每格 2 世界单位varN42,CELL2,HALFN*CELL/2;varT{EMPTY:0,WATER:1,ROAD:2,BRIDGE:3,HOUSE:4,FARM:5,TREE:6,DECOR:7,PLAZA:8};配套一组极简的网格 APIidx / inB / get / set / wpwp 负责网格坐标 → 世界坐标转换。所有生成规则都只操作这个一维数组与渲染完全解耦——先想清楚哪格是什么再决定画成什么样。2.2 确定性随机mulberry32用 32 位种子的 mulberry32 PRNG 替换Math.random同一种子 → 同一村庄方便复现与分享functionmulberry32(a){returnfunction(){a|0;aa0x6D2B79F5|0;vartMath.imul(a^a15,1|a);ttMath.imul(t^t7,61|t)^t;return((t^t14)0)/4294967296;};}在其上封装rr(a,b)浮点区间、ri(a,b)整数区间、pick(arr)随机取样、shuffle洗牌构成整个生成器的随机工具箱。3. 程序化生成算法生成顺序经过精心设计优先级高的元素先落地后续元素避让3.1 河流随机游走 (Random Walk)随机选择横向或纵向贯穿宽 2~3 格。每前进一格有 45% 概率上下偏移 1 格形成自然蜿蜒if(rand()0.45)rpospick([-1,0,1]);rposclamp(rpos,4,N-5-rw);rcell/dcell两个坐标交换函数让横河纵路与纵河横路共用同一套逻辑。3.2 主路与桥正交穿越 冲突升级主路与河流方向垂直同样随机游走关键技巧是靠近水域时锁定方向向前探测 4 格保证道路垂直过河。路遇水格自动升级为BRIDGE桥格集合最终会拟合成一座完整石拱桥。3.3 支路、牌坊、房屋支路从主路上随机点垂直伸出 2~3 条长 8~16 格遇水即停。牌坊主路两端a2 与 aN-385% 概率各立一座占位DECOR防止被侵占。房屋沿路吸附算法打乱所有路格对每个路格尝试 4 个方向放置 2×2~3×2 的房屋要求目标格全部EMPTY周围一圈无其他HOUSE避免贴脸朝向取Math.atan2(-dir[0], -dir[1])大门永远面向道路。3.4 广场 水井、农田、宝塔广场在前 80 个路格附近找 3×3 空地中央放水井并打通与路的连接格。农田随机撒 3~5 块 3~5 × 3~4 的矩形要求周边 3 格内有路/水/房贴近生活区55% 概率为水田。宝塔随机试 60 次要求 3×3 空地且曼哈顿距离最近房屋 ≥ 16——远离村落的山野孤塔更有意境。3.5 植被概率场叠加树木的生成概率是多个因子的叠加简单却有效varpedgeD4?0.24:0.045;// 地图边缘密林if(靠近树丛中心)p0.3;// 3 个随机 grove 聚簇if(nearW)p0.2;// 水边概率固定树种也与环境关联水边多柳树/桃树内陆多松树/阔叶树。水边空格还有 25% 概率长芦苇1.2% 概率放石头。4. 渲染优化4.1 材质 / 几何体缓存池M()与G()两个工厂函数按 key 缓存全场景同色材质、同参几何体只创建一次functionM(hex,opts){varkeyhex|JSON.stringify(opts||);if(!MATS[key])MATS[key]newTHREE.MeshLambertMaterial(...);returnMATS[key];}重新生成地图时disposeMap()通过GEOS_set区分共享几何体与一次性几何体只 dispose 后者避免误杀缓存。4.2 InstancedMesh 大批量绘制数量最大的五类物体全部使用实例化渲染Draw Call 从数千降到个位数对象实例数量级逐实例颜色地块 (land)~1600setHSL 按类型着色带随机扰动水面 (water)~100蓝绿色随机明度庄稼 (crops)数百~上千旱田/水田不同色相花朵 (flowers)数百5 色随机远景森林 (foltrk)220×2绿色系 HSL 抖动逐格颜色扰动col.setHSL(h, s, l rand()*0.05)是低成本打破单调感的关键。4.3 顶点着色的程序化远山makeMountain在 ConeGeometry 上做两层正弦扰动低频轮廓 高频细节再按高度 t 混合底色→山腰→岩石→雪线四段颜色写入 vertex color一个材质渲染所有山体varnMath.sin(ang*f1ph1)*0.22Math.sin(ang*f2ph2)*0.12;pos.setX(i,x*(1n*(1-t*0.55)));// 越往山顶扰动越小远山分两圈布置半径 1.5~1.95 HALF 的中景、2~3 HALF 的远景加上 16 个绿色半球丘陵与 220 棵实例化松树营造纵深。4.4 光影与氛围半球光天空蓝 / 草地绿 暖色平行光2048 阴影贴图 PCFSoft normalBias 防阴影痤疮THREE.Fog(0xcfe3bd, 130, 400)让远山融入天色太阳与云朵材质设fog:false排除雾化太阳 实体球 半透明光晕球 Canvas 径向渐变生成的 Sprite 辉光零贴图资源CSS 径向渐变 vignette 覆盖层压暗四角Lambert 材质 ACES 色调映射得到柔和的低多边形水彩感5. 动画系统全部动画集中在一个requestAnimationFrame循环dt钳制在 0.05s 防切页跳变入场弹出每个物体注册进popList延迟 距地图中心距离popDelay用easeOutBack缓动从 0 弹到目标缩放——生成地图时呈现从中心向外扩散的波浪式生长效果。云朵9 组椭球云以各自速度向 X 漂移超界回绕。水面整体材质明度按sin(t*1.4)波动模拟粼光。小船sin驱动 y 浮沉 z 轴微倾摇橹感。6. 交互与相机自实现轨道相机球坐标 theta/phi/radius target左键拖拽改 theta/phiphi 钳制 0.18~1.35不许翻到地下右键平移沿相机水平面的 forward/right 移动 target范围限 ±45滚轮/双指捏合缩放 radius28~190移动端 touch 事件独立处理touch-action:none防浏览器手势冲突7. 最后效果想要获取源码绿泡泡搜 ”码来的小朋友” 。