A cinematic particle background can make or break a portfolio site. But render 2000 particles on a mobile device and you'll tank the frame rate. Here's how I got smooth 60fps everywhere.
Custom Shaders Over Built-in Materials
The first rule: never use PointsMaterial for serious particle work. It's a one-size-fits-all solution that can't optimize for your specific use case. Custom ShaderMaterial gives you full control over the GPU pipeline.
const material = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0 },
uPixelRatio: { value: Math.min(window.devicePixelRatio, 2) }
},
vertexShader: `
attribute float size;
varying float vAlpha;
void main() {
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = size * uPixelRatio * (80.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
varying float vAlpha;
void main() {
vec2 center = gl_PointCoord - vec2(0.5);
if (length(center) > 0.5) discard;
gl_FragColor = vec4(1.0, 0.8, 0.4, vAlpha);
}
`,
transparent: true,
depthWrite: false,
blending: THREE.AdditiveBlending
});
Cap the Pixel Ratio
Modern phones have 3x pixel density. Rendering at native resolution means 3× the pixels to fill. Always cap devicePixelRatio at 2:
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
The visual difference between 2x and 3x is negligible for particles, but the performance difference is massive.
Additive Blending
Use THREE.AdditiveBlending for particles. It creates a beautiful glow effect where particles overlap, and it's cheaper than alpha blending because the GPU can skip depth testing entirely.
Distance-Based Alpha
Fade particles based on their distance from the camera. This creates depth and reduces the visual noise of far-away particles:
vAlpha = smoothstep(150.0, 20.0, dist) * 0.6;
BufferGeometry, Not Geometry
Always use BufferGeometry with Float32Array for particle positions. The old Geometry class is deprecated and significantly slower.
The Result
2000 particles with custom shaders, running at 60fps on an iPhone 11. The secret isn't fewer particles — it's smarter rendering.
Performance isn't about doing less. It's about doing the right things efficiently.