Comprehensive Guide to Babylon.js Game Engine
Introduction to Babylon.js
Originally created by Microsoft developers and now powered by hundreds of contributors, Babylon.js is a comprehensive open-source 3D game engine that empowers developers with exceptional freedom. Unlike other 3D frameworks that lock users into proprietary editors and workflows, Babylon.js takes a pure code-first approach—it's entirely JavaScript-based, requiring no resource-heavy editing tools, compilation steps, or specialized environments. This liberating design philosophy means developers can work with familiar web technologies and their preferred coding tools, whether that's a lightweight text editor or full IDE. The framework's straightforward API allows direct manipulation of the 3D world through clean, readable code that integrates seamlessly with existing web projects. Built on WebGL, Babylon.js delivers impressive performance for games, visualizations, and immersive applications while maintaining the flexibility that web developers value. This rare combination of sophisticated capabilities and development freedom has made Babylon.js the engine of choice for developers who want powerful 3D features without sacrificing their autonomy or adding bloated toolchains to their workflow.
Key strengths of Babylon.js include:
- Performance: Optimized for speed and efficiency with powerful rendering capabilities
- Extensibility: Modular architecture that allows for custom features and extensions
- Comprehensive Features: Built-in physics, audio, particle systems, and advanced materials
- Cross-Platform: Works across desktop and mobile browsers without plugins
- Community Support: Active development community and extensive documentation
- Modern Web Standards: Supports WebXR, WebGPU, and other cutting-edge web technologies
Babylon.js stands out for its balanced approach - powerful enough for professional game development while remaining accessible for web developers transitioning into 3D graphics.
Getting Started with Babylon.js
There are multiple ways to incorporate Babylon.js into your project, giving you flexibility based on your development environment and needs. You can include it via CDN for quick prototyping, install it using npm for modern development workflows, or download the files directly for offline use.
When importing Babylon.js in your code, you have two primary approaches: a modular approach where you selectively import only the specific components you need (recommended for production to minimize bundle size), or importing the entire library for a more convenient development experience when experimenting with different features. This flexibility allows you to optimize for either development speed or production performance.
CDN Integration
You can quickly add Babylon.js to your project by including scripts from the Content Delivery Network:
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<!-- Optional modules as needed -->
<script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
<script src="https://cdn.babylonjs.com/materials/babylonjs.materials.min.js"></script>
<script src="https://cdn.babylonjs.com/gui/babylon.gui.min.js"></script>
NPM Installation
For modern JavaScript projects using build tools, you can install Babylon.js via npm:
npm install babylonjs babylonjs-loaders --save
Global Import
Import the entire library for easier development experience:
With Babylon.js 7, the modular approach has been refined to improve loading performance and reduce bundle sizes. For development environments, using the global import provides convenience with access to all APIs without needing to add imports for each component.
// Import everything - larger bundle size but simpler development
import * as BABYLON from 'babylonjs';
import 'babylonjs-loaders';
Selective Import
Import only the specific components you need to minimize bundle size. This approach is recommended for production as it can significantly reduce final bundle size.
// Import only required components
import { Engine, Scene, FreeCamera, Vector3, HemisphericLight, MeshBuilder } from 'babylonjs';
import { SceneLoader } from 'babylonjs/Loading/sceneLoader';
Setting Up the Canvas
The HTML5 canvas element provides the foundation for Babylon.js rendering. This section covers how to properly set up your canvas element and prepare it for 3D rendering.
The canvas acts as the viewport into your 3D world, and proper setup is crucial for performance and user experience. Here's how to create a basic HTML structure with a properly configured canvas:
Basic HTML Setup
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Babylon.js Basic Scene</title>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#renderCanvas {
width: 100%;
height: 100%;
touch-action: none;
}
</style>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script src="app.js"></script>
</body>
</html>
Canvas Considerations
Key canvas considerations:
- Fullscreen Canvas: Setting width and height to 100% creates an immersive experience that fills the viewport
- Touch Action: The touch-action: none CSS property prevents default touch behaviors like scrolling when interacting with the canvas
- Canvas ID: Assigning an ID makes it easy to reference the canvas in JavaScript
- Overflow Handling: Setting overflow: hidden on the body prevents scrollbars when the canvas fills the viewport
Responsive Canvas
For responsive applications, you might need to handle canvas resizing when the window dimensions change:
window.addEventListener('resize', function() {
engine.resize();
});
This ensures your 3D scene maintains the correct aspect ratio and fills the available space when the browser window is resized.
The Engine Object
The Engine
is the foundation of Babylon.js, responsible for interfacing with WebGL, managing the rendering context, and coordinating the rendering pipeline. Understanding the Engine object is essential for creating optimized 3D applications.
Engine Initialization
Creating an Engine instance links Babylon.js to your canvas and initializes the WebGL context:
// Get the canvas element from the DOM
const canvas = document.getElementById("renderCanvas");
// Initialize the Babylon Engine with the canvas
// Parameters: canvas, antialias, options, adaptToDeviceRatio
const engine = new BABYLON.Engine(canvas, true, {
preserveDrawingBuffer: true,
stencil: true,
disableWebGL2Support: false
}, true);
Engine Options
The Engine constructor accepts several important options:
- antialias (boolean): Enables antialiasing for smoother edges
- preserveDrawingBuffer (boolean): Required for taking screenshots of the canvas
- stencil (boolean): Enables stencil buffer for advanced rendering effects
- premultipliedAlpha (boolean): Controls alpha blending behavior
- adaptToDeviceRatio (boolean): Adjusts rendering resolution based on device pixel ratio
In Babylon.js 7, the Engine architecture has been refined for better performance with modern WebGL implementations and includes improved support for WebGPU, offering significant performance improvements on compatible browsers.
Engine Architecture
The Engine acts as an abstraction layer between your application code and the underlying WebGL context. It manages crucial aspects including:
- Rendering Context: Handles WebGL state and operations
- Render Loop: Controls the animation timing and frame rate
- Resource Management: Manages GPU resources like textures and buffers
- Capabilities Detection: Identifies hardware/browser capabilities and limitations
- Scene Rendering: Coordinates the rendering of all scenes
Engine Methods
Common Engine methods include:
// Start the render loop
engine.runRenderLoop(() => {
scene.render();
});
// Handle window resize events
engine.resize();
// Get current FPS
const fps = engine.getFps().toFixed();
// Set hardware scaling level
engine.setHardwareScalingLevel(0.5); // render at half resolution
// Dispose engine and release resources
engine.dispose();
Hardware Acceleration and Optimization
The Engine automatically detects hardware capabilities and adapts accordingly. You can also implement your own optimizations:
// Adjust performance based on FPS
engine.runRenderLoop(() => {
const currentFps = engine.getFps();
if (currentFps < 30) {
// Reduce quality to improve performance
engine.setHardwareScalingLevel(0.8);
} else if (currentFps > 60) {
// Improve quality if performance is good
engine.setHardwareScalingLevel(1.0);
}
scene.render();
});
Babylon.js 7 introduces enhanced performance profiling tools, allowing developers to identify and address bottlenecks more effectively.
Scene Management
The Scene object is the container for all elements in your 3D world - meshes, lights, cameras, and more. It coordinates these elements and manages their interactions within the 3D space.
Scene Creation
Creating a scene is straightforward:
// Create a new scene
const scene = new BABYLON.Scene(engine);
// Optional: Set scene properties
scene.clearColor = new BABYLON.Color4(0.2, 0.2, 0.3, 1); // Set background color
scene.ambientColor = new BABYLON.Color3(0.3, 0.3, 0.3); // Set ambient light
Scene Architecture
The Scene class implements several important software engineering patterns:
The Scene class in Babylon.js implements several important software engineering patterns that facilitate effective 3D application development:
- Observer Pattern: The scene utilizes a robust event system where objects emit events that other components can subscribe to. This enables loose coupling between components, as listeners can react to changes without direct dependencies. For example,
scene.onBeforeRenderObservable
allows code to execute before each frame renders without modifying the render loop itself. This pattern is extensively used throughout Babylon.js for everything from input handling to animation triggers.
- Component System: Elements like meshes, lights, cameras, and materials are managed as components attached to the scene. Each component handles its specific functionality while the scene coordinates their interactions. This modular approach allows developers to add, remove, or modify scene elements independently. The scene maintains registries of these components (e.g.,
scene.meshes
,scene.lights
) and handles their lifecycle events appropriately.
- Hierarchical Structure: Objects within the scene can form parent-child relationships creating a scene graph. Transformations applied to parent nodes cascade down to their children, allowing for complex object grouping and simplified manipulation of related elements. This hierarchy enables intuitive organization of scene elements and efficient transforms through matrix inheritance. The scene itself acts as the root of this hierarchy.
- Rendering Pipeline: The scene controls a sophisticated rendering pipeline that determines how and when elements are drawn to the screen. This pipeline includes stages for shadow generation, post-processing effects, particle systems, and various render targets. Developers can customize this pipeline through scene rendering groups, layer masks, and custom render targets to achieve complex visual effects while maintaining performance.
- State Management: The scene maintains the global state of the 3D environment, including active cameras, lights, environmental settings, and physics configuration. This centralized state management provides a single source of truth for the rendering system and ensures consistent behavior across the application.
- Resource Management: The scene helps coordinate resource loading, tracking, and disposal. It provides mechanisms for asynchronous asset loading, texture management, and garbage collection of unused resources to prevent memory leaks.
Scene Lifecycle
The scene provides several key lifecycle hooks:
// Before render hook - called before each frame render
scene.onBeforeRenderObservable.add(() => {
// Perform operations before rendering
});
// After render hook - called after each frame render
scene.onAfterRenderObservable.add(() => {
// Perform operations after rendering
});
// Ready event - called when the scene is fully loaded
scene.onReadyObservable.add(() => {
// Scene is ready, all resources loaded
});
Scene Management
For complex applications, you might manage multiple scenes:
// Create multiple scenes
const gameScene = new BABYLON.Scene(engine);
const uiScene = new BABYLON.Scene(engine);
// Switch between scenes
let activeScene = gameScene;
engine.runRenderLoop(() => {
activeScene.render();
});
// Function to switch scenes
function switchToUIScene() {
activeScene = uiScene;
}
Scene Graph
The scene maintains a hierarchical graph of all objects:
// Create a parent node
const parentNode = new BABYLON.TransformNode("parent", scene);
// Create child meshes
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {}, scene);
sphere.parent = parentNode;
const box = BABYLON.MeshBuilder.CreateBox("box", {}, scene);
box.parent = parentNode;
// Transforming the parent affects all children
parentNode.position.y = 2;
parentNode.rotation.y = Math.PI / 4;
Scene Optimization
Babylon.js 7 includes enhanced scene optimization tools:
// Activate scene optimizer
const options = new BABYLON.SceneOptimizerOptions();
options.addOptimization(new BABYLON.HardwareScalingOptimization(0, 1));
options.addOptimization(new BABYLON.ParticlesOptimization(0.5));
options.addOptimization(new BABYLON.ShadowsOptimization(0.5));
const optimizer = new BABYLON.SceneOptimizer(scene, options);
optimizer.start();
Scene Serialization
Scenes can be serialized to JSON for saving and loading:
// Serialize scene to JSON
const serializedScene = BABYLON.SceneSerializer.Serialize(scene);
const sceneString = JSON.stringify(serializedScene);
// Save to localStorage or server...
localStorage.setItem('savedScene', sceneString);
// Later, load the scene
const loadedScene = new BABYLON.Scene(engine);
const savedData = localStorage.getItem('savedScene');
BABYLON.SceneLoader.Append("", "data:" + savedData, loadedScene);
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.
Basic Render Loop
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.
Render Loop Architecture
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.
Custom Logic in the Render Loop
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.
Performance Monitoring
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();
});
Optimizing the Render Loop
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();
});
Multiple Scenes in the Render Loop
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();
});
Advanced Timing Control
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.
Cameras
Cameras define the viewpoint from which your scene is rendered. Babylon.js offers various camera types to suit different application needs, from traditional gaming perspectives to VR experiences.
Camera Fundamentals
All cameras share certain core properties:
// Core camera properties
camera.position = new BABYLON.Vector3(0, 5, -10); // Position in 3D space
camera.target = new BABYLON.Vector3(0, 0, 0); // Look-at target
camera.fov = 0.8; // Field of view in radians
camera.minZ = 0.1; // Near clipping plane
camera.maxZ = 1000; // Far clipping plane
Field of View (FOV) controls the camera's viewing angle, measured in radians. A wider FOV (higher value) creates a fisheye-like effect showing more of the scene but with more distortion. A narrower FOV (lower value) creates a telescopic effect with less distortion but shows less of the scene. Typical values range from 0.5 to 1.2 radians (approximately 30° to 70°).
Near and Far Clipping Planes define the viewing frustum's boundaries:
- The Near Clipping Plane (
minZ
) is the minimum distance from the camera at which objects become visible. Objects closer than this distance won't be rendered. It should be set as large as possible (typically 0.1-1.0) while still including all necessary objects to avoid precision issues.
- The Far Clipping Plane (
maxZ
) is the maximum distance from the camera at which objects are still visible. Objects beyond this distance won't be rendered. Setting this value appropriately (typically 100-2000) can improve performance and depth buffer precision.
ArcRotateCamera
The ArcRotateCamera is one of the most versatile and widely used cameras in Babylon.js. It orbits around a target point, making it ideal for object viewing, 3D modeling applications, and orbital gameplay.
// Create ArcRotateCamera: name, alpha, beta, radius, target, scene
const camera = new BABYLON.ArcRotateCamera("camera",
Math.PI / 2, // Alpha (horizontal rotation)
Math.PI / 4, // Beta (vertical angle)
10, // Radius (distance from target)
new BABYLON.Vector3(0, 0, 0), // Target
scene);
// Enable camera controls
camera.attachControl(canvas, true);
// Configure mouse/touch behavior
camera.inputs.attached.mousewheel.detachControl(canvas); // Disable zoom with mouse wheel
camera.inputs.addMouseWheel(); // Re-add with custom settings
camera.wheelPrecision = 50; // Lower value = faster zoom
// Constrain camera movement
camera.lowerRadiusLimit = 5; // Minimum zoom distance
camera.upperRadiusLimit = 20; // Maximum zoom distance
camera.lowerBetaLimit = 0.1; // Limit vertical rotation (lower)
camera.upperBetaLimit = Math.PI / 2.2; // Limit vertical rotation (upper)
// Camera inertia (smooth movement)
camera.inertia = 0.7; // Higher = more inertia
// Adjust rotation speed
camera.angularSensibilityX = 500; // Horizontal rotation sensitivity
camera.angularSensibilityY = 500; // Vertical rotation sensitivity
ArcRotateCamera Animation
Animate the camera for cinematic effects or guided tours:
// Animate the camera orbit
let alpha = 0;
scene.onBeforeRenderObservable.add(() => {
alpha += 0.01;
camera.alpha = alpha;
});
// Or use the animation system
const rotationAnimation = new BABYLON.Animation(
"cameraRotation",
"alpha",
30, // frames per second
BABYLON.Animation.ANIMATIONTYPE_FLOAT,
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);
const keyFrames = [];
keyFrames.push({ frame: 0, value: 0 });
keyFrames.push({ frame: 120, value: Math.PI * 2 });
rotationAnimation.setKeys(keyFrames);
camera.animations = [rotationAnimation];
scene.beginAnimation(camera, 0, 120, true); // true = loop
ArcRotateCamera Advanced Features
// Auto-rotation when idle
camera.useAutoRotationBehavior = true;
camera.autoRotationBehavior.idleRotationSpeed = 0.2;
camera.autoRotationBehavior.idleRotationWaitTime = 3000; // ms
// Bouncing behavior when hitting limits
camera.useBouncingBehavior = true;
camera.bouncingBehavior.transitionDuration = 150; // ms
FreeCamera
The FreeCamera (or Universal Camera) provides first-person movement, ideal for exploration and FPS-style games:
// Create FreeCamera: name, position, scene
const camera = new BABYLON.FreeCamera("camera",
new BABYLON.Vector3(0, 2, -10), // Position
scene);
// Set camera direction
camera.setTarget(BABYLON.Vector3.Zero());
// Enable controls
camera.attachControl(canvas, true);
// Configure keyboard controls
camera.keysUp = [87]; // W key
camera.keysDown = [83]; // S key
camera.keysLeft = [65]; // A key
camera.keysRight = [68]; // D key
// Adjust movement speed
camera.speed = 0.5;
camera.angularSensibility = 1000; // Mouse look sensitivity
// Add WASD movement for FPS style controls
camera.inputs.addKeyboard();
FollowCamera
The FollowCamera tracks a target object while maintaining a specified offset, perfect for third-person games:
// Create target object
const player = BABYLON.MeshBuilder.CreateBox("player", {size: 1}, scene);
// Create FollowCamera
const camera = new BABYLON.FollowCamera("followCam",
new BABYLON.Vector3(0, 10, -10), // Initial position
scene);
// Configure follow behavior
camera.lockedTarget = player; // Target to follow
camera.radius = 10; // Distance from target
camera.heightOffset = 5; // Height above target
camera.rotationOffset = 180; // Rotation around target in degrees
camera.cameraAcceleration = 0.05; // Camera acceleration
camera.maxCameraSpeed = 10; // Maximum speed of camera
// Animate the player to see the camera follow
scene.onBeforeRenderObservable.add(() => {
player.position.x = Math.sin(Date.now() / 1000) * 5;
});
VR and XR Cameras
Babylon.js 7 includes enhanced support for Virtual Reality and Augmented Reality through the WebXR standard:
// Create default experience helper
const xrHelper = await scene.createDefaultXRExperienceAsync({
floorMeshes: [ground] // Optional meshes to use as floor reference
});
// Check if XR is available
if (xrHelper.isSupported) {
// Create button for VR entry
const vrButton = document.createElement("button");
vrButton.textContent = "Enter VR";
vrButton.style.position = "absolute";
vrButton.style.bottom = "10px";
vrButton.style.right = "10px";
document.body.appendChild(vrButton);
vrButton.addEventListener("click", async () => {
await xrHelper.enterXRAsync("immersive-vr", "local-floor");
});
}
Camera Input Management
Customize camera controls for different devices and user preferences:
// Remove default inputs
camera.inputs.clear();
// Add only the inputs you need
camera.inputs.add(new BABYLON.ArcRotateCameraKeyboardMoveInput());
camera.inputs.add(new BABYLON.ArcRotateCameraMouseWheelInput());
camera.inputs.add(new BABYLON.ArcRotateCameraPointersInput());
// Create custom camera controls
class CustomCameraInput implements BABYLON.ICameraInput<BABYLON.ArcRotateCamera> {
camera: BABYLON.ArcRotateCamera;
getClassName(): string {
return "CustomCameraInput";
}
getSimpleName(): string {
return "custom";
}
attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
// Custom control logic here
}
detachControl(element: HTMLElement): void {
// Clean up event listeners
}
}
// Add custom input
camera.inputs.add(new CustomCameraInput());
Babylon.js 7 includes enhanced camera features for better control and performance, making cameras more versatile across different device capabilities.
Physics in Babylon.js
Babylon.js supports realistic physics simulation through multiple physics engines. This allows you to create interactive environments with gravity, collisions, forces, and constraints that behave according to physical laws.
Physics Engines Overview
Babylon.js provides several physics engine options through a plugin architecture:
- Cannon.js: Lightweight physics engine with good performance-to-accuracy balance
- Oimo.js: Fast physics engine optimized for performance over accuracy
- Ammo.js: Powerful engine (WebAssembly port of Bullet Physics) with advanced features
- Havok: Commercial-grade physics with premium performance (requires license)
Each engine offers different trade-offs between performance, accuracy, feature set, and ease of use. For most projects, Cannon.js provides a good balance and is the recommended starting point.
// Import the physics engine (Cannon.js example)
// In HTML: <script src="https://cdn.babylonjs.com/cannon.js"></script>
// Or in modules: import * as CANNON from 'cannon';
// Enable physics with Cannon.js
const gravityVector = new BABYLON.Vector3(0, -9.81, 0);
const physicsPlugin = new BABYLON.CannonJSPlugin();
scene.enablePhysics(gravityVector, physicsPlugin);
Setting Up Physics
Configure the physics environment for your scene:
// Setup physics with Ammo.js
// First, ensure Ammo.js is loaded
// <script src="https://cdn.babylonjs.com/ammo.js"></script>
// Initialize Ammo
await Ammo();
// Create physics plugin
const gravityVector = new BABYLON.Vector3(0, -9.81, 0);
const physicsPlugin = new BABYLON.AmmoJSPlugin();
// Enable physics in the scene
scene.enablePhysics(gravityVector, physicsPlugin);
// Configure physics engine properties
scene.getPhysicsEngine().setSubTimeStep(0.01); // For more accurate simulation
// Set global collision margin (Ammo.js specific)
physicsPlugin.setDefaultNonContactCustomMaterial({
friction: 0.3,
restitution: 0.3
});
Physics Bodies
Add physics properties to meshes to make them interact with the physics world:
// Create a ground plane with physics
const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 50, height: 50}, scene);
ground.physicsImpostor = new BABYLON.PhysicsImpostor(
ground,
BABYLON.PhysicsImpostor.BoxImpostor, // Use box collision for ground
{ mass: 0, restitution: 0.3, friction: 0.3 }, // mass 0 = static object
scene
);
// Create a dynamic sphere
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 2}, scene);
sphere.position.y = 10; // Start above ground
sphere.physicsImpostor = new BABYLON.PhysicsImpostor(
sphere,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 1, restitution: 0.7, friction: 0.1 }, // mass > 0 = dynamic object
scene
);
// Create a dynamic box
const box = BABYLON.MeshBuilder.CreateBox("box", {size: 2}, scene);
box.position = new BABYLON.Vector3(0, 15, 0);
box.physicsImpostor = new BABYLON.PhysicsImpostor(
box,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 2, restitution: 0.2, friction: 0.4 },
scene
);
Impostor Types
Different collision shapes for different needs:
Babylon.js provides several impostor types for different collision needs:
- BoxImpostor: Simple box collision - efficient for rectangular objects
- SphereImpostor: Spherical collision - most efficient, good for round objects
- CylinderImpostor: Cylindrical collision - for tubes, pillars, etc.
- MeshImpostor: Uses actual mesh geometry - most accurate but computationally expensive
- HeightmapImpostor: Efficient for terrain - uses height data for collision
- ConvexHullImpostor: Creates simplified collision from mesh vertices - good balance
- ParticleImpostor: Point with mass but no rotation - for simple particles
- NoImpostor: For compound objects with child impostors
For performance, always use the simplest collision shape that adequately represents your object.
// Complex mesh with accurate collision
const complexMesh = BABYLON.MeshBuilder.CreateTorusKnot("tk", {}, scene);
complexMesh.position.y = 10;
// Options for different collision types
// 1. Exact mesh collision (expensive)
complexMesh.physicsImpostor = new BABYLON.PhysicsImpostor(
complexMesh,
BABYLON.PhysicsImpostor.MeshImpostor,
{ mass: 3, restitution: 0.4 },
scene
);
// 2. Convex hull (better performance)
complexMesh.physicsImpostor = new BABYLON.PhysicsImpostor(
complexMesh,
BABYLON.PhysicsImpostor.ConvexHullImpostor,
{ mass: 3, restitution: 0.4 },
scene
);
// 3. Simple approximation (best performance)
complexMesh.physicsImpostor = new BABYLON.PhysicsImpostor(
complexMesh,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 3, restitution: 0.4 },
scene
);
Compound Bodies
Create complex physics objects from multiple shapes:
// Create a compound object (e.g., a dumbbell)
const dumbbell = new BABYLON.Mesh("dumbbell", scene);
// Create the middle bar
const bar = BABYLON.MeshBuilder.CreateCylinder("bar", {
height: 5,
diameter: 0.5
}, scene);
// Create the weights
const leftWeight = BABYLON.MeshBuilder.CreateSphere("leftWeight", {
diameter: 2
}, scene);
leftWeight.position.x = -2.5;
const rightWeight = BABYLON.MeshBuilder.CreateSphere("rightWeight", {
diameter: 2
}, scene);
rightWeight.position.x = 2.5;
// Make the parts children of the main mesh
bar.parent = dumbbell;
leftWeight.parent = dumbbell;
rightWeight.parent = dumbbell;
// Position the dumbbell
dumbbell.position.y = 10;
// Create compound physics impostor
dumbbell.physicsImpostor = new BABYLON.PhysicsImpostor(
dumbbell,
BABYLON.PhysicsImpostor.NoImpostor, // Parent has no direct impostor
{ mass: 10 },
scene
);
// Add child impostors
bar.physicsImpostor = new BABYLON.PhysicsImpostor(
bar,
BABYLON.PhysicsImpostor.CylinderImpostor,
{ mass: 0 }, // Child masses are ignored when part of compound
scene
);
leftWeight.physicsImpostor = new BABYLON.PhysicsImpostor(
leftWeight,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 0 },
scene
);
rightWeight.physicsImpostor = new BABYLON.PhysicsImpostor(
rightWeight,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 0 },
scene
);
Forces and Impulses
Apply physics forces to objects in your scene:
// Apply a continuous force (newtons)
sphere.physicsImpostor.applyForce(
new BABYLON.Vector3(0, 0, 10), // Force direction and magnitude
sphere.getAbsolutePosition() // Application point
);
// Apply an impulse (instantaneous force)
box.physicsImpostor.applyImpulse(
new BABYLON.Vector3(5, 0, 0), // Impulse direction and magnitude
box.getAbsolutePosition() // Application point
);
// Set linear velocity directly
sphere.physicsImpostor.setLinearVelocity(new BABYLON.Vector3(0, 5, 0));
// Set angular velocity (rotation)
box.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0, 2, 0));
// Apply a torque (rotational force)
box.physicsImpostor.applyTorque(new BABYLON.Vector3(0, 10, 0));
Interactive Forces
Create user-controlled physics interactions:
// Add force on key press
scene.onKeyboardObservable.add((kbInfo) => {
if (kbInfo.type === BABYLON.KeyboardEventTypes.KEYDOWN) {
switch (kbInfo.event.key) {
case "ArrowUp":
sphere.physicsImpostor.applyImpulse(
new BABYLON.Vector3(0, 0, -10),
sphere.getAbsolutePosition()
);
break;
case "ArrowDown":
sphere.physicsImpostor.applyImpulse(
new BABYLON.Vector3(0, 0, 10),
sphere.getAbsolutePosition()
);
break;
case "ArrowLeft":
sphere.physicsImpostor.applyImpulse(
new BABYLON.Vector3(-10, 0, 0),
sphere.getAbsolutePosition()
);
break;
case "ArrowRight":
sphere.physicsImpostor.applyImpulse(
new BABYLON.Vector3(10, 0, 0),
sphere.getAbsolutePosition()
);
break;
case " ": // Space key
sphere.physicsImpostor.applyImpulse(
new BABYLON.Vector3(0, 10, 0), // Jump
sphere.getAbsolutePosition()
);
break;
}
}
});
Constraints and Joints
Connect physics objects with various types of joints:
// Create a hinge joint (like a door hinge)
const hingePivot = new BABYLON.Vector3(0, 5, 0);
const hingeAxis = new BABYLON.Vector3(0, 1, 0); // Rotate around Y axis
const hingeJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.HingeJoint, {
mainPivot: hingePivot,
connectedPivot: new BABYLON.Vector3(0, 0, 0),
mainAxis: hingeAxis,
connectedAxis: hingeAxis
}
);
// Connect a box to a fixed anchor point
const anchor = BABYLON.MeshBuilder.CreateBox("anchor", {size: 0.5}, scene);
anchor.position = hingePivot;
anchor.physicsImpostor = new BABYLON.PhysicsImpostor(
anchor,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 0 }, // Fixed point (zero mass)
scene
);
// Create a panel to act as a door
const door = BABYLON.MeshBuilder.CreateBox("door", {
width: 0.5,
height: 6,
depth: 4
}, scene);
door.position = new BABYLON.Vector3(2, 5, 0);
door.physicsImpostor = new BABYLON.PhysicsImpostor(
door,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 5, friction: 0.5 },
scene
);
// Connect the door to the anchor with a hinge joint
anchor.physicsImpostor.addJoint(door.physicsImpostor, hingeJoint);
Additional Joint Types
Other types of physical joints and constraints:
// Ball-and-socket joint (allows rotation in all directions)
const ballJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.BallAndSocketJoint, {
mainPivot: new BABYLON.Vector3(0, 0, 0),
connectedPivot: new BABYLON.Vector3(0, 2, 0)
}
);
// Distance joint (maintains fixed distance between objects)
const distanceJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.DistanceJoint, {
maxDistance: 5 // Maximum distance constraint
}
);
// Prismatic joint (allows movement along one axis only)
const prismaticJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.PrismaticJoint, {
mainAxis: new BABYLON.Vector3(1, 0, 0), // Movement along X axis
connectedAxis: new BABYLON.Vector3(1, 0, 0)
}
);
// Slider joint with limits
const sliderJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.SliderJoint, {
mainAxis: new BABYLON.Vector3(0, 1, 0),
connectedAxis: new BABYLON.Vector3(0, 1, 0)
}
);
sliderJoint.setLimit(1, 5); // Min and max distance
Spring Systems
Create spring-like behavior with physics:
// Create a spring joint
const spring = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.SpringJoint, {
length: 5, // Rest length
stiffness: 50, // Spring strength
damping: 0.5, // Damping factor
collision: true // Allow connected bodies to collide
}
);
// Create anchor point
const springAnchor = BABYLON.MeshBuilder.CreateSphere("anchor", {diameter: 1}, scene);
springAnchor.position.y = 15;
springAnchor.physicsImpostor = new BABYLON.PhysicsImpostor(
springAnchor,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 0 }, // Fixed position
scene
);
// Create suspended object
const bob = BABYLON.MeshBuilder.CreateSphere("bob", {diameter: 2}, scene);
bob.position.y = 10;
bob.physicsImpostor = new BABYLON.PhysicsImpostor(
bob,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 2, restitution: 0.6 },
scene
);
// Connect with spring joint
springAnchor.physicsImpostor.addJoint(bob.physicsImpostor, spring);
// Visualize the spring connection with a line
const springLine = BABYLON.MeshBuilder.CreateLines("springLine", {
points: [springAnchor.position, bob.position],
updatable: true
}, scene);
// Update the line in the render loop
scene.onBeforeRenderObservable.add(() => {
springLine = BABYLON.MeshBuilder.CreateLines("springLine", {
points: [springAnchor.position, bob.position],
instance: springLine
});
});
Collision Detection & Events
Respond to physics collisions in your scene:
// Add collision event handler
sphere.physicsImpostor.onCollideEvent = (collider, collidedWith) => {
// collider is the sphere's impostor
// collidedWith is the other object's impostor
// Change material on collision
const material = sphere.material;
material.diffuseColor = new BABYLON.Color3(1, 0, 0); // Turn red
// Reset color after delay
setTimeout(() => {
material.diffuseColor = new BABYLON.Color3(1, 1, 1); // Back to white
}, 500);
// Play sound on collision
const impactVelocity = collider.getLinearVelocity().length();
if (impactVelocity > 1) {
const collisionSound = new BABYLON.Sound(
"collision",
"sounds/collision.mp3",
scene,
null,
{ volume: Math.min(impactVelocity / 10, 1) }
);
collisionSound.play();
}
};
Trigger Volumes
Create non-solid volumes that detect when objects enter them:
// Create a trigger zone
const triggerZone = BABYLON.MeshBuilder.CreateBox("trigger", {
width: 10,
height: 5,
depth: 10
}, scene);
triggerZone.position.y = 2.5;
triggerZone.visibility = 0.2; // Semi-transparent
triggerZone.material = new BABYLON.StandardMaterial("triggerMat", scene);
triggerZone.material.diffuseColor = new BABYLON.Color3(0, 1, 0); // Green
triggerZone.material.alpha = 0.2;
// Make it a trigger volume (non-solid)
triggerZone.physicsImpostor = new BABYLON.PhysicsImpostor(
triggerZone,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 0, isTrigger: true },
scene
);
// Track objects in the trigger zone
const objectsInZone = new Set();
// Handle collisions with the trigger
triggerZone.physicsImpostor.onCollideEvent = (collider, collidedWith) => {
// Get the mesh associated with the colliding impostor
const collidingMesh = collidedWith.object;
// Track object entering the zone
if (!objectsInZone.has(collidingMesh)) {
objectsInZone.add(collidingMesh);
console.log(`${collidingMesh.name} entered the trigger zone!`);
// Change trigger color
triggerZone.material.diffuseColor = new BABYLON.Color3(1, 0, 0);
}
};
// Check if objects have left the zone
scene.onBeforeRenderObservable.add(() => {
for (const obj of objectsInZone) {
// Check if still intersecting
if (!triggerZone.intersectsMesh(obj)) {
objectsInZone.delete(obj);
console.log(`${obj.name} left the trigger zone!`);
// Change back to green if empty
if (objectsInZone.size === 0) {
triggerZone.material.diffuseColor = new BABYLON.Color3(0, 1, 0);
}
}
}
});
Physics Raycasting
Use raycasting to detect physics objects:
// Cast a ray through the physics world
const origin = new BABYLON.Vector3(0, 10, -10);
const direction = new BABYLON.Vector3(0, -1, 1).normalize();
const length = 20;
const raycastResult = scene.getPhysicsEngine().raycast(
origin,
direction,
length
);
// Check if we hit something
if (raycastResult.hasHit) {
console.log(`Hit object: ${raycastResult.body.object.name}`);
console.log(`Hit point: ${raycastResult.hitPointWorld}`);
console.log(`Hit normal: ${raycastResult.hitNormalWorld}`);
console.log(`Hit distance: ${raycastResult.hitDistance}`);
}
// Visualize the ray
const rayHelper = new BABYLON.RayHelper(new BABYLON.Ray(origin, direction, length));
rayHelper.show(scene);
// Interactive raycasting with mouse click
scene.onPointerDown = (evt, pickResult) => {
if (pickResult.hit) {
// Cast ray from camera through click point
const ray = scene.createPickingRay(
scene.pointerX,
scene.pointerY,
BABYLON.Matrix.Identity(),
camera
);
const raycastResult = scene.getPhysicsEngine().raycast(
ray.origin,
ray.direction,
100
);
if (raycastResult.hasHit) {
// Apply force at hit point
const hitBody = raycastResult.body;
const hitPoint = raycastResult.hitPointWorld;
const force = ray.direction.scale(50); // Force strength
hitBody.applyForce(force, hitPoint);
}
}
};
Character Controllers
Implement physics-based character movement:
// Create a character with capsule physics
const characterHeight = 3;
const characterRadius = 0.5;
// Create body parts
const body = BABYLON.MeshBuilder.CreateCylinder("body", {
height: characterHeight - 2 * characterRadius,
diameter: characterRadius * 2
}, scene);
const head = BABYLON.MeshBuilder.CreateSphere("head", {
diameter: characterRadius * 2
}, scene);
head.position.y = (characterHeight - 2 * characterRadius) / 2;
head.parent = body;
const feet = BABYLON.MeshBuilder.CreateSphere("feet", {
diameter: characterRadius * 2
}, scene);
feet.position.y = -(characterHeight - 2 * characterRadius) / 2;
feet.parent = body;
// Position the character
body.position.y = characterHeight / 2;
// Add physics with capsule shape (cylinder + sphere caps)
body.physicsImpostor = new BABYLON.PhysicsImpostor(
body,
BABYLON.PhysicsImpostor.CylinderImpostor,
{ mass: 1, friction: 0.5, restitution: 0.3 },
scene
);
// Variables for movement control
const moveDirection = new BABYLON.Vector3();
let isGrounded = false;
const jumpForce = 10;
const moveSpeed = 0.5;
// Input handling for character movement
const keyStatus = {};
scene.onKeyboardObservable.add((kbInfo) => {
const key = kbInfo.event.key.toLowerCase();
if (kbInfo.type === BABYLON.KeyboardEventTypes.KEYDOWN) {
keyStatus[key] = true;
} else if (kbInfo.type === BABYLON.KeyboardEventTypes.KEYUP) {
keyStatus[key] = false;
}
});
// Ground detection using raycasting
function checkGrounded() {
const origin = body.getAbsolutePosition();
const direction = new BABYLON.Vector3(0, -1, 0);
const length = characterHeight / 2 + 0.1; // Slightly longer than character radius
const raycastResult = scene.getPhysicsEngine().raycast(
origin,
direction,
length
);
return raycastResult.hasHit;
}
// Character controller update
scene.onBeforeRenderObservable.add(() => {
// Check if character is on ground
isGrounded = checkGrounded();
// Reset movement direction
moveDirection.setAll(0);
// Calculate move direction from input
if (keyStatus["w"]) moveDirection.z += 1;
if (keyStatus["s"]) moveDirection.z -= 1;
if (keyStatus["a"]) moveDirection.x -= 1;
if (keyStatus["d"]) moveDirection.x += 1;
// Jump if on ground
if (keyStatus[" "] && isGrounded) {
body.physicsImpostor.applyImpulse(
new BABYLON.Vector3(0, jumpForce, 0),
body.getAbsolutePosition()
);
}
// Normalize and apply movement
if (moveDirection.length() > 0) {
moveDirection.normalize();
// Align with camera direction
const cameraDirection = camera.getTarget().subtract(camera.position);
cameraDirection.y = 0;
cameraDirection.normalize();
// Create rotation matrix from camera direction
const rotationMatrix = BABYLON.Matrix.RotationY(
Math.atan2(cameraDirection.x, cameraDirection.z)
);
// Transform movement direction relative to camera
const transformedDirection = BABYLON.Vector3.TransformNormal(
moveDirection,
rotationMatrix
);
// Apply movement as impulse (only when on ground)
if (isGrounded) {
body.physicsImpostor.applyImpulse(
transformedDirection.scale(moveSpeed),
body.getAbsolutePosition()
);
}
}
});
Advanced Physics Features
Babylon.js 7 includes support for advanced physics simulations:
Vehicle Physics
Create physics-based vehicles with wheel constraints:
// This example uses Ammo.js which supports advanced vehicle physics
// Note: Vehicle physics implementation depends on the physics engine used
// Create a vehicle chassis
const chassisWidth = 2;
const chassisHeight = 0.6;
const chassisLength = 4;
const chassis = BABYLON.MeshBuilder.CreateBox("chassis", {
width: chassisWidth,
height: chassisHeight,
depth: chassisLength
}, scene);
chassis.position.y = 4;
// Create wheels
const wheelRadius = 0.4;
const wheelWidth = 0.3;
const wheelPositions = [
new BABYLON.Vector3(chassisWidth/2, -chassisHeight/2, chassisLength/2 - wheelRadius), // front right
new BABYLON.Vector3(-chassisWidth/2, -chassisHeight/2, chassisLength/2 - wheelRadius), // front left
new BABYLON.Vector3(chassisWidth/2, -chassisHeight/2, -chassisLength/2 + wheelRadius), // rear right
new BABYLON.Vector3(-chassisWidth/2, -chassisHeight/2, -chassisLength/2 + wheelRadius) // rear left
];
const wheels = [];
for (let i = 0; i < 4; i++) {
const wheel = BABYLON.MeshBuilder.CreateCylinder(`wheel${i}`, {
diameter: wheelRadius * 2,
height: wheelWidth,
tessellation: 24
}, scene);
wheel.rotation.z = Math.PI / 2; // Rotate to correct orientation
wheel.position = chassis.position.add(wheelPositions[i]);
wheels.push(wheel);
}
// Add physics
chassis.physicsImpostor = new BABYLON.PhysicsImpostor(
chassis,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 800, friction: 0.5, restitution: 0.2 },
scene
);
// Configure vehicle physics (Ammo.js specific)
// Note: Implementation details vary by physics engine
if (scene.getPhysicsEngine().getPhysicsPluginName() === "AmmoJSPlugin") {
const vehicle = new BABYLON.AmmoJSPlugin.Vehicle(chassis, scene);
// Add wheels to vehicle
for (let i = 0; i < 4; i++) {
const isFront = i < 2;
vehicle.addWheel(
wheels[i],
wheelPositions[i],
new BABYLON.Vector3(0, -1, 0), // Down direction
new BABYLON.Vector3(0, 0, 1), // Forward direction
0.6, // Suspension rest length
wheelRadius,
isFront // Steering wheel?
);
}
// Configure vehicle properties
vehicle.setSteeringValue(0.0); // Initial steering
vehicle.applyEngineForce(0.0); // Initial engine force
vehicle.setBrake(0.0); // Initial brake force
// Vehicle control with keyboard
scene.onKeyboardObservable.add((kbInfo) => {
const key = kbInfo.event.key;
const isDown = kbInfo.type === BABYLON.KeyboardEventTypes.KEYDOWN;
switch (key) {
case "w":
vehicle.applyEngineForce(isDown ? 1000 : 0);
break;
case "s":
vehicle.applyEngineForce(isDown ? -500 : 0);
break;
case "a":
vehicle.setSteeringValue(isDown ? 0.3 : 0);
break;
case "d":
vehicle.setSteeringValue(isDown ? -0.3 : 0);
break;
case " ":
vehicle.setBrake(isDown ? 50 : 0);
break;
}
});
}
Ragdoll Physics
Create articulated character physics for realistic reactions:
// Create a simple ragdoll character
function createRagdoll(position) {
// Create body parts
const parts = {};
// Torso
parts.torso = BABYLON.MeshBuilder.CreateBox("torso", {
width: 1, height: 1.5, depth: 0.5
}, scene);
parts.torso.position = position.clone();
// Head
parts.head = BABYLON.MeshBuilder.CreateSphere("head", {
diameter: 0.7
}, scene);
parts.head.position = position.clone();
parts.head.position.y += 1;
// Upper arms
parts.leftUpperArm = BABYLON.MeshBuilder.CreateCylinder("leftUpperArm", {
height: 0.8, diameter: 0.25
}, scene);
parts.leftUpperArm.rotation.z = Math.PI / 2;
parts.leftUpperArm.position = position.clone();
parts.leftUpperArm.position.x -= 0.8;
parts.leftUpperArm.position.y += 0.4;
parts.rightUpperArm = BABYLON.MeshBuilder.CreateCylinder("rightUpperArm", {
height: 0.8, diameter: 0.25
}, scene);
parts.rightUpperArm.rotation.z = Math.PI / 2;
parts.rightUpperArm.position = position.clone();
parts.rightUpperArm.position.x += 0.8;
parts.rightUpperArm.position.y += 0.4;
// Lower arms
parts.leftLowerArm = BABYLON.MeshBuilder.CreateCylinder("leftLowerArm", {
height: 0.8, diameter: 0.2
}, scene);
parts.leftLowerArm.rotation.z = Math.PI / 2;
parts.leftLowerArm.position = position.clone();
parts.leftLowerArm.position.x -= 1.6;
parts.leftLowerArm.position.y += 0.4;
parts.rightLowerArm = BABYLON.MeshBuilder.CreateCylinder("rightLowerArm", {
height: 0.8, diameter: 0.2
}, scene);
parts.rightLowerArm.rotation.z = Math.PI / 2;
parts.rightLowerArm.position = position.clone();
parts.rightLowerArm.position.x += 1.6;
parts.rightLowerArm.position.y += 0.4;
// Upper legs
parts.leftUpperLeg = BABYLON.MeshBuilder.CreateCylinder("leftUpperLeg", {
height: 1, diameter: 0.3
}, scene);
parts.leftUpperLeg.position = position.clone();
parts.leftUpperLeg.position.x -= 0.3;
parts.leftUpperLeg.position.y -= 1;
parts.rightUpperLeg = BABYLON.MeshBuilder.CreateCylinder("rightUpperLeg", {
height: 1, diameter: 0.3
}, scene);
parts.rightUpperLeg.position = position.clone();
parts.rightUpperLeg.position.x += 0.3;
parts.rightUpperLeg.position.y -= 1;
// Lower legs
parts.leftLowerLeg = BABYLON.MeshBuilder.CreateCylinder("leftLowerLeg", {
height: 1, diameter: 0.25
}, scene);
parts.leftLowerLeg.position = position.clone();
parts.leftLowerLeg.position.x -= 0.3;
parts.leftLowerLeg.position.y -= 2;
parts.rightLowerLeg = BABYLON.MeshBuilder.CreateCylinder("rightLowerLeg", {
height: 1, diameter: 0.25
}, scene);
parts.rightLowerLeg.position = position.clone();
parts.rightLowerLeg.position.x += 0.3;
parts.rightLowerLeg.position.y -= 2;
// Add physics impostors to all parts
for (const [name, part] of Object.entries(parts)) {
part.physicsImpostor = new BABYLON.PhysicsImpostor(
part,
BABYLON.PhysicsImpostor.BoxImpostor, // Use box for better performance
{ mass: name === "torso" ? 3 : 1, friction: 0.5, restitution: 0.3 },
scene
);
}
// Create joints to connect the body parts
// Neck joint
const neckJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.BallAndSocketJoint, {
mainPivot: new BABYLON.Vector3(0, 0.75, 0),
connectedPivot: new BABYLON.Vector3(0, -0.35, 0)
}
);
parts.torso.physicsImpostor.addJoint(parts.head.physicsImpostor, neckJoint);
// Shoulder joints
const leftShoulderJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.BallAndSocketJoint, {
mainPivot: new BABYLON.Vector3(-0.5, 0.5, 0),
connectedPivot: new BABYLON.Vector3(0.4, 0, 0)
}
);
parts.torso.physicsImpostor.addJoint(parts.leftUpperArm.physicsImpostor, leftShoulderJoint);
const rightShoulderJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.BallAndSocketJoint, {
mainPivot: new BABYLON.Vector3(0.5, 0.5, 0),
connectedPivot: new BABYLON.Vector3(-0.4, 0, 0)
}
);
parts.torso.physicsImpostor.addJoint(parts.rightUpperArm.physicsImpostor, rightShoulderJoint);
// Elbow joints
const leftElbowJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.HingeJoint, {
mainPivot: new BABYLON.Vector3(-0.4, 0, 0),
connectedPivot: new BABYLON.Vector3(0.4, 0, 0),
mainAxis: new BABYLON.Vector3(0, 0, 1),
connectedAxis: new BABYLON.Vector3(0, 0, 1)
}
);
parts.leftUpperArm.physicsImpostor.addJoint(parts.leftLowerArm.physicsImpostor, leftElbowJoint);
const rightElbowJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.HingeJoint, {
mainPivot: new BABYLON.Vector3(0.4, 0, 0),
connectedPivot: new BABYLON.Vector3(-0.4, 0, 0),
mainAxis: new BABYLON.Vector3(0, 0, 1),
connectedAxis: new BABYLON.Vector3(0, 0, 1)
}
);
parts.rightUpperArm.physicsImpostor.addJoint(parts.rightLowerArm.physicsImpostor, rightElbowJoint);
// Hip joints
const leftHipJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.BallAndSocketJoint, {
mainPivot: new BABYLON.Vector3(-0.3, -0.75, 0),
connectedPivot: new BABYLON.Vector3(0, 0.5, 0)
}
);
parts.torso.physicsImpostor.addJoint(parts.leftUpperLeg.physicsImpostor, leftHipJoint);
const rightHipJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.BallAndSocketJoint, {
mainPivot: new BABYLON.Vector3(0.3, -0.75, 0),
connectedPivot: new BABYLON.Vector3(0, 0.5, 0)
}
);
parts.torso.physicsImpostor.addJoint(parts.rightUpperLeg.physicsImpostor, rightHipJoint);
// Knee joints
const leftKneeJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.HingeJoint, {
mainPivot: new BABYLON.Vector3(0, -0.5, 0),
connectedPivot: new BABYLON.Vector3(0, 0.5, 0),
mainAxis: new BABYLON.Vector3(1, 0, 0),
connectedAxis: new BABYLON.Vector3(1, 0, 0)
}
);
parts.leftUpperLeg.physicsImpostor.addJoint(parts.leftLowerLeg.physicsImpostor, leftKneeJoint);
const rightKneeJoint = new BABYLON.PhysicsJoint(
BABYLON.PhysicsJoint.HingeJoint, {
mainPivot: new BABYLON.Vector3(0, -0.5, 0),
connectedPivot: new BABYLON.Vector3(0, 0.5, 0),
mainAxis: new BABYLON.Vector3(1, 0, 0),
connectedAxis: new BABYLON.Vector3(1, 0, 0)
}
);
parts.rightUpperLeg.physicsImpostor.addJoint(parts.rightLowerLeg.physicsImpostor, rightKneeJoint);
return parts;
}
// Create a ragdoll
const ragdoll = createRagdoll(new BABYLON.Vector3(0, 10, 0));
// Add button to apply explosion force
const explodeButton = document.createElement("button");
explodeButton.textContent = "Apply Force";
explodeButton.style.position = "absolute";
explodeButton.style.top = "10px";
explodeButton.style.left = "10px";
document.body.appendChild(explodeButton);
explodeButton.addEventListener("click", () => {
// Apply random impulse to torso
ragdoll.torso.physicsImpostor.applyImpulse(
new BABYLON.Vector3(
(Math.random() - 0.5) * 20,
Math.random() * 10,
(Math.random() - 0.5) * 20
),
ragdoll.torso.getAbsolutePosition()
);
});
Performance Optimization
Optimize physics for better performance:
// Configure physics engine for performance
const physicsEngine = scene.getPhysicsEngine();
// Set physics timestep (smaller = more accurate but slower)
physicsEngine.setTimeStep(1/60);
// Enable sub-stepping for more stable simulation
physicsEngine.setSubTimeStep(2); // 2 sub-steps per frame
// Performance techniques
// 1. Use simpler collision shapes
// Sphere and box impostors are much faster than mesh impostors
const complexMesh = BABYLON.MeshBuilder.CreateTorusKnot("complex", {}, scene);
complexMesh.position.y = 5;
// Use a bounding box instead of exact mesh shape
const boundingInfo = complexMesh.getBoundingInfo();
const dimensions = boundingInfo.boundingBox.extendSize.scale(2);
complexMesh.physicsImpostor = new BABYLON.PhysicsImpostor(
complexMesh,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 1, friction: 0.5, restitution: 0.3 },
scene
);
// 2. Use sleep states for static objects
ground.physicsImpostor.sleep(); // Put a static object to sleep
// 3. Group small objects into compound objects
const smallObjectsParent = new BABYLON.Mesh("smallObjects", scene);
for (let i = 0; i < 10; i++) {
const small = BABYLON.MeshBuilder.CreateBox(`small${i}`, {size: 0.2}, scene);
small.position.x = i * 0.3 - 1.5;
small.position.y = 2;
small.parent = smallObjectsParent;
}
// Treat them as one physics object
smallObjectsParent.physicsImpostor = new BABYLON.PhysicsImpostor(
smallObjectsParent,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 2, friction: 0.5 },
scene
);
// 4. Disable physics for distant objects
// Create a function to check distance from camera
const maxPhysicsDistance = 50; // Maximum distance for active physics
scene.onBeforeRenderObservable.add(() => {
// Get active camera position
const cameraPosition = scene.activeCamera.position;
// Check all physics objects
for (const mesh of scene.meshes) {
if (mesh.physicsImpostor) {
const distance = BABYLON.Vector3.Distance(mesh.position, cameraPosition);
// Enable/disable physics based on distance
if (distance > maxPhysicsDistance) {
if (!mesh.physicsImpostor.isDisposed) {
// Store current state before disabling
mesh._physicsEnabled = false;
mesh._linearVelocity = mesh.physicsImpostor.getLinearVelocity().clone();
mesh._angularVelocity = mesh.physicsImpostor.getAngularVelocity().clone();
mesh.physicsImpostor.sleep(); // Put to sleep to reduce computation
}
} else if (mesh.hasOwnProperty("_physicsEnabled") && mesh._physicsEnabled === false) {
// Re-enable physics and restore velocity
mesh._physicsEnabled = true;
mesh.physicsImpostor.wakeUp();
if (mesh._linearVelocity) {
mesh.physicsImpostor.setLinearVelocity(mesh._linearVelocity);
}
if (mesh._angularVelocity) {
mesh.physicsImpostor.setAngularVelocity(mesh._angularVelocity);
}
}
}
}
});
Soft Bodies Physics
Some physics engines like Ammo.js support soft body physics for cloth, jelly-like objects, and deformable surfaces:
// Note: Soft body support depends on the physics engine
// This example uses Ammo.js which has soft body capabilities
// First ensure Ammo.js is properly loaded with soft body support
// <script src="https://cdn.babylonjs.com/ammo.js"></script>
// Create a soft body cloth
async function createSoftBodyCloth() {
// Wait for Ammo initialization
await Ammo();
// Create a cloth mesh (plane with subdivisions)
const cloth = BABYLON.MeshBuilder.CreateGround("cloth", {
width: 5,
height: 5,
subdivisions: 20
}, scene);
cloth.position.y = 5;
// Create material for visualization
const clothMaterial = new BABYLON.StandardMaterial("clothMat", scene);
clothMaterial.diffuseTexture = new BABYLON.Texture("textures/fabric.jpg", scene);
clothMaterial.backFaceCulling = false;
cloth.material = clothMaterial;
// Physics initialization with Ammo.js
scene.enablePhysics(new BABYLON.Vector3(0, -9.81, 0), new BABYLON.AmmoJSPlugin());
// Configure soft body parameters
const softBodyHelperOptions = {
fixedPoints: [0, 1, 20, 21], // Fix the top corners (depends on subdivision)
mass: 1,
pressure: 100,
stiffness: 0.1,
friction: 0.8
};
// Create the soft body impostor with Ammo.js
cloth.physicsImpostor = new BABYLON.PhysicsImpostor(
cloth,
BABYLON.PhysicsImpostor.SoftImpostor,
softBodyHelperOptions,
scene
);
// Add collision with other objects
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {
diameter: 1
}, scene);
sphere.position = new BABYLON.Vector3(0, 2, 0);
sphere.physicsImpostor = new BABYLON.PhysicsImpostor(
sphere,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 2, restitution: 0.7 },
scene
);
// Apply wind force to the cloth
let time = 0;
scene.onBeforeRenderObservable.add(() => {
time += scene.getEngine().getDeltaTime() / 1000;
// Oscillating wind force
const windStrength = 5 + Math.sin(time * 0.5) * 4;
const windDirection = new BABYLON.Vector3(Math.sin(time * 0.2), 0, Math.cos(time * 0.2));
// Apply wind to soft body
cloth.physicsImpostor.applyForce(
windDirection.scale(windStrength),
cloth.position
);
});
}
// Call the function to create the soft body
createSoftBodyCloth();
Debugging Physics
Visualize and debug physics to diagnose issues:
// Enable physics debug visualization
const physicsViewer = new BABYLON.PhysicsViewer(scene);
// Show impostors for specific objects
physicsViewer.showImpostor(box.physicsImpostor, box);
physicsViewer.showImpostor(sphere.physicsImpostor, sphere);
// Visualize all physics impostors in the scene
scene.meshes.forEach(mesh => {
if (mesh.physicsImpostor) {
physicsViewer.showImpostor(mesh.physicsImpostor, mesh);
}
});
// Add debugging UI
const debugLayer = new BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
const panel = new BABYLON.GUI.StackPanel();
panel.width = "220px";
panel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
panel.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
debugLayer.addControl(panel);
// Button to toggle physics debug visibility
const showPhysicsButton = BABYLON.GUI.Button.CreateSimpleButton("showPhysics", "Toggle Physics Debug");
showPhysicsButton.width = "200px";
showPhysicsButton.height = "40px";
showPhysicsButton.color = "white";
showPhysicsButton.background = "green";
showPhysicsButton.onPointerUpObservable.add(() => {
// Toggle all impostor renderers
physicsViewer.isEnabled = !physicsViewer.isEnabled;
});
panel.addControl(showPhysicsButton);
// Button to pause/resume physics
const pausePhysicsButton = BABYLON.GUI.Button.CreateSimpleButton("pausePhysics", "Pause Physics");
pausePhysicsButton.width = "200px";
pausePhysicsButton.height = "40px";
pausePhysicsButton.color = "white";
pausePhysicsButton.background = "blue";
let physicsEnabled = true;
pausePhysicsButton.onPointerUpObservable.add(() => {
if (physicsEnabled) {
// Pause physics
scene.getPhysicsEngine().setTimeStep(0);
pausePhysicsButton.textBlock.text = "Resume Physics";
} else {
// Resume physics
scene.getPhysicsEngine().setTimeStep(1/60);
pausePhysicsButton.textBlock.text = "Pause Physics";
}
physicsEnabled = !physicsEnabled;
});
panel.addControl(pausePhysicsButton);
// Button to reset all physics objects
const resetPhysicsButton = BABYLON.GUI.Button.CreateSimpleButton("resetPhysics", "Reset Physics");
resetPhysicsButton.width = "200px";
resetPhysicsButton.height = "40px";
resetPhysicsButton.color = "white";
resetPhysicsButton.background = "red";
resetPhysicsButton.onPointerUpObservable.add(() => {
// Reset all dynamic objects to their original positions
scene.meshes.forEach(mesh => {
if (mesh.physicsImpostor && mesh.physicsImpostor.mass > 0) {
// Reset position and rotation
if (mesh._originalPosition) {
mesh.position = mesh._originalPosition.clone();
mesh.rotationQuaternion = mesh._originalRotation ?
mesh._originalRotation.clone() : new BABYLON.Quaternion();
// Reset velocities
mesh.physicsImpostor.setLinearVelocity(BABYLON.Vector3.Zero());
mesh.physicsImpostor.setAngularVelocity(BABYLON.Vector3.Zero());
}
}
});
});
panel.addControl(resetPhysicsButton);
// Store original positions for reset functionality
scene.meshes.forEach(mesh => {
if (mesh.physicsImpostor) {
mesh._originalPosition = mesh.position.clone();
mesh._originalRotation = mesh.rotationQuaternion ?
mesh.rotationQuaternion.clone() : null;
}
});
// Log physics activity
let collisionCount = 0;
scene.meshes.forEach(mesh => {
if (mesh.physicsImpostor) {
mesh.physicsImpostor.onCollideEvent = (collider, collidedWith) => {
collisionCount++;
console.log(`Collision #${collisionCount}: ${collider.object.name} hit ${collidedWith.object.name}`);
// Get collision velocity
const relVelocity = collider.getLinearVelocity().subtract(
collidedWith.getLinearVelocity()
).length();
console.log(`Impact velocity: ${relVelocity.toFixed(2)}`);
};
}
});
Common Physics Pitfalls
Avoid common issues when working with physics in Babylon.js:
1. Scale Issues
Problem: Non-uniform scaling can cause physics behavior to be unpredictable.
Solution: Use mesh.scaling.isEqual(BABYLON.Vector3.One()) to check for uniform scaling. If non-uniform scaling is needed, create a properly sized collision mesh as a child of the visual mesh.
// Bad practice: Non-uniform scaling with physics
box.scaling = new BABYLON.Vector3(1, 2, 1);
box.physicsImpostor = new BABYLON.PhysicsImpostor(...);
// Good practice: Use a separate collision mesh
const visualMesh = BABYLON.MeshBuilder.CreateBox("visual", {width: 1, height: 2, depth: 1}, scene);
const collisionMesh = BABYLON.MeshBuilder.CreateBox("collision", {size: 1}, scene);
collisionMesh.scaling = new BABYLON.Vector3(1, 2, 1);
collisionMesh.visibility = 0; // Invisible collision mesh
collisionMesh.physicsImpostor = new BABYLON.PhysicsImpostor(...);
2. Mesh Parenting Issues
Problem: Physics impostors may not work correctly with parented meshes.
Solution: Apply physics to the root mesh or use compound bodies for complex hierarchies.
3. Tunneling (Objects Passing Through Each Other)
Problem: Fast-moving objects may pass through thin objects due to discrete time stepping.
Solution: Use continuous collision detection or increase the number of substeps.
// Enable CCD (Continuous Collision Detection) for fast objects
// Note: Implementation depends on physics engine
sphere.physicsImpostor.physicsBody.setCcdMotionThreshold(1);
sphere.physicsImpostor.physicsBody.setCcdSweptSphereRadius(0.2);
4. Performance Issues with Many Objects
Problem: Too many physics objects can severely impact performance.
Solution: Use instancing, compound bodies, and LOD for physics.
5. Stability Issues with Constraints
Problem: Joints and constraints can become unstable, causing objects to jitter or explode.
Solution: Use appropriate constraint limits, damping, and ensure proper initialization.
// Add damping to stabilize physics objects
box.physicsImpostor.setLinearDamping(0.1);
box.physicsImpostor.setAngularDamping(0.1);
6. Collision Filtering Issues
Problem: Objects collide with things they shouldn't, or don't collide with things they should.
Solution: Use collision groups and masks to control what collides with what.
// Set collision groups (implementation varies by physics engine)
playerMesh.physicsImpostor.physicsBody.collisionFilterGroup = 2; // Player group
playerMesh.physicsImpostor.physicsBody.collisionFilterMask = 1; // Collide only with environment
enemyMesh.physicsImpostor.physicsBody.collisionFilterGroup = 4; // Enemy group
enemyMesh.physicsImpostor.physicsBody.collisionFilterMask = 1; // Collide only with environment
7. Initialization Order Issues
Problem: Physics behaviors depend on proper initialization order.
Solution: Always ensure physics engine is initialized before creating impostors, and impostors are created before joints/constraints.
8. Garbage Collection Pauses
Problem: Creating and destroying many physics objects can trigger garbage collection, causing stutters.
Solution: Reuse physics objects using object pooling, and dispose of unused objects properly.
// Proper disposal of physics objects
function cleanupPhysicsObject(mesh) {
if (mesh.physicsImpostor) {
mesh.physicsImpostor.dispose();
mesh.physicsImpostor = null;
}
mesh.dispose();
}
Meshes and Geometry
Meshes are the 3D objects that populate your scene. Babylon.js provides comprehensive tools for creating, manipulating, and optimizing meshes, from built-in primitive shapes to complex imported models.
Built-in Primitive Meshes
Babylon.js includes a variety of parametric shapes through the MeshBuilder class:
// Create a sphere
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {
diameter: 2,
segments: 32,
updatable: true // Allow geometry to be modified later
}, scene);
// Create a box/cube
const box = BABYLON.MeshBuilder.CreateBox("box", {
size: 2, // Size in all dimensions
width: 3, // Override specific dimensions
height: 1,
depth: 2,
faceColors: [ // Color each face differently
new BABYLON.Color4(1,0,0,1), // red front
new BABYLON.Color4(0,1,0,1), // green back
BABYLON.Color4.Blue(), // blue top
BABYLON.Color4.White(), // white bottom
BABYLON.Color4.Yellow(), // yellow right
BABYLON.Color4.Cyan() // cyan left
]
}, scene);
// Create a plane
const plane = BABYLON.MeshBuilder.CreatePlane("plane", {
width: 10,
height: 5,
sideOrientation: BABYLON.Mesh.DOUBLESIDE // Visible from both sides
}, scene);
// Create a cylinder
const cylinder = BABYLON.MeshBuilder.CreateCylinder("cylinder", {
height: 3,
diameterTop: 1,
diameterBottom: 2,
tessellation: 24, // Number of segments around circumference
subdivisions: 1 // Number of height segments
}, scene);
// Create a torus (donut)
const torus = BABYLON.MeshBuilder.CreateTorus("torus", {
diameter: 5,
thickness: 1,
tessellation: 32
}, scene);
// Create a ground mesh
const ground = BABYLON.MeshBuilder.CreateGround("ground", {
width: 100,
height: 100,
subdivisions: 20, // Increases detail for height maps
updatable: false
}, scene);
Mesh Transformations
Position, rotate, and scale meshes to arrange your scene:
// Position (translation)
box.position = new BABYLON.Vector3(0, 2, 0); // Set absolute position
box.position.y += 1; // Relative position change
// Rotation (in radians)
cylinder.rotation = new BABYLON.Vector3(
0, // X-axis rotation (pitch)
Math.PI / 4, // Y-axis rotation (yaw)
0 // Z-axis rotation (roll)
);
// Alternative rotation with Quaternions (better for complex rotations)
const quaternion = BABYLON.Quaternion.RotationAxis(
new BABYLON.Vector3(1, 1, 0).normalize(), // Axis to rotate around
Math.PI / 3 // Angle in radians
);
sphere.rotationQuaternion = quaternion;
// Scaling
torus.scaling = new BABYLON.Vector3(1.5, 1.5, 1.5); // Uniform scaling
plane.scaling = new BABYLON.Vector3(2, 1, 1); // Non-uniform scaling
// Apply multiple transformations with a matrix
const matrix = BABYLON.Matrix.Compose(
new BABYLON.Vector3(1.2, 0.8, 1.2), // Scaling
BABYLON.Quaternion.RotationYawPitchRoll(0.3, 0, 0), // Rotation
new BABYLON.Vector3(2, 0, -3) // Translation
);
ground.setPivotMatrix(matrix);
Mesh Hierarchies
Create parent-child relationships between meshes:
// Create parent node
const parent = new BABYLON.TransformNode("parent", scene);
parent.position.y = 5;
// Make meshes children of the parent
sphere.parent = parent;
box.parent = parent;
// Position children relative to parent
sphere.position = new BABYLON.Vector3(2, 0, 0); // 2 units right of parent
box.position = new BABYLON.Vector3(-2, 0, 0); // 2 units left of parent
// Rotate the parent (affects all children)
parent.rotation.y = Math.PI / 4;
Imported Meshes
Load 3D models created in external applications:
// Load a single model (glTF or GLB format - recommended)
BABYLON.SceneLoader.ImportMesh(
"", // Names of meshes to import (empty = all)
"./models/", // Path to models
"character.glb", // Filename
scene, // Scene to import into
(meshes, particleSystems, skeletons, animationGroups) => {
// Successfully imported
const character = meshes[0];
character.scaling = new BABYLON.Vector3(0.1, 0.1, 0.1);
// Play animations if available
if (animationGroups.length > 0) {
animationGroups[0].start(true); // true = loop
}
}
);
// Asynchronous loading with await
async function loadModel() {
const result = await BABYLON.SceneLoader.ImportMeshAsync(
"", "./models/", "scene.gltf", scene
);
// Process imported meshes
for (const mesh of result.meshes) {
mesh.checkCollisions = true;
}
// Handle animations
if (result.animationGroups.length > 0) {
const idleAnim = result.animationGroups.find(g => g.name === "Idle");
if (idleAnim) idleAnim.start(true);
}
}
// Load a complete scene (replaces current scene)
BABYLON.SceneLoader.Load("./models/", "environment.glb", engine, (newScene) => {
// newScene is now the active scene
newScene.createDefaultCameraOrLight(true, true, true);
});
Mesh Operations
Combine and modify meshes:
// Merge multiple meshes into one for better performance
const merged = BABYLON.Mesh.MergeMeshes(
[box, sphere, cylinder], // Meshes to merge
true, // Dispose original meshes
true, // Allow different materials
undefined, // Use default world matrix
false, // Don't clone meshes
true // Allow different vertex colors
);
// Create instances for repeated objects with shared geometry
const boxInstance = box.createInstance("boxInstance");
boxInstance.position.x = 5;
// Create many instances efficiently
for (let i = 0; i < 100; i++) {
const instance = box.createInstance("box" + i);
instance.position = new BABYLON.Vector3(
Math.random() * 20 - 10,
Math.random() * 5,
Math.random() * 20 - 10
);
instance.rotation.y = Math.random() * Math.PI * 2;
}
Custom Geometry
Create custom meshes with vertex data:
// Create a custom mesh (triangle)
const customMesh = new BABYLON.Mesh("custom", scene);
// Define vertex data
const vertexData = new BABYLON.VertexData();
// Positions (3 vertices, each with x,y,z coordinates)
vertexData.positions = [
0, 1, 0, // Top vertex
-1, -1, 0, // Bottom left vertex
1, -1, 0 // Bottom right vertex
];
// Indices (defines triangles using position indices)
vertexData.indices = [0, 1, 2]; // Counter-clockwise winding
// Normals (perpendicular vectors for lighting calculations)
vertexData.normals = [
0, 0, 1,
0, 0, 1,
0, 0, 1
];
// UV coordinates for texturing
vertexData.uvs = [
0.5, 0, // Top vertex UV
0, 1, // Bottom left UV
1, 1 // Bottom right UV
];
// Apply the vertex data to the mesh
vertexData.applyToMesh(customMesh);
Mesh Optimization
Optimize meshes for better performance:
// Freeze transformations (improves performance)
box.freezeWorldMatrix();
// Convert to a thin instance mesh (very efficient for many copies)
const matrix1 = BABYLON.Matrix.Translation(3, 0, 0);
const matrix2 = BABYLON.Matrix.Translation(-3, 0, 0);
const matrix3 = BABYLON.Matrix.Translation(0, 0, 3);
sphere.thinInstanceSetBuffer("matrix", [matrix1, matrix2, matrix3]);
// Level of Detail (LOD)
const highDetailSphere = BABYLON.MeshBuilder.CreateSphere("highDetail", {
segments: 32,
diameter: 2
}, scene);
const mediumDetailSphere = BABYLON.MeshBuilder.CreateSphere("mediumDetail", {
segments: 16,
diameter: 2
}, scene);
const lowDetailSphere = BABYLON.MeshBuilder.CreateSphere("lowDetail", {
segments: 8,
diameter: 2
}, scene);
// Add LOD levels
highDetailSphere.addLODLevel(30, mediumDetailSphere); // Switch at 30 units distance
highDetailSphere.addLODLevel(60, lowDetailSphere); // Switch at 60 units distance
highDetailSphere.addLODLevel(100, null); // Hide beyond 100 units
// Only the highDetailSphere needs to be visible in the scene
mediumDetailSphere.isVisible = false;
lowDetailSphere.isVisible = false;
Mesh Advanced Features in Babylon.js 7
Babylon.js 7 introduces improved mesh features:
// Enable mesh tessellation (dynamic subdivision)
ground.enableTessellation = true;
ground.tessellationProperties = {
maxSubdivisions: 8, // Maximum subdivision level
distanceFunction: (x, y, z, camera) => {
// Custom function to determine subdivision based on distance
const distanceToCamera = BABYLON.Vector3.Distance(
new BABYLON.Vector3(x, y, z),
camera.position
);
return Math.max(1, 8 - Math.floor(distanceToCamera / 10));
}
};
// GPU instancing with custom attributes
const buffer = new Float32Array(4 * 100); // 100 instances, 4 values each
for (let i = 0; i < 100; i++) {
// Custom color and scale for each instance
buffer[i*4] = Math.random(); // R
buffer[i*4+1] = Math.random(); // G
buffer[i*4+2] = Math.random(); // B
buffer[i*4+3] = 0.5 + Math.random() * 0.5; // Scale
}
sphere.thinInstanceSetBuffer("color", buffer, 4);
// Create vertex shader that uses the buffer
const shader = new BABYLON.ShaderMaterial(/* shader code that accesses custom attributes */);
sphere.material = shader;
These mesh capabilities in Babylon.js 7 provide powerful tools for creating and optimizing 3D objects, from simple primitives to complex imported models, with performance optimizations for both desktop and mobile platforms.
Materials and Textures
Materials define the visual appearance of meshes, controlling properties like color, reflectivity, and texture. Babylon.js offers a comprehensive material system that supports everything from simple colored surfaces to physically-based rendering.
Standard Material
The StandardMaterial is a versatile, performance-focused material for basic rendering needs:
// Create a standard material
const material = new BABYLON.StandardMaterial("material", scene);
// Basic color properties
material.diffuseColor = new BABYLON.Color3(1, 0, 0); // Red diffuse (main color)
material.specularColor = new BABYLON.Color3(1, 1, 1); // White specular (highlights)
material.emissiveColor = new BABYLON.Color3(0, 0, 0.2); // Slight blue emission
material.ambientColor = new BABYLON.Color3(0.1, 0.1, 0.1); // Ambient lighting contribution
// Shininess and specularity
material.specularPower = 32; // Sharpness of specular highlights (higher = sharper)
// Transparency
material.alpha = 0.8; // 80% opaque
// Apply material to a mesh
sphere.material = material;
PBR Materials
Physically Based Rendering (PBR) materials create realistic surfaces following physical principles of light interaction:
// Create a PBR material
const pbr = new BABYLON.PBRMaterial("pbr", scene);
// Base properties
pbr.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5); // Base color (like diffuse)
pbr.metallic = 0.7; // 0 = dielectric (plastic), 1 = metal
pbr.roughness = 0.2; // 0 = smooth, 1 = rough
// Optional properties
pbr.subSurface.isTranslucencyEnabled = true; // Enable subsurface scattering
pbr.subSurface.translucencyIntensity = 0.8; // Intensity of scattering
// Apply material
sphere.material = pbr;
Material Examples
Here are examples of creating realistic materials with PBR:
// Create a material that looks like gold
const gold = new BABYLON.PBRMaterial("gold", scene);
gold.albedoColor = new BABYLON.Color3(1.0, 0.766, 0.336);
gold.metallic = 1.0; // Fully metallic
gold.roughness = 0.1; // Fairly smooth
gold.environmentIntensity = 0.8; // Strength of reflections
// Create a glass material
const glass = new BABYLON.PBRMaterial("glass", scene);
glass.albedoColor = new BABYLON.Color3(0.85, 0.85, 0.9);
glass.alpha = 0.2; // Mostly transparent
glass.metallic = 0.0; // Not metallic
glass.roughness = 0.0; // Very smooth
glass.environmentIntensity = 0.9; // Strong reflections
glass.indexOfRefraction = 1.5; // Like real glass
glass.subSurface.isRefractionEnabled = true; // Enable refraction
Textures
Textures add detailed surface information to materials:
// Load a basic texture
const diffuseTexture = new BABYLON.Texture("textures/wood.jpg", scene);
material.diffuseTexture = diffuseTexture;
// Configure texture properties
diffuseTexture.uScale = 2; // Repeat texture horizontally 2 times
diffuseTexture.vScale = 2; // Repeat texture vertically 2 times
diffuseTexture.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE; // Mirror wrapping horizontally
diffuseTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE; // Clamp edges vertically
Multiple Texture Maps
Add additional texture maps for detailed surface information:
// Add additional texture maps
material.bumpTexture = new BABYLON.Texture("textures/woodNormal.png", scene); // Normal map
material.ambientTexture = new BABYLON.Texture("textures/woodAO.jpg", scene); // Ambient occlusion
material.specularTexture = new BABYLON.Texture("textures/woodSpecular.jpg", scene); // Specular map
// Adjust normal map intensity
material.bumpTexture.level = 1.5; // Intensity of normal mapping
PBR Texture Workflow
Using textures with PBR materials for realistic rendering:
// Using textures with PBR materials
pbr.albedoTexture = new BABYLON.Texture("textures/metal/albedo.png", scene);
pbr.metallicTexture = new BABYLON.Texture("textures/metal/metallic.png", scene);
pbr.roughnessTexture = new BABYLON.Texture("textures/metal/roughness.png", scene);
pbr.normalTexture = new BABYLON.Texture("textures/metal/normal.png", scene);
pbr.ambientOcclusionTexture = new BABYLON.Texture("textures/metal/ao.png", scene);
// Using a single channel from a texture
pbr.useRoughnessFromMetallicTextureAlpha = false;
pbr.useRoughnessFromMetallicTextureGreen = true; // Use green channel for roughness
pbr.useMetallicFromMetallicTextureBlue = true; // Use blue channel for metallic
Environment Mapping
Add environment reflections to materials:
// Environment mapping for reflections
const envTexture = new BABYLON.CubeTexture("textures/environment/skybox", scene);
pbr.environmentTexture = envTexture;
Procedural Textures
Generate textures programmatically:
// Create a wood procedural texture
const woodTexture = new BABYLON.WoodProceduralTexture("woodTex", 512, scene);
woodTexture.ampScale = 100.0;
woodTexture.woodColor = new BABYLON.Color3(0.49, 0.25, 0.08);
material.diffuseTexture = woodTexture;
Custom Procedural Textures
Create custom procedural textures with shaders:
// Create a custom procedural texture with a fragment shader
const customProceduralTexture = new BABYLON.ProceduralTexture(
"customTex",
512, // Size
"customShader", // Shader name
scene,
null,
true, // Generate mipmaps
true // Is fragment only
);
// Set shader parameters
customProceduralTexture.setFloat("time", 0);
customProceduralTexture.setVector2("resolution", new BABYLON.Vector2(512, 512));
// Update time in the render loop
scene.onBeforeRenderObservable.add(() => {
customProceduralTexture.setFloat("time", performance.now() / 1000);
});
Material Management
Efficiently manage multiple materials:
Material Instances and Cloning
// Clone a material
const material2 = material.clone("material2");
material2.diffuseColor = new BABYLON.Color3(0, 1, 0); // Change color to green
// Create material instances (shares resources but allows property overrides)
const materialInstance = material.instantiateForInstance();
materialInstance.diffuseColor = new BABYLON.Color3(0, 0, 1); // Blue variant
Advanced Material Effects
Create special visual effects with materials:
Fresnel Effects
// Fresnel effect (edge glow)
material.diffuseFresnelParameters = new BABYLON.FresnelParameters();
material.diffuseFresnelParameters.leftColor = BABYLON.Color3.White();
material.diffuseFresnelParameters.rightColor = BABYLON.Color3.Blue();
material.diffuseFresnelParameters.power = 2;
material.diffuseFresnelParameters.bias = 0.1;
// Emissive Fresnel (good for energy shields or holograms)
material.emissiveFresnelParameters = new BABYLON.FresnelParameters();
material.emissiveFresnelParameters.leftColor = BABYLON.Color3.Black();
material.emissiveFresnelParameters.rightColor = BABYLON.Color3.Green();
material.emissiveFresnelParameters.power = 4;
material.emissiveFresnelParameters.bias = 0.5;
Culling and Lighting Options
// Back face culling (don't render backside of mesh)
material.backFaceCulling = true;
// Two-sided material
material.backFaceCulling = false;
material.twoSidedLighting = true;
// Disable lighting calculation (useful for UI elements)
material.disableLighting = true;
Material Library
Babylon.js 7 includes pre-built materials for common effects:
Gradient Material
// Import the materials library
// In HTML: <script src="https://cdn.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
// Or in modules: import { GradientMaterial } from 'babylonjs-materials';
// Gradient material
const gradientMaterial = new BABYLON.GradientMaterial("gradient", scene);
gradientMaterial.topColor = BABYLON.Color3.Blue();
gradientMaterial.bottomColor = BABYLON.Color3.White();
gradientMaterial.offset = 0.5;
Fire Material
// Fire material
const fireMaterial = new BABYLON.FireMaterial("fire", scene);
fireMaterial.diffuseTexture = new BABYLON.Texture("textures/fire.png", scene);
fireMaterial.distortionTexture = new BABYLON.Texture("textures/distortion.png", scene);
fireMaterial.speed = 5.0;
Cell Shading Material
// Cell (toon) shading material
const cellMaterial = new BABYLON.CellMaterial("cell", scene);
cellMaterial.diffuseColor = new BABYLON.Color3(0.8, 0.4, 0.4);
cellMaterial.computeHighLevel = true;
cellMaterial.diffuseTexture = new BABYLON.Texture("textures/amiga.jpg", scene);
Node Material Editor
Babylon.js 7 includes an enhanced Node Material Editor for visual material creation:
// Load a material created in the Node Material Editor
BABYLON.NodeMaterial.ParseFromFileAsync("", "materials/custom.json", scene).then(nodeMaterial => {
sphere.material = nodeMaterial;
// Access and animate material properties
const timeBlock = nodeMaterial.getBlockByName("Time");
scene.onBeforeRenderObservable.add(() => {
timeBlock.value = performance.now() / 1000;
});
});
These material capabilities in Babylon.js 7 provide extensive control over visual appearance, from basic colored surfaces to complex physically-based materials with realistic light interaction.
Animation System
Babylon.js provides a powerful animation system that enables smooth transitions and movements for all scene objects. From simple property changes to complex character animations, the framework offers comprehensive tools for bringing your 3D world to life.
Basic Animation
Create animations by defining keyframes for any object property:
// Create a box to animate
const box = BABYLON.MeshBuilder.CreateBox("box", {size: 1}, scene);
// Create an animation for position
const animation = new BABYLON.Animation(
"positionAnimation", // Name
"position.y", // Property to animate
30, // Frames per second
BABYLON.Animation.ANIMATIONTYPE_FLOAT, // Type of value
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE // Loop mode
);
// Define keyframes
const keyFrames = [];
keyFrames.push({ frame: 0, value: 0 }); // Start at y=0
keyFrames.push({ frame: 30, value: 3 }); // Move to y=3 at frame 30
keyFrames.push({ frame: 60, value: 0 }); // Back to y=0 at frame 60
// Assign keyframes to animation
animation.setKeys(keyFrames);
// Attach animation to the box
box.animations = [animation];
// Start the animation
scene.beginAnimation(box, 0, 60, true); // true = loop
Easing Functions
Add natural motion with easing functions:
// Create an easing function
const easingFunction = new BABYLON.CircleEase();
easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
// Apply to animation
animation.setEasingFunction(easingFunction);
Available easing types: BABYLON.BackEase, BABYLON.BounceEase, BABYLON.CircleEase, BABYLON.CubicEase, BABYLON.ElasticEase, BABYLON.ExponentialEase, BABYLON.PowerEase, BABYLON.QuadraticEase, BABYLON.QuarticEase, BABYLON.QuinticEase, BABYLON.SineEase
Animation Blending
Smoothly transition between animations:
// Create a second animation
const animation2 = new BABYLON.Animation(
"scaleAnimation",
"scaling",
30,
BABYLON.Animation.ANIMATIONTYPE_VECTOR3,
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);
// Define keyframes for scaling
const scaleKeyFrames = [];
scaleKeyFrames.push({ frame: 0, value: new BABYLON.Vector3(1, 1, 1) });
scaleKeyFrames.push({ frame: 30, value: new BABYLON.Vector3(2, 0.5, 2) });
scaleKeyFrames.push({ frame: 60, value: new BABYLON.Vector3(1, 1, 1) });
animation2.setKeys(scaleKeyFrames);
// Start with position animation
const animatable = scene.beginAnimation(box, 0, 60, true);
// Later, blend to scale animation
setTimeout(() => {
box.animations = [animation2];
scene.beginAnimation(box, 0, 60, true, 1.0); // 1.0 = blend speed
}, 3000);
Animation Groups
Synchronize multiple animations:
// Create an animation group
const animationGroup = new BABYLON.AnimationGroup("myGroup");
// Add animations to the group
animationGroup.addTargetedAnimation(animation, box);
Adding Multiple Animations
// Create and add rotation animation
const rotationAnimation = new BABYLON.Animation(
"rotationAnimation",
"rotation.y",
30,
BABYLON.Animation.ANIMATIONTYPE_FLOAT,
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);
const rotationKeys = [];
rotationKeys.push({ frame: 0, value: 0 });
rotationKeys.push({ frame: 60, value: Math.PI * 2 });
rotationAnimation.setKeys(rotationKeys);
animationGroup.addTargetedAnimation(rotationAnimation, box);
Animation Group Control
// Control the animation group
animationGroup.play(true); // true = loop
// animationGroup.pause();
// animationGroup.stop();
// animationGroup.reset();
// Adjust speed
animationGroup.speedRatio = 0.5; // Half speed
// Animation group events
animationGroup.onAnimationEndObservable.add(() => {
console.log("Animation group completed");
});
Skeletal Animation
Work with rigged character animations:
// Import a rigged model
BABYLON.SceneLoader.ImportMesh("", "models/", "character.glb", scene, (meshes, particleSystems, skeletons, animationGroups) => {
const character = meshes[0];
const skeleton = skeletons[0];
// Play an animation by name
const idleAnim = animationGroups.find(a => a.name === "Idle");
const walkAnim = animationGroups.find(a => a.name === "Walking");
const runAnim = animationGroups.find(a => a.name === "Running");
if (idleAnim) idleAnim.play(true);
Animation Switching
Switch between animations with blending:
// Later, switch animations with blending
document.getElementById("walk").addEventListener("click", () => {
idleAnim.stop();
walkAnim.play(true);
walkAnim.speedRatio = 1.0;
});
document.getElementById("run").addEventListener("click", () => {
walkAnim.stop();
runAnim.play(true);
runAnim.speedRatio = 1.5;
});
Bone Manipulation
Access skeleton bones for fine control:
// Access skeleton bones for fine control
const rightArm = skeleton.bones.find(b => b.name === "RightArm");
if (rightArm) {
// Override bone rotation
rightArm.rotation.z += Math.PI / 8;
}
});
Morph Targets
Animate between different mesh shapes:
// Import a model with morph targets
BABYLON.SceneLoader.ImportMesh("", "models/", "face.glb", scene, (meshes) => {
const face = meshes[0];
// Access morph targets
const morphTargetManager = face.morphTargetManager;
if (morphTargetManager) {
// Get specific morph targets
const smileTarget = morphTargetManager.getTarget(0);
const frownTarget = morphTargetManager.getTarget(1);
const blinkTarget = morphTargetManager.getTarget(2);
Animating Morph Targets
// Animate morph target influences
let time = 0;
scene.onBeforeRenderObservable.add(() => {
time += scene.getEngine().getDeltaTime() / 1000;
// Smile-frown cycle
smileTarget.influence = Math.sin(time * 0.5) * 0.5 + 0.5;
frownTarget.influence = Math.sin(time * 0.5 + Math.PI) * 0.5 + 0.5;
// Occasional blink
blinkTarget.influence = Math.pow(Math.sin(time * 3), 16);
});
}
});
Animation Events
Trigger events at specific points in animations:
// Create an animation with events
const jumpAnimation = new BABYLON.Animation(
"jumpAnimation",
"position.y",
60,
BABYLON.Animation.ANIMATIONTYPE_FLOAT,
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);
const jumpKeys = [];
jumpKeys.push({ frame: 0, value: 0 });
jumpKeys.push({ frame: 30, value: 5 });
jumpKeys.push({ frame: 60, value: 0 });
jumpAnimation.setKeys(jumpKeys);
Adding Animation Events
// Add events at specific frames
jumpAnimation.addEvent(new BABYLON.AnimationEvent(
10, // Frame number
function() {
// Event callback
console.log("Starting to jump");
// Play sound
const jumpSound = new BABYLON.Sound("jump", "sounds/jump.mp3", scene);
jumpSound.play();
}
));
jumpAnimation.addEvent(new BABYLON.AnimationEvent(
50, // Frame number
function() {
console.log("Landing");
// Play landing sound
const landSound = new BABYLON.Sound("land", "sounds/land.mp3", scene);
landSound.play();
}
));
// Apply and play
box.animations = [jumpAnimation];
scene.beginAnimation(box, 0, 60, true);
Procedural Animation
Create animations directly in code:
// Animate in the render loop
let time = 0;
scene.onBeforeRenderObservable.add(() => {
time += scene.getEngine().getDeltaTime() / 1000;
// Circular motion
sphere.position.x = Math.cos(time) * 5;
sphere.position.z = Math.sin(time) * 5;
// Bobbing motion
sphere.position.y = 2 + Math.sin(time * 2) * 0.5;
// Continuous rotation
box.rotation.y += 0.01;
box.rotation.x = Math.sin(time) * 0.2;
});
Babylon.js 7 Animation Improvements
Babylon.js 7 includes enhanced animation capabilities:
Animation Curve Editor
// Animation curve editor integration
const curveEditor = new BABYLON.AnimationCurveEditor();
curveEditor.addAnimation(animation);
Animation State Machine
// Animation state machine
const stateMachine = new BABYLON.AnimationStateMachine("characterStates", scene);
// Define states
const idleState = new BABYLON.AnimationState("idle", idleAnim);
const walkState = new BABYLON.AnimationState("walk", walkAnim);
const runState = new BABYLON.AnimationState("run", runAnim);
// Add states to state machine
stateMachine.addState(idleState);
stateMachine.addState(walkState);
stateMachine.addState(runState);
State Transitions
// Define transitions between states
stateMachine.addTransition(idleState, walkState, "startWalking");
stateMachine.addTransition(walkState, runState, "startRunning");
stateMachine.addTransition(runState, walkState, "slowDown");
stateMachine.addTransition(walkState, idleState, "stop");
// Start with idle state
stateMachine.setCurrentState("idle");
// Later, trigger transitions
document.getElementById("walkButton").addEventListener("click", () => {
stateMachine.trigger("startWalking");
});
document.getElementById("runButton").addEventListener("click", () => {
stateMachine.trigger("startRunning");
});