嗨,我是小路。今天主要和大家分享的主题是"vue+three.js 五彩烟花效果封装+加载字体"。
在做烟花的基础的基础上,加上文字字体,这是很多背景版常用的基础极端。如节日庆典。今天主要是准备一个简单的端午节快乐的字体面板。

1.FontLoader、TextGeometry的加载
注意:在three.js中,在加载字体的时候,不能按照库包里面位置进行加载,需要加载成如下:
javascript
import { FontLoader } from "three/addons/loaders/FontLoader.js";
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";
2.字体json的加载
注意:在three.js中,在加载字体的时候,需要将字体转换成json格式;当然也可以用three.js提供的字体库;

3.烟花类的封装以及颜色的改变
注意:在进行方法封装时,可以将类通过export default进行封装,同时传入scene,这样保证整个屏幕在创建烟花之后,能在屏幕上加载出来;同时在材料中,进行颜色设置;去除顶点颜色配置,并设置默认的颜色;

二、实例代码
javascript
<template>
<div class="pageBox">
<div class="leftBox" ref="leftRef"></div>
</div>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue';
import * as THREE from 'three';
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { FontLoader } from "three/addons/loaders/FontLoader.js";
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";
import Firework from "../utils/Firework"
const leftRef = ref();
// 定义相机输出画布的尺寸(单位:像素px)
let width = window.innerWidth; //宽度
let height = window.innerHeight; //高度
// 创建3D场景对象Scene
const scene = new THREE.Scene();
//设置背景色
scene.background = new THREE.Color(0x002244);
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
camera.position.z = 5;
// 创建渲染器对象
const renderer = new THREE.WebGLRenderer();
const createLight = () => {
const pointLight = new THREE.PointLight(0xffffff, 0.8);
pointLight.position.set(10, 10, 10);
scene.add(pointLight);
// 添加光源和背景等(可选)
const ambientLight = new THREE.AmbientLight(0xffffff, 0.8); // 环境光
scene.add(ambientLight);
}
let currentText;//生成的字体模型
const createFont = (text = "端午节快乐",
size = 1,
height = 0.4,//字体的宽度
bevel = false) => {
if (currentText) {
scene.remove(currentText);
}
const fontLoader = new FontLoader();
fontLoader.load('./fonts/FangSong_Regular.json',function(font){
const textGeometry = new TextGeometry(text,{
font: font,
size: size,
height: height,
curveSegments: 32,
bevelEnabled: bevel,
bevelThickness: 0.05,
bevelSize: 0.02,
bevelOffset: 0,
bevelSegments: 8,
});
textGeometry.center();
const material = new THREE.MeshStandardMaterial({
color: 0xffaa00,//设置字体颜色
metalness: 0.3,//金属性贴图
roughness: 0.8,//粗糙程度
});
currentText = new THREE.Mesh(textGeometry, material);
scene.add(currentText);
})
}
const fireworks = [];
/**
* 创建随机位置的烟花
* 在场景的合理范围内随机选择位置
*/
const createRandomFirework = () => {
const x = (Math.random() * 2 - 1) * 30; // x范围:-30到30
const y = (Math.random() * 2 - 1) * 25; // y范围:-25到25
const z = (Math.random() * 2 - 1) * 25; // y范围:-25到25
fireworks.push(new Firework(x, y, z,scene));
}
const renderFirework = ()=>{
// 有25%的概率生成新烟花
if (Math.random() < 0.25) {
createRandomFirework();
}
// 更新所有烟花,移除已经消失的烟花
for (let i = fireworks.length - 1; i >= 0; i--) {
const alive = fireworks[i].update(scene);
if (!alive) {
fireworks[i].dispose(scene);
fireworks.splice(i, 1);
}
}
}
onMounted(() => {
createLight();
initData()
//添加相机空间
const controls = new OrbitControls(camera, renderer.domElement);
// 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
controls.addEventListener('change', function () {
renderer.render(scene, camera); //执行渲染操作
});//监听鼠标、键盘事件
renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)
//将innerHTML置空,避免append重复添加渲染
leftRef.value.innerHTML = ''
leftRef.value.append(renderer.domElement);
})
const initData = () => {
//创建字体
createFont();
render();
}
function render() {
requestAnimationFrame(render);
renderFirework();
renderer.render(scene, camera);
}
</script>
<style scoped lang="less">
.pageBox {
width: 100%;
height: 100vh;
padding: 0;
margin: 0;
display: flex;
justify-content: space-between;
align-items: center;
.rightBox {
width: 100%;
height: 100%;
}
}
</style>
javascript
/**
* 烟花粒子类
* 负责创建和管理单个烟花的所有粒子
*/
import * as THREE from 'three';
/**
* 烟花粒子类
* 负责创建和管理单个烟花的所有粒子
*/
export default class Firework {
constructor(x, y, z,scene) {
// 初始化属性
this.particles = []; // 粒子数组
this.geometry = new THREE.BufferGeometry(); // 粒子几何体
this.count = 10000; // 粒子数量
this.positions = new Float32Array(this.count * 3); // 粒子位置数组
this.velocities = []; // 粒子速度数组
this.colors = new Float32Array(this.count * 3); // 粒子颜色数组
this.sizes = new Float32Array(this.count); // 粒子大小数组
this.life = new Float32Array(this.count); // 粒子生命周期数组
this.scene = scene;
//定义可以渲染的颜色
this.colorArr = [0xfff44ff,0xfff33ff,0xff00fff,0xff34fff,0xff45fff,0xfff33ff,0xfff22ff,0xfffff00,0xfffffff,0xfffff11]
// 初始化每个粒子
for (let i = 0; i < this.count; i++) {
// 使用球面坐标系计算粒子初始方向
const phi = Math.random() * Math.PI * 2; // 水平角度
const theta = Math.random() * Math.PI; // 垂直角度
const velocity = 2 + Math.random() * 2; // 随机速度
// 计算粒子速度向量
this.velocities.push(
velocity * Math.sin(theta) * Math.cos(phi), // x方向速度
velocity * Math.sin(theta) * Math.sin(phi), // y方向速度
velocity * Math.cos(theta) // z方向速度
);
// 设置粒子初始位置
this.positions[i * 3] = x; // x坐标
this.positions[i * 3 + 1] = y; // y坐标
this.positions[i * 3 + 2] = z; // z坐标
// 设置粒子颜色(红色为主,带随机变化)
this.colors[i * 3] = 1.0; // 红色通道
this.colors[i * 3 + 1] = Math.random() * 0.2; // 绿色通道
this.colors[i * 3 + 2] = Math.random() * 0.2; // 蓝色通道
// 初始化粒子大小和生命值
this.sizes[i] = 0.3; // 初始大小
this.life[i] = 1.0; // 初始生命值
}
// 设置几何体属性
this.geometry.setAttribute(
"position",
new THREE.BufferAttribute(this.positions, 3)
);
this.geometry.setAttribute(
"color",
new THREE.BufferAttribute(this.colors, 3)
);
this.geometry.setAttribute(
"size",
new THREE.BufferAttribute(this.sizes, 1)
);
// 创建粒子材质
const material = new THREE.PointsMaterial({
size: 0.3, // 粒子大小
vertexColors: false, // 启用顶点颜色
blending: THREE.AdditiveBlending, // 使用加法混合
transparent: true, // 启用透明
opacity: 0.8, // 设置透明度
color:this.colorArr[Math.floor(Math.random()*10)]//生成随机颜色
});
// 创建粒子系统并添加到场景
this.points = new THREE.Points(this.geometry, material);
this.scene.add(this.points);
}
//生成随机颜色
createColor(){
let num = Math.floor(Math.random()*100)%16
num.toString()
let b = '0x'+(Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0')).toUpperCase();
b = b.toUpperCase()
console.log('color',b);
return '0x'+b;
}
// 更新烟花状态
update() {
let alive = false;
for (let i = 0; i < this.count; i++) {
if (this.life[i] > 0) {
alive = true;
// 根据速度更新位置
this.positions[i * 3] += this.velocities[i * 3] * 0.1;
this.positions[i * 3 + 1] += this.velocities[i * 3 + 1] * 0.1;
this.positions[i * 3 + 2] += this.velocities[i * 3 + 2] * 0.1;
// 添加重力效果
this.velocities[i * 3 + 1] -= 0.05;
// 更新生命值和大小
this.life[i] -= 0.015;
this.sizes[i] = this.life[i] * 0.3;
}
}
// 标记属性需要更新
this.geometry.attributes.position.needsUpdate = true;
this.geometry.attributes.size.needsUpdate = true;
return alive; // 返回是否还有活着的粒子
}
// 清理烟花资源
dispose() {
this.scene.remove(this.points); // 从场景中移除
this.geometry.dispose(); // 释放几何体
this.points.material.dispose(); // 释放材质
}
}
三、总结
宁可十年不将军,不可一日不拱卒。预祝大家端午节快乐!
都看到这里了,记得【点赞】+【关注】哟。