- Particles can be used to create stars, smoke, rain, dust, fire, etc.
- You can have thousands of them with a reasonable frame rate(帧率)
- Each particles is composed of plane(two triangles) always facing the camera
- Create particles is like to create Mesh:
- a geometry(BufferGeometry)
- a material(PointsMaterial)
- a Points instance(instead of a Mesh)
-
Set up
// 导入three.js
import * as THREE from 'three'
// 导入轨道控制器
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// scene
const scene = new THREE.Scene()
// camera
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
100
)
camera.position.z = 3
// render
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})
// axesHelper (根据自己的需要选择是否添加坐标轴)
const axesHelper = new THREE.AxesHelper(5)
scene.add(axesHelper)
// controls
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
// animate
const clock = new THREE.Clock()
function animate () {
let elapsedTime = clock.getElapsedTime()
controls.update()
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate()
-
Simple particles(完成后如下图)
/**
* particles
*/
// Geometry
const particlesGeometry = new THREE.SphereGeometry(1, 32, 32) // 球体 - 半径、水平和垂直分段数
// Material
const particlesMaterial = new THREE.PointsMaterial({
size: 0.02, // 粒子大小
sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小
})
// Points
const particles = new THREE.Points(particlesGeometry, particlesMaterial)
scene.add(particles)
-
Custom geometry 自定义几何图形
/**
* particles
*/
// Geometry
const particlesGeometry = new THREE.BufferGeometry()
const count = 500 // bufferGeometry个数
const positions = new Float32Array(count * 3) // 参数表示数组长度,每个bufferGeometry有 x,y,z 3个坐标点
// 填充positions数组
for(let i = 0; i < count * 3; i++) {
positions[i] = (Math.random() - 0.5) * 10 // 事粒子居中并扩大显示范围
}
// 3个值为一组(向缓冲区几何体添加属性)
particlesGeometry.setAttribute(
'position',
new THREE.BufferAttribute(positions, 3)
)
// Material
const particlesMaterial = new THREE.PointsMaterial({
size: 0.02, // 粒子大小
sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小
})
// Points
const particles = new THREE.Points(particlesGeometry, particlesMaterial)
scene.add(particles)
-
Color, Map and Alpha Map
- 添加简单的颜色、texture设置,仔细观察下图会发现前面的parcles遮挡了后面的,并且有明显的边缘
/** * Textures */ const textureLoader = new THREE.TextureLoader() const particleTexture = textureLoader.load('../public/imgs/particles/2.png') ... ... // Material const particlesMaterial = new THREE.PointsMaterial({ color: '#ff88cc', size: 0.1, // 粒子大小 sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小 map: particleTexture, })
- 理想中的效果应当是除圆环以外的部分都是透明不可见的,不会出现前后遮挡的问题,解决这个问题首先:activate the transparency and use the texture on the
alphaMap
instead of themap
,但是修改后还是无法完美解决,遮挡的问题看似解决了,但是particle的边缘有时还依然存在
// Material const particlesMaterial = new THREE.PointsMaterial({ color: '#ff88cc', size: 0.1, // 粒子大小 sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小 transparent: true, alphaMap: particleTexture, // webgl对一个粒子的渲染和创建是同时的,同样黑色部分透明度都为0,就无法分辨前后 })
-
we can still see the edges of the particles, this is because the particles are drawn in the same order as they are created, and WebGl doesn't really know which on is on front of the other
there are multiple ways to fix it, first we can use the alpha test, the
alphaTest
is a value between 0 and 1 that enables the WebGl to know when not to render the pixel according to the pixel's transparency, 有一定的效果,但是particle的边缘依然不能很好的重叠,前后遮挡效果一般
// Material const particlesMaterial = new THREE.PointsMaterial({ color: '#ff88cc', size: 0.1, // 粒子大小 sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小 transparent: true, alphaMap: particleTexture, // webgl对一个粒子的渲染和创建是同时的,同样黑色部分透明度都为0,就无法分辨前后 alphaTest: 0.01, // 依然会有边缘出现 })
- the second we can use depth test, the WebGl tests if what's being drawn is closer than what's already drawn, if it's behind , the particles won't be drawn; but it can create bugs if you have other objects in your scene or particles with different colors
// Material const particlesMaterial = new THREE.PointsMaterial({ color: '#ff88cc', size: 0.1, // 粒子大小 sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小 transparent: true, alphaMap: particleTexture, // webgl对一个粒子的渲染和创建是同时的,同样黑色部分透明度都为0,就无法分辨前后 // alphaTest: 0.01, // 依然会有边缘出现 depthTest: false, // 不适用页面有多个不同颜色物体,会出现穿透效果,测试当前正在渲染的粒子是否在其他粒子前面,behind就不画 }) ... ... // Cube const cube = new THREE.Mesh( new THREE.BoxGeometry(), new THREE.MeshBasicMaterial() ) scene.add(cube)
- the third we can use depth write, the depth of what's being drawn is stored in what we call depth buffer(深度缓冲区), instead of not testing if the particle is closer than what's in this depth buffer, we can tell the WebGl not to write particles in that depth buffer, 我们可以理解为有一个正在绘制的灰度处理区域,在这个区域里同时绘制当前正在绘制的内容
// Material const particlesMaterial = new THREE.PointsMaterial({ color: '#ff88cc', size: 0.1, // 粒子大小 sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小 transparent: true, alphaMap: particleTexture, // webgl对一个粒子的渲染和创建是同时的,同样黑色部分透明度都为0,就无法分辨前后 // alphaTest: 0.01, // 依然会有边缘出现 // depthTest: false, // 不适用页面有多个不同颜色物体,会出现穿透效果,测试当前正在渲染的粒子是否在其他粒子前面,behind就不画 depthWrite: false, // depth buffer存储已渲染的部分 })
- the last solution is Blending, with the blending property, we can tell the WebGl to add the color of the pixel to the color of the pixel already drawn, 在绘制过程中颜色是层层叠加的,所以我们会看到叠加的部分很亮,接近白色;使用
blending
可能会影响性能,因此建议不要同时渲染太多或太大的particles
// Material const particlesMaterial = new THREE.PointsMaterial({ color: '#ff88cc', size: 0.1, // 粒子大小 sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小 transparent: true, alphaMap: particleTexture, // webgl对一个粒子的渲染和创建是同时的,同样黑色部分透明度都为0,就无法分辨前后 // alphaTest: 0.01, // 依然会有边缘出现 // depthTest: false, // 不适用页面有多个不同颜色物体,会出现穿透效果,测试当前正在渲染的粒子是否在其他粒子前面,behind就不画 depthWrite: false, // depth buffer存储已渲染的部分 blending: THREE.AdditiveBlending, // 颜色叠加 })
-
Different Color of each particle,使用顶点着色
/**
* particles
*/
// Geometry
...
const colors = new Float32Array(count * 3) // 颜色是由 r,g,b 组成的
// 填充positions数组
for(let i = 0; i < count * 3; i++) {
...
colors[i] = Math.random()
}
// 3个值为一组(向缓冲区几何体添加属性)
...
particlesGeometry.setAttribute(
'color',
new THREE.BufferAttribute(colors, 3)
)
// Material
const particlesMaterial = new THREE.PointsMaterial({
// color: '#ff88cc',
...
depthWrite: false, // depth buffer存储已渲染的部分
blending: THREE.AdditiveBlending, // 颜色叠加
vertexColors: true, // 顶点颜色
})
-
Animate
- make the particles move up and down, like waves
- use the points as an object, 设置每个particle的动画
- we can update each vertex separately in
particlesGeometry.attributes.position.array
, 这是一个一维数组
const clock = new THREE.Clock()
function animate () {
let elapsedTime = clock.getElapsedTime()
// update particles
for(let i = 0; i < count; i++) { // count表示particles的数量,但是每个particle都有x,y,z3个坐标信息
const i3 = i * 3 // 对应每个particle的x坐标值
const x = particlesGeometry.attributes.position.array[i3]
particlesGeometry.attributes.position.array[i3 + 1] = Math.sin(elapsedTime + x)
}
particlesGeometry.attributes.position.needsUpdate = true // geometry的属性改变时需要重置
controls.update()
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate()