前端three.js ——粒子波浪效果实践
通过将对象添加到场景中,Three.js 的渲染器可以将这些对象渲染为二维图像,从而实现三维场景的可视化。new THREE.WebGLRenderer() 得到的渲染器可以将3D模型渲染至 <canvas>容器中。之后便可直接在其他页面上直接导入使用,另外需要注意导入后的页面元素层级关系,避免某些元素的原有布局被影响。three.js创建的场景对象Scene是一个容器 ,用于组织管理三维空间中的
一、three.js
使用three.js 渲染canvas 实现粒子波浪律动效果
three.js 粒子波浪效果
实现过程:
1、构建粒子系统
首先搭建出一个二维平面(x-z平面),每一个点位均匀铺在这个平面上
const AMOUNTX = 50; //X轴上的点位数量
const AMOUNTY = 50; //Y轴上的点位数量
const SEPARATION = 100; //每个点位间的距离
const numParticles = AMOUNTX * AMOUNTY;
const vertices = new Float32Array(numParticles * 3); //存储每个粒子的三维坐标 每三个下标一组[x,y,z]
const scales = new Float32Array(numParticles); //存储每个粒子的缩放值
//初始化粒子坐标
let i = 0;
for (let ix = 0; ix < AMOUNTX; ix++) {
for (let iy = 0; iy < AMOUNTY; iy++) {
vertices[i] = ix * SEPARATION - (AMOUNTX * SEPARATION) / 2; // x
vertices[i + 1] = 0; // y,初始值为 0,后续动态更新
vertices[i + 2] = iy * SEPARATION - (AMOUNTY * SEPARATION) / 2; // z
i += 3;
}
}
2、创建粒子材质纹理
import * as THREE from 'three';
import g from '@/assets/gradient.png'; //导入纹理材质
const TextureLoader = new THREE.TextureLoader(); //创建纹理加载器
const material = new THREE.PointsMaterial({ //创建点材质
size: 5, //设置大小
sizeAttenuation: false, //粒子大小是否随相机位置衰减
transparent: true, //开启透明
opacity: 1, //透明程度
blending: THREE.AdditiveBlending,
depthWrite: false,
map: TextureLoader.load(g)
});
3、基于上面两步得到的粒子几何位置与材质纹理构建一个几何体对象
//three.js提供的缓冲类 便于处理大规模的几何体
const geometry = new THREE.BufferGeometry();
//给几何体设置点位坐标与每个点位的缩放值
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
geometry.setAttribute('scale', new THREE.BufferAttribute(scales, 1));
//应用点模型
const points = new THREE.Points(geometry, material);
4、创建场景
three.js创建的场景对象Scene是一个容器 ,用于组织管理三维空间中的所有对象
通过将对象添加到场景中,Three.js 的渲染器可以将这些对象渲染为二维图像,从而实现三维场景的可视化
const scene = new THREE.Scene(); //创建场景容器
scene.background = new THREE.Color(0x000000); //设置场景背景
scene.fog = new THREE.FogExp2(328972, 5e-4) //添加烟雾
5、创建相机
three.js提供了两种类型的相机:
①OrthographicCamera 正投影相机
②PerspectiveCamera 透视投影相机 (物体大小随距离发生变化)
PerspectiveCamera 在3D场景渲染中更为常见
相机的可见空间可以视为一个视锥体:
参考文章资料:
掌握视锥体原理:3D图形中的视觉空间与渲染规则-CSDN博客
const camera = new THREE.PerspectiveCamera({
80, //设定相机的fov
window.innerWidth / window.innerHeight, //视口宽高比
1, //近裁剪面
5e4 //远裁剪面
})
camera.position.set(0, 0, 1e3); //相对于场景中心的位置
camera.lookAt(new THREE.Vector3(0,0,0)); //设定相机朝向
6、创建渲染器等参数设置
new THREE.WebGLRenderer() 得到的渲染器可以将3D模型渲染至 <canvas>容器中
详细过程可以参阅three.js相关源码与资料
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.autoClear = false
renderer.setClearColor(scene.fog.color)
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
this.$refs.threeContainer.appendChild(renderer.domElement);
// 添加粒子系统
const particles = GetFlatGeometry();
scene.add(particles);
// 添加环境光
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
// 添加点光源
const pointLight = new THREE.PointLight(0xffffff, 1, 1000);
pointLight.position.set(500, 500, 500);
scene.add(pointLight);
7、添加动画效果
采用递归方法,结合requestAnimationFrame方法,在浏览器绘制下一帧之前更新该几何对象的位置等属性
requestAnimationFrame在EventLoop的什么阶段执行? - 王铁柱6 - 博客园
const animate = () => {
requestAnimationFrame(animate);
// 更新波浪效果
const positions = particles.geometry.attributes.position.array;
for (let i = 0; i < positions.length; i += 3) {
const x = positions[i];
const z = positions[i + 2];
positions[i + 1] = Math.sin((x + time) * 0.5) * 50 + Math.cos((z + time) * 0.5) * 50 -350;
}
particles.geometry.attributes.position.needsUpdate = true;
// 渲染场景
renderer.render(scene, camera);
time += 0.03; // 控制波浪的动态变化速度
};
8、让渲染出的canvas适应浏览器视窗的变化
// 响应窗口大小调整
window.addEventListener("resize", () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
});
我将其封装成了一个vue组件:ParticleSystem.vue
完整代码如下:
//GetFlatGeometry.js
import * as THREE from 'three';
import g from '@/assets/gradient.png';
export default function GetFlatGeometry() {
const AMOUNTX = 70;
const AMOUNTY = 70;
const SEPARATION = 100;
const numParticles = AMOUNTX * AMOUNTY;
const vertices = new Float32Array(numParticles * 3); //存储每个粒子的三维坐标 每三个下标一组[x,y,z]
const scales = new Float32Array(numParticles); //存储每个粒子的缩放值
const TextureLoader = new THREE.TextureLoader();
const material = new THREE.PointsMaterial({
size: 5,
sizeAttenuation: false,
transparent: true,
opacity: 1,
blending: THREE.AdditiveBlending,
depthWrite: false,
map: TextureLoader.load(g)
});
let i = 0;
for (let ix = 0; ix < AMOUNTX; ix++) {
for (let iy = 0; iy < AMOUNTY; iy++) {
vertices[i] = ix * SEPARATION - (AMOUNTX * SEPARATION) / 2; // x
vertices[i + 1] = 0; // y,初始值为 0,后续动态更新
vertices[i + 2] = iy * SEPARATION - (AMOUNTY * SEPARATION) / 2; // z
i += 3;
}
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
geometry.setAttribute('scale', new THREE.BufferAttribute(scales, 1));
const points = new THREE.Points(geometry, material);
return points;
}
//ParticleSystem.vue
<template>
<div ref="threeContainer" class="three-container"></div>
</template>
<script>
import * as THREE from "three";
import GetFlatGeometry from "@/utils/GetFlatGeometry.js";
export default {
mounted() {
this.initThree();
},
methods: {
initThree() {
// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
scene.fog = new THREE.FogExp2(328972, 5e-4)
// 创建相机
const camera = new THREE.PerspectiveCamera(
80,
window.innerWidth / window.innerHeight,
1,
5e4
);
camera.position.set(0, 0, 1e3);
camera.lookAt(new THREE.Vector3(0,0,0)); // 固定相机朝向
// const axesHelper = new THREE.AxesHelper(500)
// scene.add(axesHelper)
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.autoClear = false
renderer.setClearColor(scene.fog.color)
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
this.$refs.threeContainer.appendChild(renderer.domElement);
// 添加粒子系统
const particles = GetFlatGeometry();
scene.add(particles);
// 添加环境光
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
// 添加点光源
const pointLight = new THREE.PointLight(0xffffff, 1, 1000);
pointLight.position.set(500, 500, 500);
scene.add(pointLight);
// 动画循环
let time = 0;
const animate = () => {
requestAnimationFrame(animate);
// 更新波浪效果
const positions = particles.geometry.attributes.position.array;
for (let i = 0; i < positions.length; i += 3) {
const x = positions[i];
const z = positions[i + 2];
positions[i + 1] = Math.sin((x + time) * 0.5) * 50 + Math.cos((z + time) * 0.5) * 50 -350;
}
particles.geometry.attributes.position.needsUpdate = true;
// 渲染场景
renderer.render(scene, camera);
time += 0.03; // 控制波浪的动态变化速度
};
animate();
// 响应窗口大小调整
window.addEventListener("resize", () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
});
},
},
};
</script>
<style scoped>
.three-container {
width: 100%;
height: 100vh;
}
canvas{
width: 100%;
height: 100%;
}
</style>
之后便可直接在其他页面上直接导入使用,另外需要注意导入后的页面元素层级关系,避免某些元素的原有布局被影响
更多推荐
所有评论(0)