Three.js 入门系列(9):从零搭一座“闹鬼小屋”

前面几篇把材质、纹理、光源。这些模块都过了一遍。这篇不学新东西了,用它们搭一座"闹鬼小屋"------有房子、有墓碑、有鬼魂、有雾。本篇不讲每一行代码,重点讲怎么把学过的知识串起来,做成一个完整的场景。

效果预览

一、先搭房子

设计思路

房子是整个场景的核心。我把它分成几个部分:墙壁、屋顶、门、灌木丛。用 THREE.Group 把房子包起来,方便整体管理位置和旋转。

关键代码

javascript

php 复制代码
// 房子组
const house = new THREE.Group()
scene.add(house)

// 墙壁
const walls = new THREE.Mesh(
    new THREE.BoxGeometry(4, 2.5, 4),
    new THREE.MeshStandardMaterial({ 
        map: wallColorTexture,
        aoMap: wallARMTexture,
        normalMap: wallNormalTexture,
        roughnessMap: wallARMTexture
    })
)
walls.position.y = 1.25
house.add(walls)

// 屋顶
const roof = new THREE.Mesh(
    new THREE.ConeGeometry(3.5, 1, 4),
    new THREE.MeshStandardMaterial({ color: '#b35f45' })
)
roof.position.y = 2.5 + 0.5
roof.rotation.y = Math.PI * 0.25
house.add(roof)

// 门
const door = new THREE.Mesh(
    new THREE.PlaneGeometry(2.2, 2.2, 100, 100),
    new THREE.MeshStandardMaterial({
        map: doorColorTexture,
        transparent: true,
        alphaMap: doorAlphaTexture,
        aoMap: doorAmbientOcclusionTexture,
        displacementMap: doorHeightTexture,
        displacementScale: 0.1,
        normalMap: doorNormalTexture,
        metalnessMap: doorMetalnessTexture,
        roughnessMap: doorRoughnessTexture
    })
)
door.position.y = 1
door.position.z = 2 + 0.01
house.add(door)

// 灌木丛
const bushGeometry = new THREE.SphereGeometry(1, 16, 16)
const bushMaterial = new THREE.MeshStandardMaterial({ color: '#0da00d' })

const bush1 = new THREE.Mesh(bushGeometry, bushMaterial)
bush1.scale.set(0.5, 0.5, 0.5)
bush1.position.set(0.8, 0.2, 2.2)
house.add(bush1)
// 同理添加 bush2, bush3, bush4...

做了什么

  • 墙壁用砖墙纹理,配合 aoMapnormalMaproughnessMap 增加真实感
  • 屋顶用锥体几何体,旋转 45 度让棱角对齐墙壁
  • 门是一块平面,叠了颜色、透明度、环境遮蔽、置换、法线、金属度、粗糙度 7 张贴图
  • 灌木丛用球体缩放,大小不一地摆在门两侧

关键技巧

UV2 设置aoMap 需要用到第二套 UV 坐标,需要手动添加:

javascript

php 复制代码
walls.geometry.setAttribute(
    'uv2',
    new THREE.Float32BufferAttribute(walls.geometry.attributes.uv.array, 2)
)

门和地面也是同样处理。

二、墓碑围成圈

设计思路

墓碑要散落在房子周围,但不能整整齐齐。用 sincos 算出圆形上的坐标,再加一点随机偏移,让墓碑看起来像是自然长出来的。

关键代码

javascript

ini 复制代码
const graves = new THREE.Group()
scene.add(graves)

const graveGeometry = new THREE.BoxGeometry(0.6, 0.8, 0.2)
const graveMaterial = new THREE.MeshStandardMaterial({ color: '#b2b6b1' })

for(let i = 0; i < 50; i++) {
    const angle = Math.random() * Math.PI * 2
    const radius = 3 + Math.random() * 6
    const x = Math.sin(angle) * radius
    const z = Math.cos(angle) * radius

    const grave = new THREE.Mesh(graveGeometry, graveMaterial)
    grave.position.set(x, 0.3, z)
    grave.rotation.y = (Math.random() - 0.5) * 0.4
    grave.rotation.z = (Math.random() - 0.5) * 0.4
    graves.add(grave)
}

