使用 Three.js 创建圣诞树场景
今天带大家一起实现一个圣诞树,先看下效果
一、导入模块并初始化 Three.js 场景
1. 创建场景
创建场景的目的是构建 3D 空间的容器,用于承载各种 3D 对象和光源。
const scene = new THREE.Scene();
2. 创建相机
创建相机用于定义观察视角,我们使用透视相机(PerspectiveCamera
),它能模拟人眼观察场景的效果。
- 参数含义:
75
:视角大小,决定相机的视野范围。window.innerWidth / window.innerHeight
:宽高比,保证画面比例正常。0.1
和1000
:近裁剪面和远裁剪面,控制相机可见范围。
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000
);
3. 创建渲染器
渲染器用于将 3D 场景绘制到屏幕上。我们选择 WebGLRenderer
并启用抗锯齿功能以优化图形效果。
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
4. 将渲染器插入到 HTML 页面
将渲染器生成的 <canvas>
元素添加到页面的 #app
容器中,以便显示 3D 场景。
document.getElementById("app").appendChild(renderer.domElement);
完整代码
import * as THREE from "three";
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000
);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("app").appendChild(renderer.domElement);
二、添加交互控件
为场景添加 OrbitControls 控件,方便用户通过鼠标交互操作场景。
1. 引入 OrbitControls
我们需要从 Three.js 的扩展模块中导入 OrbitControls。
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
2. 初始化控件
OrbitControls 用于添加鼠标交互功能,让用户可以旋转、缩放和拖动场景。
const controls = new OrbitControls(camera, renderer.domElement);
3. 启用惯性效果
为了让交互更加自然,我们启用惯性功能,并设置阻尼系数。
controls.enableDamping = true; // 开启惯性
controls.dampingFactor = 0.05; // 设置惯性阻尼系数
4. 设置初始相机位置
为便于观察场景,设置相机位置为 (0, 5, 10)
,稍微抬高视角。
camera.position.set(0, 5, 10);
controls.update();
三、创建圣诞树
在这一部分,我们将详细讲解如何创建圣诞树,包括树干、树叶以及装饰的实现过程。
1. 创建树干
- 几何体创建:
- 使用
THREE.CylinderGeometry
创建一个圆柱体,表示树干的形状。 - 圆柱体的底部半径为
0.5
,顶部半径为0.3
,高度为2.5
,分段为32
。
- 使用
- 材质设置:
- 使用
THREE.MeshPhongMaterial
,设置颜色为0x8b4513
(棕色)以模拟木材质感。 - 增加光泽效果,设置
shininess
为20
。
- 使用
- 位置调整:
- 设置
position.y = 1.25
,将树干移动到地面以上。
- 设置
const trunkGeometry = new THREE.CylinderGeometry(0.3, 0.5, 2.5, 32);
const trunkMaterial = new THREE.MeshPhongMaterial({color: 0x8b4513,shininess: 20,
});
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
trunk.position.y = 1.25;
treeGroup.add(trunk);
2. 创建树叶
在圣诞树中,树叶部分是通过多个圆锥体逐层堆叠实现的。这种设计方法既简单又直观,每一层的大小逐渐减小,高度逐渐增高,从而形成自然的树形结构。
实现思路
- 层级控制:
- 设置
levels = 5
,生成 5 层树叶,每层逐渐变小且向上移动。
- 设置
- 几何体创建:
- 使用
THREE.ConeGeometry
创建圆锥体,控制半径逐层递减。
- 使用
- 材质设置:
- 使用
THREE.MeshPhongMaterial
设置绿色(0x228b22
),并增加光泽。
- 使用
- 位置调整:
- 每层的高度间隔为
1.1
,确保层次分明且紧凑。
- 每层的高度间隔为
const levels = 5;
for (let i = 0; i < levels; i++) {const radius = 3 - i * 0.5;const height = 1.2;const coneGeometry = new THREE.ConeGeometry(radius, height, 32);const coneMaterial = new THREE.MeshPhongMaterial({color: 0x228b22,shininess: 30,});const cone = new THREE.Mesh(coneGeometry, coneMaterial);cone.position.y = 2.5 + i * 1.1;treeGroup.add(cone);
}
3. 添加装饰
为圣诞树添加装饰,比如彩球:
- 随机生成装饰球:
- 使用
Math.random()
控制彩球的大小、颜色和位置。 - 设置彩球的数量为
35
,大小范围为0.08 ~ 0.2
。
- 使用
- 材质优化:
- 彩球以随机颜色为主,设置 30% 的概率为金色(
0xffd700
)。
- 彩球以随机颜色为主,设置 30% 的概率为金色(
- 位置控制:
- 使用极坐标
Math.cos(angle)
和Math.sin(angle)
计算x
和z
坐标。 - 高度范围限定为
2.5 ~ 6.5
。
- 使用极坐标
const decorationCount = 35;
for (let i = 0; i < decorationCount; i++) {const size = 0.08 + Math.random() * 0.12;const ballGeometry = new THREE.SphereGeometry(size, 32, 32);const ballMaterial = new THREE.MeshPhongMaterial({color: Math.random() < 0.7 ? Math.random() * 0xffffff : 0xffd700,shininess: 100,});const ball = new THREE.Mesh(ballGeometry, ballMaterial);const angle = Math.random() * Math.PI * 2;const radius = Math.random() * 2.2;const height = Math.random() * 4 + 2.5;ball.position.x = Math.cos(angle) * radius;ball.position.y = height;ball.position.z = Math.sin(angle) * radius;treeGroup.add(ball);
}
4. 添加彩带
通过曲线模拟彩带,为圣诞树增加装饰。
-
曲线生成:
- 使用
THREE.CatmullRomCurve3
创建一条随机曲线。 - 曲线由三个随机控制点构成,起始点在树干顶部附近。
- 使用
-
管道几何体:
- 使用
THREE.TubeGeometry
基于曲线生成管道形状,模拟彩带。
- 使用
-
材质设置:
- 彩带材质为随机红色系(
0xff0000
),光泽度高。
- 彩带材质为随机红色系(
const ribbonCount = 15;
for (let i = 0; i < ribbonCount; i++) {const curve = new THREE.CatmullRomCurve3([new THREE.Vector3(0, 2.5 + Math.random() * 3, 0),new THREE.Vector3(Math.random() * 2 - 1,2.5 + Math.random() * 3,Math.random() * 2 - 1),new THREE.Vector3(Math.random() * 3 - 1.5,2.5 + Math.random() * 3,Math.random() * 3 - 1.5),]);const ribbonGeometry = new THREE.TubeGeometry(curve, 20, 0.02, 8, false);const ribbonMaterial = new THREE.MeshPhongMaterial({color: Math.random() * 0xff0000,shininess: 80,});const ribbon = new THREE.Mesh(ribbonGeometry, ribbonMaterial);treeGroup.add(ribbon);
}
5. 添加礼物盒
在树底部放置礼物盒,提升整体氛围。
1. 创建礼物盒的容器:THREE.Group
`const giftGroup = new THREE.Group();`;
- 每个礼物盒由一个
THREE.Group
组成,用来包含盒子和丝带。 - 使用
THREE.Group
可以方便地对礼物盒整体设置位置和旋转角度。
2. 随机生成盒子的尺寸
const size = 0.4 + Math.random() * 0.3; // 宽度和深度:0.4 到 0.7
const height = 0.4 + Math.random() * 0.3; // 高度:0.4 到 0.7
- 每个礼物盒的尺寸在一定范围内随机变化:
size
控制盒子的宽度和深度。height
控制盒子的高度。
- 这样可以避免所有盒子大小一致带来的单调感。
3. 创建盒子的几何形状和材质
const boxGeometry = new THREE.BoxGeometry(size, height, size);
const boxMaterial = new THREE.MeshPhongMaterial({color: Math.random() * 0xffffff, // 随机颜色shininess: 50, // 适中的光泽度
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
- 使用
THREE.BoxGeometry
定义盒子的几何形状。 - 使用
THREE.MeshPhongMaterial
定义盒子的材质:- 颜色是随机生成的 RGB 值。
- 设置光泽度为
50
,使盒子表面具有柔和的反光效果。
4. 生成丝带
水平丝带
const ribbonWidth = size * 0.1; // 丝带宽度是盒子宽度的 10%
const ribbonGeometry = new THREE.BoxGeometry(size, ribbonWidth, ribbonWidth);
const ribbonMaterial = new THREE.MeshPhongMaterial({color: 0xff0000, // 红色丝带shininess: 80, // 更高的光泽度
});
const ribbonH = new THREE.Mesh(ribbonGeometry, ribbonMaterial);
- 水平丝带覆盖在盒子的顶部。
- 宽度和盒子一样,但厚度很薄,确保比例适当。
垂直丝带
const ribbonV = new THREE.Mesh(new THREE.BoxGeometry(ribbonWidth, ribbonWidth, size),ribbonMaterial
);
- 垂直丝带沿着盒子的深度方向包裹盒子。
- 使用与水平丝带相同的材质,保持统一的红色和光泽效果。
将丝带与盒子组合
giftGroup.add(box, ribbonH, ribbonV);
- 使用
giftGroup.add
方法将盒子和两条丝带加入到礼物盒组中,形成完整的礼物盒。
5. 设置礼物盒的位置和旋转
const angle = Math.random() * Math.PI * 2; // 随机角度
const radius = 1.5 + Math.random() * 1; // 距离中心点的半径
giftGroup.position.x = Math.cos(angle) * radius; // 计算 x 坐标
giftGroup.position.y = height / 2; // y 坐标为盒子高度的一半
giftGroup.position.z = Math.sin(angle) * radius; // 计算 z 坐标
giftGroup.rotation.y = Math.random() * Math.PI * 2; // 随机旋转角度
- 位置设置:
- 使用三角函数
Math.cos
和Math.sin
将礼物盒沿树底圆周随机分布。 radius
确定盒子到树中心的距离,使礼物盒分布在树的底部区域。height / 2
确保盒子底部与地面接触。
- 使用三角函数
- 旋转设置:
- 每个礼物盒的朝向通过
rotation.y
随机化,增加随机性。
- 每个礼物盒的朝向通过
6. 将礼物盒添加到圣诞树组
treeGroup.add(giftGroup);
- 使用 treeGroup.add 将每个生成的礼物盒组添加到圣诞树的主组中。
- 这样礼物盒会随着圣诞树一起移动或旋转。
6. 添加星星
为圣诞树顶部添加金色星星,提升节日气氛。
const starGeometry = new THREE.OctahedronGeometry(0.3, 0);
const starMaterial = new THREE.MeshPhongMaterial({color: 0xffd700,shininess: 100,
});
const star = new THREE.Mesh(starGeometry, starMaterial);
star.position.y = 5.5;
treeGroup.add(star);
四、添加动画
我们将实现圣诞树顶部星星的旋转动画,并更新渲染器。
function animate() {requestAnimationFrame(animate);if (christmasTree.children[christmasTree.children.length - 1]) {christmasTree.children[christmasTree.children.length - 1].rotation.y += 0.01;}controls.update();renderer.render(scene, camera);
}
animate();
完整的圣诞树场景即告完成!
代码
github
https://github.com/calmound/threejs-demo/tree/main/shengdanshu
gitee
https://gitee.com/calmound/threejs-demo/tree/main/shengdanshu