Skip to Content
Quarks

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:

  1. Instanced Rendering: Uses WebGL instancing to render many particles with a single draw call
  2. Batching: Groups similar particle systems together to minimize state changes
  3. Various Render Modes: Supports billboards, trails, meshes, and stretched billboards
  4. 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:

ModeValueDescription
BillBoardRenderMode.BillBoardQuads that always face the camera
StretchedBillBoardRenderMode.StretchedBillBoardQuads that face the camera but stretch in the direction of velocity
MeshRenderMode.MeshInstanced meshes with custom geometry
TrailRenderMode.TrailParticles that leave trails behind them
HorizontalBillBoardRenderMode.HorizontalBillBoardQuads that face the camera but maintain a horizontal orientation
VerticalBillBoardRenderMode.VerticalBillBoardQuads 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:

  1. SpriteBatch: Handles billboard, stretched billboard, and mesh particles
  2. 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

PropertyDescription
transparentSet to true for particles with transparency
blendingControls how particles blend with the background
depthWriteSet to false for additive transparent particles
sideUse DoubleSide if you need particles to be visible from behind
mapTexture 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:

  1. 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, }, );
  1. 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, }), });
  1. 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:

  1. Share materials between particle systems when possible
  2. Use the same render mode for similar effects
  3. Use sprite sheets instead of multiple materials
  4. 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

  1. 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
  2. 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
  3. Visual Artifacts:

    • For transparent particles, set depthWrite: false
    • Try enabling soft particles for intersections with geometry
    • For overlapping transparent particles, adjust the render order
  4. Z-Fighting:

    • Use the renderOrder parameter to control rendering sequence
    • Adjust the particle size and position slightly

Next Steps

Last updated on