Renderers
The rendering system in three.quarks is designed to efficiently display large numbers of particles with minimal performance impact. The primary renderer in three.quarks is the BatchedRenderer
, which optimizes rendering by batching similar particle systems together to reduce draw calls.
Overview
Efficient rendering of particle systems is critical for performance, especially in applications with many particles. The three.quarks rendering system provides:
- Instanced Rendering: Uses WebGL instancing to render many particles with a single draw call
- Batching: Groups similar particle systems together to minimize state changes
- Various Render Modes: Supports billboards, trails, meshes, and stretched billboards
- Optimization: Automatically handles visibility, culling, and buffer management
BatchedRenderer
The BatchedRenderer
is the central rendering component in three.quarks. It’s a node in the three.js scene graph that manages the rendering of multiple particle systems.
Creating a BatchedRenderer
// Create a scene
const scene = new Scene();
// Create a batched renderer
const batchedRenderer = new BatchedRenderer();
// Add the renderer to the scene
scene.add(batchedRenderer);
Adding Particle Systems
Once you’ve created a batched renderer, you can add particle systems to it:
// Create a particle system
const particleSystem = new ParticleSystem({
// Particle system configuration...
});
// Add the particle system's emitter to the scene
scene.add(particleSystem.emitter);
// Add the particle system to the batched renderer
batchedRenderer.addSystem(particleSystem);
Updating the Renderer
In your animation loop, you need to update the batched renderer to simulate and render the particles:
function animate(time) {
requestAnimationFrame(animate);
// Calculate delta time (in seconds)
const deltaTime = (time - lastTime) / 1000;
lastTime = time;
// Update the batched renderer
batchedRenderer.update(deltaTime);
// Render the scene
renderer.render(scene, camera);
}
let lastTime = 0;
animate(0);
Render Modes
three.quarks supports several rendering modes for particles, specified through the renderMode
property of the ParticleSystem
:
Mode | Value | Description |
---|---|---|
BillBoard | RenderMode.BillBoard | Quads that always face the camera |
StretchedBillBoard | RenderMode.StretchedBillBoard | Quads that face the camera but stretch in the direction of velocity |
Mesh | RenderMode.Mesh | Instanced meshes with custom geometry |
Trail | RenderMode.Trail | Particles that leave trails behind them |
HorizontalBillBoard | RenderMode.HorizontalBillBoard | Quads that face the camera but maintain a horizontal orientation |
VerticalBillBoard | RenderMode.VerticalBillBoard | Quads that face the camera but maintain a vertical orientation |
Billboard Mode
This is the default and most common mode. Particles are rendered as quads that always face the camera:
const particleSystem = new ParticleSystem({
// ...other settings
renderMode: RenderMode.BillBoard,
material: new MeshBasicMaterial({
map: particleTexture,
transparent: true,
blending: AdditiveBlending,
}),
});
Stretched Billboard Mode
Useful for effects with directional motion like rain, sparks, or bullets. Particles face the camera but stretch in the direction of movement:
const particleSystem = new ParticleSystem({
// ...other settings
renderMode: RenderMode.StretchedBillBoard,
rendererEmitterSettings: {
speedFactor: 0.1, // How much velocity affects stretch
lengthFactor: 1, // Base length factor
},
material: new MeshBasicMaterial({
map: streakTexture,
transparent: true,
blending: AdditiveBlending,
}),
});
Mesh Mode
Renders particles using custom 3D geometry instead of quads. Useful for non-billboard effects like debris or complex shapes:
// Create a custom geometry
const particleGeometry = new TorusKnotGeometry(0.2, 0.1, 32, 8);
const particleSystem = new ParticleSystem({
// ...other settings
renderMode: RenderMode.Mesh,
instancingGeometry: particleGeometry,
material: new MeshStandardMaterial({
color: 0x00ffff,
roughness: 0.5,
metalness: 0.8,
}),
});
Trail Mode
Creates particles that leave trails behind them, good for effects like smoke trails, magic spells, or light streaks:
const particleSystem = new ParticleSystem({
// ...other settings
renderMode: RenderMode.Trail,
rendererEmitterSettings: {
startLength: new ConstantValue(30), // Length of the trail
followLocalOrigin: false, // Whether trail follows emitter
},
material: new MeshBasicMaterial({
map: trailTexture,
transparent: true,
blending: AdditiveBlending,
}),
behaviors: [
// Control width along the trail
new WidthOverLength(new PiecewiseBezier([[new Bezier(1, 0.8, 0.4, 0), 0]])),
],
});
SpriteBatch and TrailBatch
Internally, the BatchedRenderer
uses two types of batches:
- SpriteBatch: Handles billboard, stretched billboard, and mesh particles
- TrailBatch: Handles trail particles
These are managed automatically by the BatchedRenderer
based on your particle systems’ render modes.
Material Setup
Materials are crucial for the appearance of your particles. three.quarks works with standard three.js materials but applies special shaders to make them work with the particle system:
// Basic material for additive blending (glowing particles)
const additiveMaterial = new MeshBasicMaterial({
map: particleTexture,
transparent: true,
blending: AdditiveBlending,
depthWrite: false, // Important for transparent particles
});
// Standard material for lit particles
const litMaterial = new MeshStandardMaterial({
map: particleTexture,
transparent: true,
blending: NormalBlending,
roughness: 0.5,
metalness: 0.2,
});
Important Material Properties
Property | Description |
---|---|
transparent | Set to true for particles with transparency |
blending | Controls how particles blend with the background |
depthWrite | Set to false for additive transparent particles |
side | Use DoubleSide if you need particles to be visible from behind |
map | Texture used for the particles |
Sprite Sheets
Sprite sheets allow you to use multiple frames in a single texture. This can be used for animated particles or to add variety:
const particleSystem = new ParticleSystem({
// ...other settings
uTileCount: 4, // 4 columns in the sprite sheet
vTileCount: 4, // 4 rows in the sprite sheet
startTileIndex: new IntervalValue(0, 15), // Random initial tile
blendTiles: true, // Blend between tiles for smoother animation
material: new MeshBasicMaterial({
map: spriteSheetTexture,
transparent: true,
}),
behaviors: [
// Animate through the sprite sheet over time
new FrameOverLife(new PiecewiseBezier([[new Bezier(0, 5, 10, 15), 0]])),
],
});
Soft Particles
Soft particles fade out when they intersect with scene geometry, creating a more natural look. To use soft particles:
- Set up a depth texture in your renderer:
// Enable depth texture on the WebGL renderer
webGLRenderer.shadowMap.enabled = true;
const depthTexture = new DepthTexture();
const renderTarget = new WebGLRenderTarget(
window.innerWidth,
window.innerHeight,
{
depthTexture: depthTexture,
depthBuffer: true,
},
);
- Configure your particle system to use soft particles:
const particleSystem = new ParticleSystem({
// ...other settings
softParticles: true,
softNearFade: 0.1, // Start fading at this distance from surfaces
softFarFade: 1.0, // Complete fade at this distance from surfaces
material: new MeshBasicMaterial({
map: particleTexture,
transparent: true,
}),
});
- Set the depth texture on the batched renderer:
batchedRenderer.setDepthTexture(depthTexture);
Performance Optimization
Batching Similar Systems
For optimal performance, batch similar particle systems together:
// Create multiple similar particle systems
const systems = [];
for (let i = 0; i < 10; i++) {
const system = new ParticleSystem({
// Use the same material and render mode for efficient batching
material: sharedMaterial,
renderMode: RenderMode.BillBoard,
// Other settings can vary
// ...
});
// Position the emitters differently
system.emitter.position.set(
Math.random() * 10 - 5,
Math.random() * 10 - 5,
Math.random() * 10 - 5,
);
systems.push(system);
scene.add(system.emitter);
batchedRenderer.addSystem(system);
}
Managing Draw Calls
The BatchedRenderer automatically groups particle systems based on:
- Material (texture, blending mode, etc.)
- Render mode
- Instancing geometry
- Layer mask
To minimize draw calls:
- Share materials between particle systems when possible
- Use the same render mode for similar effects
- Use sprite sheets instead of multiple materials
- Group particles by layer when appropriate
Memory Management
When a particle system is no longer needed, dispose of it properly:
// Remove the system from the batched renderer
batchedRenderer.deleteSystem(particleSystem);
// Dispose the particle system
particleSystem.dispose();
Example: Complete Rendering Setup
Here’s a complete example showing a full rendering setup with multiple particle systems:
import * as THREE from "three";
import * as QUARKS from "three.quarks";
// Create scene, camera, renderer
const scene = new Scene();
const camera = new PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000,
);
camera.position.z = 10;
const renderer = new WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Create batched renderer
const batchedRenderer = new BatchedRenderer();
scene.add(batchedRenderer);
// Load textures
const textureLoader = new TextureLoader();
const particleTexture = textureLoader.load("particle.png");
const trailTexture = textureLoader.load("trail.png");
// Create materials
const additiveMaterial = new MeshBasicMaterial({
map: particleTexture,
transparent: true,
blending: AdditiveBlending,
depthWrite: false,
});
const trailMaterial = new MeshBasicMaterial({
map: trailTexture,
transparent: true,
blending: AdditiveBlending,
depthWrite: false,
});
// Create fire particle system
const fireSystem = new ParticleSystem({
duration: 5,
looping: true,
shape: new ConeEmitter({
radius: 0.5,
angle: Math.PI / 8,
}),
startLife: new IntervalValue(1, 2),
startSpeed: new IntervalValue(2, 4),
startSize: new IntervalValue(0.8, 1.2),
startColor: new ConstantColor(new Vector4(1, 0.5, 0.1, 1)),
worldSpace: true,
material: additiveMaterial,
renderMode: RenderMode.BillBoard,
// Behaviors...
});
// Create smoke trail system
const trailSystem = new ParticleSystem({
duration: 3,
looping: true,
shape: new PointEmitter(),
startLife: new ConstantValue(2),
startSpeed: new ConstantValue(5),
startSize: new ConstantValue(0.5),
startColor: new ConstantColor(new Vector4(0.7, 0.7, 0.7, 0.5)),
worldSpace: true,
material: trailMaterial,
renderMode: RenderMode.Trail,
rendererEmitterSettings: {
startLength: new ConstantValue(20),
followLocalOrigin: false,
},
// Behaviors...
});
// Position the systems
fireSystem.emitter.position.set(0, -2, 0);
trailSystem.emitter.position.set(0, 2, 0);
// Add to scene
scene.add(fireSystem.emitter);
scene.add(trailSystem.emitter);
// Add to batched renderer
batchedRenderer.addSystem(fireSystem);
batchedRenderer.addSystem(trailSystem);
// Animation loop
let lastTime = 0;
function animate(time) {
requestAnimationFrame(animate);
// Calculate delta time (in seconds)
const deltaTime = Math.min((time - lastTime) / 1000, 0.1); // Cap at 0.1s
lastTime = time;
// Update the batched renderer
batchedRenderer.update(deltaTime);
// Render the scene
renderer.render(scene, camera);
}
animate(0);
Troubleshooting
Common Issues
-
Particles not visible:
- Check if the material is properly set up with transparent: true
- Verify the emitter is added to the scene
- Confirm the particle system is added to the batched renderer
- Check if the layer mask allows visibility
-
Low Performance:
- Reduce the number of particles
- Use shared materials
- Verify batching is working by checking the number of draw calls
- Reduce the complexity of behaviors
-
Visual Artifacts:
- For transparent particles, set depthWrite: false
- Try enabling soft particles for intersections with geometry
- For overlapping transparent particles, adjust the render order
-
Z-Fighting:
- Use the renderOrder parameter to control rendering sequence
- Adjust the particle size and position slightly
Next Steps
- Learn about Particle System Configuration for more options
- Explore Behaviors to control how particles evolve over time
- See different Emitter Shapes to control where particles spawn
- Study Performance Optimization for large-scale effects