国风乡村 3D 地图生成器世界

国风乡村 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 创建失败也有独立提示:

html 复制代码
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.min.js"></script>
<script>if (typeof THREE === 'undefined') { document.write('<script src="https://unpkg.com/..."><\/script>'); }</script>

2. 架构设计

整体流程是经典的 "数据 → 逻辑网格 → 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×42Int8Array,每格 2 世界单位:

js 复制代码
var N = 42, CELL = 2, HALF = N * CELL / 2;
var T = { EMPTY:0, WATER:1, ROAD:2, BRIDGE:3, HOUSE:4, FARM:5, TREE:6, DECOR:7, PLAZA:8 };

配套一组极简的网格 API:idx / inB / get / set / wp(wp 负责网格坐标 → 世界坐标转换)。所有生成规则都只操作这个一维数组,与渲染完全解耦------先想清楚"哪格是什么",再决定"画成什么样"

2.2 确定性随机:mulberry32

用 32 位种子的 mulberry32 PRNG 替换 Math.random,同一种子 → 同一村庄,方便复现与分享:

js 复制代码
function mulberry32(a) {
  return function () {
    a |= 0; a = a + 0x6D2B79F5 | 0;
    var t = Math.imul(a ^ a >>> 15, 1 | a);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}

在其上封装 rr(a,b) 浮点区间、ri(a,b) 整数区间、pick(arr) 随机取样、shuffle 洗牌,构成整个生成器的随机工具箱。


3. 程序化生成算法

生成顺序经过精心设计,优先级高的元素先落地,后续元素避让

3.1 河流:随机游走 (Random Walk)

随机选择横向或纵向贯穿,宽 2~3 格。每前进一格有 45% 概率上下偏移 1 格,形成自然蜿蜒:

js 复制代码
if (rand() < 0.45) rpos += pick([-1, 0, 1]);
rpos = clamp(rpos, 4, N - 5 - rw);

rcell/dcell 两个坐标交换函数让"横河+纵路"与"纵河+横路"共用同一套逻辑。

3.2 主路与桥:正交穿越 + 冲突升级

主路与河流方向垂直,同样随机游走;关键技巧是靠近水域时锁定方向 (向前探测 4 格),保证道路垂直过河。路遇水格自动升级为 BRIDGE,桥格集合最终会拟合成一座完整石拱桥。

3.3 支路、牌坊、房屋

  • 支路:从主路上随机点垂直伸出 2~3 条,长 8~16 格,遇水即停。
  • 牌坊:主路两端(a=2 与 a=N-3)85% 概率各立一座,占位 DECOR 防止被侵占。
  • 房屋(沿路吸附算法 ):打乱所有路格,对每个路格尝试 4 个方向放置 2×2~3×2 的房屋,要求:
    1. 目标格全部 EMPTY
    2. 周围一圈无其他 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 植被:概率场叠加

树木的生成概率是多个因子的叠加,简单却有效:

js 复制代码
var p = edgeD < 4 ? 0.24 : 0.045;   // 地图边缘密林
if (靠近树丛中心) p += 0.3;          // 3 个随机 grove 聚簇
if (nearW) p = 0.2;                  // 水边概率固定

树种也与环境关联:水边多柳树/桃树,内陆多松树/阔叶树。水边空格还有 25% 概率长芦苇,1.2% 概率放石头。


4. 渲染优化

4.1 材质 / 几何体缓存池

M()G() 两个工厂函数按 key 缓存,全场景同色材质、同参几何体只创建一次:

js 复制代码
function M(hex, opts) {
  var key = hex + '|' + JSON.stringify(opts || '');
  if (!MATS[key]) MATS[key] = new THREE.MeshLambertMaterial(...);
  return MATS[key];
}

重新生成地图时,disposeMap() 通过 GEOS_set 区分共享几何体与一次性几何体,只 dispose 后者,避免误杀缓存。

4.2 InstancedMesh 大批量绘制

数量最大的五类物体全部使用实例化渲染,Draw Call 从数千降到个位数

对象 实例数量级 逐实例颜色
地块 (land) ~1600 setHSL 按类型着色,带随机扰动
水面 (water) ~100 蓝绿色随机明度
庄稼 (crops) 数百~上千 旱田/水田不同色相
花朵 (flowers) 数百 5 色随机
远景森林 (fol+trk) 220×2 绿色系 HSL 抖动

逐格颜色扰动(col.setHSL(h, s, l + rand()*0.05))是低成本打破单调感的关键。

4.3 顶点着色的程序化远山

makeMountain 在 ConeGeometry 上做两层正弦扰动(低频轮廓 + 高频细节),再按高度 t 混合底色→山腰→岩石→雪线四段颜色写入 vertex color,一个材质渲染所有山体:

js 复制代码
var n = Math.sin(ang * f1 + ph1) * 0.22 + Math.sin(ang * f2 + ph2) * 0.12;
pos.setX(i, x * (1 + n * (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 防切页跳变:

  1. 入场弹出 :每个物体注册进 popList,延迟 = 距地图中心距离(popDelay),用 easeOutBack 缓动从 0 弹到目标缩放------生成地图时呈现从中心向外扩散的波浪式生长效果。
  2. 云朵:9 组椭球云以各自速度向 +X 漂移,超界回绕。
  3. 水面 :整体材质明度按 sin(t*1.4) 波动,模拟粼光。
  4. 小船sin 驱动 y 浮沉 + z 轴微倾,摇橹感。

6. 交互与相机

自实现轨道相机(球坐标 theta/phi/radius + target):

  • 左键拖拽改 theta/phi(phi 钳制 0.18~1.35,不许翻到地下)
  • 右键平移沿相机水平面的 forward/right 移动 target,范围限 ±45
  • 滚轮/双指捏合缩放 radius(28~190)
  • 移动端 touch 事件独立处理,touch-action:none 防浏览器手势冲突

7. 最后效果

想要获取源码,绿泡泡搜🔍 "码来的小朋友" 。

相关推荐
河南花仙子科技9 小时前
小游戏开发中常见的BUG类型及成因
人工智能·科技·游戏·bug
爱自由的代码工9 小时前
游戏上线前需要准备什么:核心逻辑、执行步骤与关键指标
游戏·游戏发行·游戏运营·游戏分发
liulilittle11 小时前
麻将系统架构
服务器·网络·分布式·游戏·系统架构·通信
熬夜苦读学习1 天前
基于websocket的多用户五子棋网页游戏
linux·服务器·网络·websocket·网络协议·游戏·五子棋
yangmu32031 天前
【碧蓝幻想Relink】DPS伤害统计Mod安装教程 + XMOD物品面板功能推荐
游戏
lxysbly1 天前
Android PS3模拟器下载推荐2026|安卓PS3模拟器哪个好?手机玩PS3游戏指南
android·游戏·智能手机
笨鸟先飞的橘猫2 天前
游戏场景之大世界架构设计构想
游戏
AI科技星2 天前
基于全域数学公理体系的三元极值题最简求解法【乖乖数学】
线性代数·算法·游戏·决策树·机器学习·乖乖数学·全域数学
玖玥拾2 天前
Unity 3D 基础(一)Unity基础架构、物理系统、视觉渲染、空间变换与对象查询
笔记·游戏·3d·unity·游戏引擎