做了什么

  • 50 个墓碑分布在半径 3 到 9 的环形区域内
  • 每个墓碑的位置、旋转都有随机偏移
  • 看起来像是年久失修的墓地,自然散落

三、灯光设计

设计思路

三种光源各司其职:环境光提供基础照明,月光从一侧照过来营造阴森感,门灯是屋里唯一的暖色光源。鬼魂会发光,变成会移动的彩色光源。

关键代码

javascript

csharp 复制代码
// 环境光
const ambientLight = new THREE.AmbientLight('#b9b5ff', 0.12)
scene.add(ambientLight)

// 月光(平行光)
const moonLight = new THREE.DirectionalLight('#b9b5ff', 0.12)
moonLight.position.set(4, 5, -2)
scene.add(moonLight)

// 门灯(点光源)
const doorLight = new THREE.PointLight('#ff7d46', 1, 7)
doorLight.position.set(0, 2.2, 2.7)
house.add(doorLight)

// 鬼魂(带颜色的点光源)在动画部分会移动
const ghost1 = new THREE.PointLight('#ca8eff', 6)
// ghost2, ghost3 同理
scene.add(ghost1, ghost2, ghost3)

做了什么

  • 环境光和月光都是冷色调(蓝紫色),营造夜晚氛围
  • 门灯是暖橙色,形成冷暖对比
  • 三个鬼魂各自带有不同颜色的光(紫、粉、蓝),并且会移动

四、雾(Fog)

设计思路

雾的作用:让远处的物体渐渐消失,模糊场景边界,增强神秘感。同时还能掩盖远处的性能细节。

关键代码

javascript

ini 复制代码
const fog = new THREE.Fog('#262837', 1, 15)
scene.fog = fog

做了什么

  • 颜色和场景背景色 #262837 保持一致
  • 从距离 1 开始出现雾,到距离 15 完全消失
  • 远处的墓碑会逐渐融入雾中

五、草地纹理

设计思路

地面用草地纹理,重复铺 8×8 次,避免一张图拉伸变形。

关键代码

javascript

ini 复制代码
grassColorTexture.repeat.set(8, 8)
grassColorTexture.wrapS = THREE.RepeatWrapping
grassColorTexture.wrapT = THREE.RepeatWrapping

const floor = new THREE.Mesh(
    new THREE.PlaneGeometry(20, 20),
    new THREE.MeshStandardMaterial({ 
        map: grassColorTexture,
        aoMap: grassARMTexture,
        normalMap: grassNormalTexture
    })
)
floor.rotation.x = -Math.PI * 0.5
floor.position.y = 0
scene.add(floor)

做了什么

  • 一张小草纹理,重复 8×8 次铺满整个地面
  • wrapSwrapT 设置为 RepeatWrapping,让纹理在水平/垂直方向都能重复
  • 配合 aoMapnormalMap 增加细节

六、鬼魂动画

设计思路

三个鬼魂用不同颜色、不同速度、不同半径绕圈飞行。用 sincos 控制水平运动,用多层 sin 叠加控制垂直起伏,产生飘忽不定的效果。

关键代码

javascript

ini 复制代码
const tick = () => {
    const elapsedTime = clock.getElapsedTime()

    // 鬼魂1
    const ghost1Angle = elapsedTime * 0.5
    ghost1.position.x = Math.cos(ghost1Angle) * 4
    ghost1.position.z = Math.sin(ghost1Angle) * 4
    ghost1.position.y = Math.sin(ghost1Angle) * Math.sin(ghost1Angle * 2.34) * Math.sin(ghost1Angle * 3.45)

    // 鬼魂2(速度不同,半径不同)
    const ghost2Angle = -elapsedTime * 0.38
    ghost2.position.x = Math.cos(ghost2Angle) * 5
    ghost2.position.z = Math.sin(ghost2Angle) * 5
    ghost2.position.y = Math.sin(ghost2Angle) * Math.sin(ghost2Angle * 2.34) * Math.sin(ghost2Angle * 3.45)

    // 鬼魂3
    const ghost3Angle = elapsedTime * 0.23
    ghost3.position.x = Math.cos(ghost3Angle) * 6
    ghost3.position.z = Math.sin(ghost3Angle) * 6
    ghost3.position.y = Math.sin(ghost3Angle) * Math.sin(ghost3Angle * 2.34) * Math.sin(ghost3Angle * 3.45)
}

