threejs龙门客栈:纯代码复刻大漠客栈
基于 Three.js 程序化建模复刻的电影《新龙门客栈》大漠客栈场景:木构客栈、起伏沙丘、风沙粒子、30 秒一昼夜的时辰流转全部由代码生成,还支持一键切换为 88MB 的 GLB 实景模型版。
龙门客栈 (Longmen Inn)
前面几篇都是”数据驱动”的可视化(天体按物理参数运动、元素按周期排布),这一篇换个路子——不加载任何场景资源,用纯代码”盖”出一座客栈:一个盒子一根柱地拼出木构大院,一张 Canvas 一张 Canvas 地画出木纹、青瓦、匾额和麻布幡,再用噪声函数堆出大漠沙丘。同时保留一个彩蛋:一键切换为 Sketchfab 实景模型(GLB)的”模型版”,两种形态随意对比。
目录结构
1 | |
架构说明
1. 场景引擎 (engine.ts)
InnScene 类是总指挥,把各模块组装起来并驱动动画循环:
- 渲染配置:PCFSoftShadowMap 软阴影、ACES Filmic 色调映射(电影感的关键)、场景雾
- 昼夜驱动:每帧根据运行时间算出当前时刻,交给
DayNight更新天穹、光照和雾色 - 机位切换:预设机位间用缓动插值平滑过渡
- 模型懒加载:GLB 模型(88MB)首次切换时才加载,
onProgress上报进度 - FPS 统计:每 0.5 秒回调一次给 UI 显示
2. 昼夜系统 (daynight.ts)
一昼夜 30 秒,核心是 DayNight 类。t01(0=日出,0.25=正午,0.5=日落,0.75=子夜)一个参数驱动全部状态:
- 天穹:自定义着色器按世界坐标高度做”天顶-地平线-地底”三段渐变
- 日月同弧线:太阳和月亮走同一条偏南弧线(坐北朝南,符合客栈朝向),方向光白天跟太阳、夜晚跟月亮,无缝换班
- 关键帧色板:白天/夜晚/晨昏三套颜色(天穹、雾、环境光、阳光)按
dayness/nightness两个权重插值,日出日落时太阳贴地平线,霞光权重拉高,天际线染成橙红 - 十二时辰:时刻映射为子丑寅卯……通过回调通知 UI 显示
3. 大漠地形 (desert.ts)
1200×1200 的 PlaneGeometry 切成 240×240 分段,每个顶点的高度由三种尺度的地形叠加:
| 层次 | 生成方式 |
|---|---|
| 近处波状小沙丘 | 正弦函数叠加,幅度小 |
| 中景鱼脊状沙脊 | 确定性二维噪声取 `1- |
| 远景巨型沙山 | 8 座手工布点的高斯函数山头(h·e^(-d²/r²)),错落围合 |
两个细节:一是院落必须平坦——用 smoothstep 按到客栈的距离做权重,48 米内全部压平;二是噪声必须确定性(正弦叠加代替随机数),否则每次刷新地形都不同。
沙丘的明暗用顶点色实现:向光面浅、背光面深、山顶泛白,vertexColors 与沙地纹理相乘,一个通道解决大范围色彩过渡,不用画第二张贴图。
4. 客栈大院 (inn.ts)
参照电影布局的完整院落:门楼(”龙门客栈”匾 + 大门对联)→ 影壁 → 中央庭院(篝火、水井、桌椅、拴马桩)→ 两层主楼(”安旅堂”匾 + 对联)→ 东西厢房,外围夯土围墙。全部用 BoxGeometry / CylinderGeometry 等基本体拼装,封装了 box() / cyl() 两个带阴影设置的小工厂,几百个构件按坐标摆放。
最有味道的一个构件是檐角长幡:PlaneGeometry 细分 10×24 网格,每帧直接改顶点坐标做布料模拟——幡头固定,越往下摆动越大,波动由四个正弦波叠加:
- 自上而下的主行波(风势越猛频率越高)
- 横向细褶的”猎猎”抖动
- 低频鼓荡(绷紧-舒展)
- 风歇时向内堆叠的褶皱
风势本身又是两个慢正弦波叠出的间歇性阵风,所以幡面的起伏有”松-紧-翻飞”的节奏感,不是机械循环。幡底还有 42 个沙尘粒子随风势明灭、顺风飘散,夜间天黑自动隐去。
5. 配景与风沙 (props.ts / sandstorm.ts)
- 配景:围石篝火(双层火焰锥 + 呼吸灯般摇曳的点光源)、水井、两桌酒具(大侠们围坐喝酒的地方)、高出围墙的四叶木风车、红篷马车、门口大枯树、院外骆驼刺与乱石
- 风沙:近景 2400 颗沙粒(顺风疾行 + 上下翻卷,出界即从另一侧回收)+ 远景 320 个大尺寸低透明度浮尘 Sprite 粒子,两层视差叠出”大漠苍茫”
6. 程序化纹理 (textures.ts)
13 种纹理全部用 2D Canvas 画出来再包成 CanvasTexture,零图片资源:
| 纹理 | 画法 |
|---|---|
| 沙地 | 底色 + 9000 颗随机颗粒 + 正弦风蚀波纹 |
| 旧木板 | 竖向木纹曲线 + 板缝 + 节疤 |
| 青瓦 | 逐格画筒瓦(椭圆明暗渐变)+ 仰瓦沟 + 瓦当 |
| 匾额 | 黑漆底 + 双层金边 + 书法体金字带金晕 |
| 对联 | 黑漆木底 + 暗金竖排字 + 阴刻高光边 |
| 长幡 | 麻布经纬织纹 + 陈旧斑驳渍 + 墨书”龙门客栈” |
| 夯土墙 | 水平层理 + 风化噪点 + 雨水冲刷痕 |
| 额枋彩画 | 石青底 + 石绿漩涡 + 沥金线(旋子彩画的程式) |
| 茅草顶 | 1500 条随机草束短线 + 层叠压纹 |
书法字使用马善政毛笔字体(Ma Shan Zheng),并等待 document.fonts.ready 之后再初始化场景——否则 Canvas 画匾额时字体还没就绪,会退化成宋体。
功能特性
机位系统
五个预设机位:正面(默认)/ 庭院 / 侧面 / 俯瞰 / 远眺。切换时相机位置和注视点同时做 1.6 秒的缓动插值(easeInOutCubic),运镜如无人机航拍。远眺机位特意设在房子西北后方 376 米、俯角 10°——正好看到日月沿偏南弧线掠过屋顶的完整升落过程。
模型版切换
代码木构 vs GLB 实景模型一键切换:
- 懒加载:模型首次切换时才请求,加载进度实时上报
- 自动落地:模型包围盒底部不是平面(栅栏桩、小树低于底板),直接取最低点会悬空。这里取底部 20% 高度内顶点密度最高的一层作为主体基面贴地,低矮桩柱随之埋入沙中
- 卷轴加载动画:古风”客栈图卷徐徐展开”进度条,且即使模型已缓存(瞬时切换)也至少展示 1.5 秒——保留仪式感
氛围细节
- 顶部对联题诗逐字显现(0.6 秒一字,墨迹晕开效果)
- 夜间窗纸透光更亮、灯笼点起、匾额对联隐约透光(
emissiveIntensity随nightness联动) - 竖屏(手机)自动扩大视野(FOV 55° → 72°)
Three.js 特性与实现
阴影系统
renderer.shadowMap.type = THREE.PCFSoftShadowMap软阴影- 方向光
castShadow,阴影相机手动收窄到 ±65 的包围盒(只覆盖客栈大院),2048(移动端 1024)分辨率下依然清晰 shadow.bias = -0.0005消除自阴影痤疮- 客栈每个构件(box/cyl 工厂里)统一
castShadow + receiveShadow
色调映射与雾
ACESFilmicToneMapping+ 动态曝光(toneMappingExposure白天 1.12、夜晚压暗到 0.94)——同一套灯光因此能同时撑起正午的炽烈和月夜的清冷THREE.Fog雾色随昼夜联动(fog.color.copy(daynight.fogColor)),远处沙山融进天色,地平线因此没有生硬边界
自定义着色器天穹
ShaderMaterial + side: THREE.BackSide 的大球,片元着色器里按世界坐标 y 归一化后做三段混合:
1 | |
天顶/地平线/地底三个颜色作为 uniform,每帧由 CPU 端按昼夜权重插值后写入——着色器只负责”怎么渐变”,颜色语义全在 JS 侧管理。
Sprite 与粒子
- 太阳:
Sprite+ 多层径向渐变的 Canvas 光团贴图 +AdditiveBlending,纯辉光无实体边缘,日升日落时颜色向橙红插值 - 月亮:Canvas 画的满月(月晕 + 环形山阴影),普通透明混合
- 星空:1400 颗星点均匀分布在半径 860 的上半球,透明度随
nightness呼吸闪烁 - 低空浮尘:大尺寸
Sprite薄雾横在天际,depthWrite: false避免与沙丘冲突
顶点级布料动画
长幡是本模块的招牌特效:不走顶点着色器,而是每帧在 JS 里直写 geometry.attributes.position 数组再 needsUpdate = true。240 个顶点的规模下 CPU 完全算得动,好处是波形公式(四个波叠加 + 阵风系数 + 垂坠/边缘权重)可以直接写成可读的数学表达式,调试远比写 GLSL 方便。
GLTFLoader 与模型落地
GLTFLoader 加载后做三步处理:按最大边长归一化缩放(50 米)、遍历所有 Mesh 顶点做直方图密度分析找主体基面、包围盒居中 + 基面贴地。这是把任意来源模型”放进”程序化场景的通用套路。
资源回收
dispose() 做得比常规更彻底:遍历全场景,几何体、材质逐个释放,材质上的每一张贴图也反射遍历释放(v instanceof THREE.Texture),最后摘掉画布、断开 ResizeObserver。配合组件的 onBeforeUnmount,单页应用里反复进出也不泄漏。
技术栈
- Vue 3: 组件框架(Composition API +
<script setup>) - Three.js: 3D 渲染引擎
- TypeScript: 类型安全
- OrbitControls: 轨道控制器
- GLTFLoader: 模型版客栈加载