Render Matter.js Bodies with PixiJS for WebGL
Combining Matter.js with PixiJS allows developers to separate 2D
rigid-body physics calculations from hardware-accelerated WebGL
rendering. While Matter.js includes a built-in HTML5 Canvas renderer, it
is meant primarily for prototyping and debugging rather than production
performance. By bypassing Matter's default canvas renderer and using
PixiJS's Application and Ticker to visually
mirror physics bodies, you achieve smooth, high-framerate rendering even
with hundreds of colliding objects on screen.
Architecture Overview
To render Matter.js bodies using PixiJS:
- Initialize the Matter.js physics engine (
EngineandWorld) without instantiatingMatter.Render. - Initialize the PixiJS
Applicationconfigured for WebGL. - Create pairs of Matter.js bodies and PixiJS display objects
(
PIXI.SpriteorPIXI.Graphics). - Drive the physics simulation inside the PixiJS render loop and synchronize the transforms (position and rotation) from the physics bodies to the visual display objects.
Step 1: Initialize Matter.js and PixiJS
Set up both engines independently. Avoid calling
Matter.Runner.run() directly if you want strict frame
synchronization; instead, advance the physics step manually inside
Pixi's ticker.
import * as PIXI from 'pixi.js';
import Matter from 'matter-js';
// 1. Initialize PixiJS
const app = new PIXI.Application({
width: 800,
height: 600,
backgroundColor: 0x1099bb,
preference: 'webgl'
});
document.body.appendChild(app.view);
// 2. Initialize Matter.js Engine
const engine = Matter.Engine.create();
const world = engine.world;Step 2: Create Linked Physics and Visual Objects
When creating physics bodies, generate a corresponding PixiJS display object. Store a reference connecting the two entities so their properties can be updated during the loop.
Important: Matter.js sets the origin of bodies to
their center of mass. For rectangles and regular shapes, ensure the
PixiJS Sprite's anchor is set to (0.5, 0.5) so rotations
occur around the exact center.
// Array to keep track of paired objects
const physicsVisualPairs = [];
function createBox(x, y, width, height) {
// Matter.js rigid body
const body = Matter.Bodies.rectangle(x, y, width, height, {
restitution: 0.8
});
Matter.Composite.add(world, body);
// PixiJS visual representation
const graphics = new PIXI.Graphics();
graphics.beginFill(0xffffff);
graphics.drawRect(-width / 2, -height / 2, width, height);
graphics.endFill();
graphics.x = x;
graphics.y = y;
app.stage.addChild(graphics);
// Store pair
physicsVisualPairs.push({ body, view: graphics });
}
// Add a ground plane and dynamic boxes
const ground = Matter.Bodies.rectangle(400, 590, 800, 20, { isStatic: true });
Matter.Composite.add(world, ground);
const groundGraphics = new PIXI.Graphics();
groundGraphics.beginFill(0x27ae60);
groundGraphics.drawRect(-400, -10, 800, 20);
groundGraphics.endFill();
groundGraphics.x = 400;
groundGraphics.y = 590;
app.stage.addChild(groundGraphics);
for (let i = 0; i < 50; i++) {
createBox(200 + Math.random() * 400, 50 + Math.random() * 200, 30, 30);
}Step 3: Implement the Synchronization Loop
Use app.ticker to bind the physics step and render step
together. Update Matter's engine, then copy the physics body's
position.x, position.y, and angle
to the PixiJS object's x, y, and
rotation.
app.ticker.add((delta) => {
// Advance physics based on the frame delta time (in milliseconds)
// delta is normalized in PixiJS (1 = ~16.66ms at 60fps)
const deltaMs = delta * (1000 / 60);
Matter.Engine.update(engine, deltaMs);
// Synchronize transforms
for (let i = 0; i < physicsVisualPairs.length; i++) {
const { body, view } = physicsVisualPairs[i];
view.x = body.position.x;
view.y = body.position.y;
view.rotation = body.angle;
}
});Optimization Techniques for Maximum Performance
- Use Sprites with a Shared Texture: Repeatedly
redrawing complex vector shapes via
PIXI.Graphicscauses draw calls and CPU overhead. Instead, render geometric shapes once to aPIXI.RenderTextureor load spritesheet textures, allowing WebGL to batch hundreds of sprites into a single draw call. - Sprite Anchoring: When using
PIXI.Sprite, setsprite.anchor.set(0.5)so that the visual anchor matches Matter's default center-point coordinate system. - Compound Bodies: For complex shapes made of
multiple convex parts, draw the visual asset to match the combined
bounds and assign it to the root parent body
(
body.parent). - Object Pooling: Avoid frequent creation and garbage
collection of bodies and display objects. Deactivate off-screen or dead
objects by removing them from
worldand settingview.visible = falseuntil reused.