The Render Loop
The render loop is the heartbeat of any Babylon.js application, responsible for continuously updating and rendering the scene at an optimal frame rate. Understanding how to properly implement and manage the render loop is essential for smooth performance.
The simplest render loop implementation uses the Engine's runRenderLoop method:
engine.runRenderLoop(() => {
scene.render();
});
This creates a continuous loop that renders the scene on every frame, automatically synchronized with the browser's refresh rate (typically 60fps) using requestAnimationFrame behind the scenes.
The render loop follows the game loop pattern common in game development, consisting of three primary phases:
- Update: Process logic, animations, physics, and other state changes
- Render: Draw the current state of the scene to the canvas
- Wait: Synchronize with the display refresh rate
Babylon.js handles this pattern automatically but allows customization at each stage.
You can add custom logic to the render loop for animations, game mechanics, or other time-based updates:
// Track time for smooth animations
let previousTime = 0;
engine.runRenderLoop(() => {
const currentTime = performance.now();
const deltaTime = (currentTime - previousTime) / 1000; // Convert to seconds
previousTime = currentTime;
// Update game objects based on elapsed time
updateGameObjects(deltaTime);
// Render the scene
scene.render();
});
function updateGameObjects(deltaTime) {
// Time-based animation example
sphere.rotation.y += 1.0 * deltaTime; // Rotate 1 radian per second
// Game logic updates
updatePlayerPosition(deltaTime);
checkCollisions();
updateAI(deltaTime);
}
Using delta time (the time elapsed since the last frame) ensures animations run at consistent speeds regardless of frame rate.
Babylon.js provides tools to monitor render loop performance:
// Display FPS counter
scene.debugLayer.show();
// Or track FPS programmatically
engine.runRenderLoop(() => {
const fps = engine.getFps().toFixed();
fpsCounter.textContent = fps + " FPS";
scene.render();
});
For optimal performance, consider these strategies:
// Conditional rendering - only render when needed
let needsToRender = true;
engine.runRenderLoop(() => {
if (needsToRender) {
scene.render();
}
});
// Pause rendering when tab is inactive
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
engine.stopRenderLoop();
} else {
engine.runRenderLoop(() => scene.render());
}
});
// Adaptive quality based on performance
engine.runRenderLoop(() => {
const currentFps = engine.getFps();
if (currentFps < 30) {
// Reduce quality to maintain performance
reduceSceneComplexity();
}
scene.render();
});
For applications with multiple scenes (e.g., a 3D world and a UI overlay):
engine.runRenderLoop(() => {
// Render main game scene
gameScene.render();
// Render UI overlay scene (without clearing the canvas)
uiScene.autoClear = false; // Don't clear previous scene
uiScene.render();
});
For more precise control over timing:
// Fixed timestep simulation (useful for physics)
const FIXED_TIMESTEP = 1/60; // 60 updates per second
let accumulator = 0;
engine.runRenderLoop(() => {
const currentTime = performance.now() / 1000;
const deltaTime = currentTime - previousTime;
previousTime = currentTime;
// Add frame time to accumulator
accumulator += Math.min(deltaTime, 0.1); // Cap delta time to prevent spiral of death
// Run physics at fixed timestep for stability
while (accumulator >= FIXED_TIMESTEP) {
updatePhysics(FIXED_TIMESTEP);
accumulator -= FIXED_TIMESTEP;
}
// Render at variable framerate
scene.render();
});
This approach separates physics simulation (which often requires fixed timesteps) from rendering (which can run at variable frame rates). In Babylon.js 7, the render loop has been optimized to reduce overhead and improve performance, particularly on mobile devices and other performance-constrained platforms.