做了什么

  • 鬼魂1:速度 0.5,半径 4,沿顺时针飞行
  • 鬼魂2:速度 0.38(逆时针),半径 5
  • 鬼魂3:速度 0.23,半径 6
  • 垂直运动用三个 sin 相乘,产生不规则的飘忽感

阴影配置

关键代码

javascript

ini 复制代码
// 渲染器
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap

// 光源投射阴影
moonLight.castShadow = true
ghost1.castShadow = true
// ghost2, ghost3 同理

// 物体投射/接收阴影
walls.castShadow = true
walls.receiveShadow = true
floor.receiveShadow = true

for(const grave of graves.children) {
    grave.castShadow = true
    grave.receiveShadow = true
}

// 阴影贴图配置
moonLight.shadow.mapSize.width = 256
moonLight.shadow.mapSize.height = 256
moonLight.shadow.camera.top = 8
moonLight.shadow.camera.right = 8
moonLight.shadow.camera.bottom = -8
moonLight.shadow.camera.left = -8
moonLight.shadow.camera.near = 1
moonLight.shadow.camera.far = 20

做了什么

  • 开启渲染器阴影,选择柔和阴影类型
  • 月光和鬼魂都投射阴影
  • 墙壁投射阴影,地面接收阴影
  • 墓碑逐个设置投射和接收阴影
  • 阴影贴图分辨率设为 256,平衡性能和质量

总结

这个场景把之前学过的知识点都串起来了:

模块 用在哪
几何体 房子、屋顶、门、灌木丛、墓碑
纹理 墙壁砖纹、门的多张贴图、草地
材质 StandardMaterial 配合多张贴图
光源 环境光、平行光(月光)、点光源(门灯、鬼魂)
阴影 月光、鬼魂投射阴影,地面接收
三角函数 墓碑圆形分布、鬼魂飘移动画
场景氛围营造

做完这个场景,你就不再是"学过 Three.js 的各个模块"了,而是"用 Three.js 完整地做过一个项目"。接下来你可以开始做你自己的场景了。

📚 本文是学习 Three.js Journey(Bruno Simon 的付费课程)的学习笔记,代码为个人练习所写,概念讲解融入了个人的理解和比喻。

相关推荐
TingTing16 分钟前
Vue3动态组件库建设
前端
码上暴富1 小时前
Cursor / VS Code 自定义文件颜色
前端·vscode
开开心心就好1 小时前
电子教鞭工具支持画框写字插图片功能齐全
android·开发语言·前端·javascript·人工智能·pdf·html
Dovis(誓平步青云)1 小时前
拍视频前先把镜头想清楚:做一个分镜取景辅助器
android·java·服务器·javascript·人工智能
CarIise2 小时前
下拉菜单HTML/CSS/JS实现
前端·css
2601_962071572 小时前
Java进阶(vue基础)
前端·javascript·vue.js
研☆香2 小时前
数组方法 splice讲解 拓展
开发语言·前端·javascript
码视野2 小时前
基于 Spring Boot + Vue3 的【城市地下燃气管网微泄漏感知与相邻地下空间燃爆预警中台】设计与实现(含PRD/三端高保真源码/大屏)
java·前端·人工智能·spring boot·后端
淡海水2 小时前
05-03-栈队列-PriorityQueue-TElement-TPriority-NET6优先队列语义与四叉堆实现
服务器·前端·c#·priorityqueue·clr·telement