使用threeJs + dat.GUI实现一个旋转星空的效果,效果如下:
完整代码可以去
文章末尾
直接拿去使用
大概步骤
通过本文的学习, 你将会收获:
这里直接写在html 里面, 引入了CDN加载. 如果在vue or react等中使用,可使用包管理器进行依赖的下载.
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <!-- 引入Three.js库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script> <!-- 引入dat.GUI库 -->
scene
。camera
,设置视角、宽高比、近裁剪面和远裁剪面。renderer
,设置渲染器的尺寸,并将其添加到文档的 body
中。 // 初始化场景、相机、渲染器
const scene = new THREE.Scene(); // 创建一个新的Three.js场景
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); // 创建透视相机
const renderer = new THREE.WebGLRenderer(); // 创建WebGL渲染器
renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染器的大小为窗口的内宽和内高
document.body.appendChild(renderer.domElement); // 将渲染器的canvas元素添加到HTML文档中
starSettings
,包括颜色、大小和数量以及重置方法. // dat.GUI配置
const starSettings = {
color: 0xffffff, // 初始星星颜色为白色
size: 1, // 初始星星大小
count: 1000, // 初始星星数量
// 重置函数
reset: function () {
this.color = 0xffffff; // 重置颜色为白色
this.size = 1; // 重置大小为1
this.count = 1000; // 重置数量为1000
updateStars(); // 更新星星 // 同步 GUI 控件的值
guiControllers.color.setValue(this.color);
guiControllers.size.setValue(this.size);
guiControllers.count.setValue(this.count);
}
};
const gui = new dat.GUI(); // 创建dat.GUI对象
// 创建 GUI 控件并保存引用以便后续更新
const guiControllers = {
color: gui.addColor(starSettings, 'color').name('颜色').onChange(updateStars), // 添加颜色控制
size: gui.add(starSettings, 'size', 0.1, 10).name('大小').onChange(updateStars), // 添加大小控制
count: gui.add(starSettings, 'count', 100, 10000).name('数量').onChange(updateStars), // 添加数量控制
reset: gui.add(starSettings, 'reset').name('重置') // 添加重置按钮
};
这里使用到的GUI的方法说明:
1. add
add(object, property, [min], [max], [step])
创建一个新的控件,并将其添加到 GUI 中。
object
:包含要控制属性的对象。property
:要控制的属性。min
:属性的最小值(可选)。max
:属性的最大值(可选)。step
:属性的步长(可选)。GUIController
对象。2. addColor
addColor(object, property)
创建一个颜色选择控件,并将其添加到 GUI 中。
object
:包含要控制属性的对象。property
:要控制的属性。GUIController
对象。createStars
函数来创建星星。
geometry
和一个空的顶点数组 vertices
。
starSettings.count
循环生成随机的 x
、y
、z
坐标,并将它们添加到 vertices
数组中。
THREE.Float32BufferAttribute
将顶点数组添加到几何体中。
material
,并结合几何体和材质创建一个 THREE.Points
对象 stars
。
function createStars() {
const geometry = new THREE.BufferGeometry(); // 创建几何体
const vertices = []; // 用于存储星星位置的数组
for (let i = 0; i < starSettings.count; i++) { // 根据星星数量生成顶点
const x = THREE.MathUtils.randFloatSpread(2000); // 随机生成x坐标
const y = THREE.MathUtils.randFloatSpread(2000); // 随机生成y坐标
const z = THREE.MathUtils.randFloatSpread(2000); // 随机生成z坐标
vertices.push(x, y, z); // 将生成的顶点添加到数组中
}
console.log(vertices); // 包含3000 个 随机顶点值的数组
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); // 将顶点添加到几何体中
console.log(geometry.getAttribute('position').count);
const material = new THREE.PointsMaterial({ color: starSettings.color, size: starSettings.size }); // 创建星星材质
const stars = new THREE.Points(geometry, material); // 创建星星物体
return stars; // 返回创建的星星
}
let stars = createStars(); // 调用createStars函数创建星星
scene.add(stars); // 将星星添加到场景中
额外说明:
调用
createStars
方法后,返回的对象stars
是一个包含 1000 个星星的THREE.Points
对象。每个星星的位置由顶点数组中的坐标决定。
具体来说,createStars
方法中:
THREE.BufferGeometry
对象 geometry
。vertices
数组,每三个元素(x, y, z)表示一个星星的位置。由于 starSettings.count
是 1000,所以会有 1000 个星星,每个星星用 3 个坐标值表示,共计 3000 个值。vertices
数组设置为 geometry
对象的 position
属性。THREE.PointsMaterial
对象 material
,用于定义星星的材质。geometry
和 material
创建一个 THREE.Points
对象 stars
,该对象包含了所有的星星。返回的 stars
对象中包含 1000 个星星,每个星星的位置由顶点数组定义。因此,尽管 createStars
方法返回的是一个对象,但这个对象实际上表示了 1000 个星星的位置和材质。
总体来说:
vertices
数组中包含 3000 个值,每三个值表示一个星星的 x, y, z 坐标。geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3))
将这些坐标作为星星的位置属性添加到几何体中。new THREE.Points(geometry, material)
创建了一个包含所有星星的 THREE.Points
对象。stars
对象表示 1000 个星星。因此,返回的 stars
对象是一个包含 1000 个星星的集合。
animate
函数,用于执行动画循环。requestAnimationFrame
调用 animate
函数,确保动画持续进行。 // 动画循环
function animate() {
requestAnimationFrame(animate); // 请求下一帧动画
stars.rotation.x += 0.001; // 旋转星星
stars.rotation.y += 0.002; // 旋转星星
renderer.render(scene, camera); // 渲染场景
}
animate(); // 开始动画
额外说明
requestAnimationFrame 方法可以查看这个链接 developer.mozilla.org/zh-CN/docs/…
当我们调制控件某个值的大小的就会触发页面的重更新.
updateStars
函数,当用户通过 dat.GUI 修改设置时,更新星星。 function updateStars() {
scene.remove(stars); // 从场景中移除旧的星星
stars = createStars(); // 创建新的星星
scene.add(stars); // 将新的星星添加到场景中
}
执行流程
修改值 ==> 修改starSettings
中的值 ==> 触发updateStars
的函数执行 ==> 删除场景 ==> 重新读取starSettings
参数并创建对象 ==> 放入场景中
添加窗口调整事件监听器,当窗口大小变化时,更新相机的宽高比和渲染器的尺寸。
// 窗口大小调整
window.addEventListener('resize', () => { // 监听窗口大小变化事件
camera.aspect = window.innerWidth / window.innerHeight; // 更新相机的宽高比
camera.updateProjectionMatrix(); // 更新相机投影矩阵
renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器大小
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js 星空</title>
<style>
body {
margin: 0;
}
/* 去掉页面的默认边距 */
canvas {
display: block;
}
/* 将canvas设置为块级元素,去掉默认内边距 */
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <!-- 引入Three.js库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script> <!-- 引入dat.GUI库 -->
<script>
// 初始化场景、相机、渲染器
const scene = new THREE.Scene(); // 创建一个新的Three.js场景
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); // 创建透视相机
const renderer = new THREE.WebGLRenderer(); // 创建WebGL渲染器
renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染器的大小为窗口的内宽和内高
document.body.appendChild(renderer.domElement); // 将渲染器的canvas元素添加到HTML文档中
// dat.GUI配置
const starSettings = {
color: 0xffffff, // 初始星星颜色为白色
size: 1, // 初始星星大小
count: 1000, // 初始星星数量
rotationSpeedX: 0.001, // 初始星星x轴旋转速度
rotationSpeedY: 0.002, // 初始星星y轴旋转速度
// 重置函数
reset: function () {
this.color = 0xffffff; // 重置颜色为白色
this.size = 1; // 重置大小为1
this.count = 1000; // 重置数量为1000
// 旋转速度
this.rotationSpeedY = 0.002;
this.rotationSpeedX = 0.001;
updateStars(); // 更新星星 // 同步 GUI 控件的值
guiControllers.color.setValue(this.color);
guiControllers.size.setValue(this.size);
guiControllers.count.setValue(this.count);
guiControllers.rotationSpeedX.setValue(this.rotationSpeedX);
guiControllers.rotationSpeedY.setValue(this.rotationSpeedY);
}
};
function createStars() {
const geometry = new THREE.BufferGeometry(); // 创建几何体
const vertices = []; // 用于存储星星位置的数组
for (let i = 0; i < starSettings.count; i++) { // 根据星星数量生成顶点
const x = THREE.MathUtils.randFloatSpread(2000); // 随机生成x坐标
const y = THREE.MathUtils.randFloatSpread(2000); // 随机生成y坐标
const z = THREE.MathUtils.randFloatSpread(2000); // 随机生成z坐标
vertices.push(x, y, z); // 将生成的顶点添加到数组中
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); // 将顶点添加到几何体中
const material = new THREE.PointsMaterial({ color: starSettings.color, size: starSettings.size }); // 创建星星材质
const stars = new THREE.Points(geometry, material); // 创建星星物体
return stars; // 返回创建的星星
}
let stars = createStars(); // 调用createStars函数创建星星
scene.add(stars); // 将星星添加到场景中
camera.position.z = 5; // 设置相机位置
// 动画循环
function animate() {
requestAnimationFrame(animate); // 请求下一帧动画
stars.rotation.x += starSettings.rotationSpeedX; // 旋转星星
stars.rotation.y += starSettings.rotationSpeedY; // 旋转星星
renderer.render(scene, camera); // 渲染场景
}
animate(); // 开始动画
const gui = new dat.GUI(); // 创建dat.GUI对象
// 创建 GUI 控件并保存引用以便后续更新
const guiControllers = {
color: gui.addColor(starSettings, 'color').name('颜色').onChange(updateStars), // 添加颜色控制
size: gui.add(starSettings, 'size', 0.1, 10).name('大小').onChange(updateStars), // 添加大小控制
count: gui.add(starSettings, 'count', 100, 10000).name('数量').onChange(updateStars), // 添加数量控制
// 添加星星的旋转速度
rotationSpeedX: gui.add(starSettings, 'rotationSpeedX', 0.001, 0.1, 0.001).name('旋转速度X').onChange(updateStars),
rotationSpeedY: gui.add(starSettings, 'rotationSpeedY', 0.001, 0.1, 0.001).name('旋转速度Y').onChange(updateStars),
reset: gui.add(starSettings, 'reset').name('重置') // 添加重置按钮
};
function updateStars() {
scene.remove(stars); // 从场景中移除旧的星星
stars = createStars(); // 创建新的星星
scene.add(stars); // 将新的星星添加到场景中
}
// 窗口大小调整
window.addEventListener('resize', () => { // 监听窗口大小变化事件
camera.aspect = window.innerWidth / window.innerHeight; // 更新相机的宽高比
camera.updateProjectionMatrix(); // 更新相机投影矩阵
renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器大小
});
</script>
</body>
</html>