BabylonJS - Mesh
BabylonJS - Working with Meshes
Section titled “BabylonJS - Working with Meshes”This chapter explores creating various 3D shapes, known as meshes, in BabylonJS. We’ll primarily use the modern MeshBuilder API, which offers a convenient and powerful way to generate standard geometric shapes.
MeshBuilder provides methods like CreateBox, CreateSphere, CreateCylinder, etc., allowing you to easily define dimensions, subdivisions, and other properties. It also simplifies applying materials and textures compared to older methods.
Creating a Box with MeshBuilder
Section titled “Creating a Box with MeshBuilder”Let’s start by creating a simple box (cube) mesh using MeshBuilder.CreateBox.
Basic Box Example
Section titled “Basic Box Example”import * as BABYLON from '@babylonjs/core';import '@babylonjs/loaders'; // If loading assets
const canvas = document.getElementById('renderCanvas');const engine = new BABYLON.Engine(canvas, true);
const createScene = async function() { const scene = new BABYLON.Scene(engine); scene.clearColor = new BABYLON.Color4(0.1, 0.1, 0.2, 1.0); // Dark blue background
// Create a camera const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 10, BABYLON.Vector3.Zero(), scene); camera.attachControl(canvas, true);
// Create a light const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0), scene); light.intensity = 0.8;
// Create a default PBR material const boxMaterial = new BABYLON.PBRMaterial("boxMat", scene); boxMaterial.albedoColor = new BABYLON.Color3(0, 1, 0); // Green color boxMaterial.metallic = 0.1; boxMaterial.roughness = 0.7;
// Define box options (optional) const boxOptions = { size: 2 // Creates a 2x2x2 box // width: 1.5, // height: 2, // depth: 1, };
// Create the box mesh const box = BABYLON.MeshBuilder.CreateBox("myBox", boxOptions, scene); box.material = boxMaterial; box.position.y = 1; // Position it slightly above the origin
// Add a ground plane (optional) const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 10, height: 10}, scene); const groundMaterial = new BABYLON.PBRMaterial("groundMat", scene); groundMaterial.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5); groundMaterial.metallic = 0; groundMaterial.roughness = 0.9; ground.material = groundMaterial;
return scene;};
createScene().then(scene => { engine.runRenderLoop(function() { if (scene) { scene.render(); } });});
// Handle window resizewindow.addEventListener('resize', function() { engine.resize();});This code sets up a basic scene with a camera, light, and then creates a green box using MeshBuilder.CreateBox. We use a PBRMaterial (Physically Based Rendering), which is the modern standard for realistic materials, providing properties like metallic and roughness. The async function pattern is used for createScene to handle potential asynchronous operations like loading assets in the future.
Applying Textures (UV Mapping)
Section titled “Applying Textures (UV Mapping)”To apply an image texture to a mesh, you need to define how the 2D image coordinates (U, V) map to the 3D surface of the mesh. MeshBuilder handles basic UV mapping automatically for standard shapes.
Let’s modify the previous example to add a texture:
// Inside createScene function...
// Create a PBR material for the textured boxconst texturedBoxMat = new BABYLON.PBRMaterial("texturedBoxMat", scene);texturedBoxMat.metallic = 0.1;texturedBoxMat.roughness = 0.8;
// Load the textureconst texture = new BABYLON.Texture("path/to/your/texture.jpg", scene);texturedBoxMat.albedoTexture = texture;
// Box options (size is often sufficient)const boxOptions = { size: 2};
// Create the box meshconst texturedBox = BABYLON.MeshBuilder.CreateBox("texturedBox", boxOptions, scene);texturedBox.material = texturedBoxMat;texturedBox.position.y = 1;
// ... rest of the scene setup ...Here, we create a BABYLON.Texture object pointing to our image file and assign it to the albedoTexture property of the PBRMaterial. This property defines the base color map for the material.
Advanced UV Mapping (Sprite Sheets / Atlas)
Section titled “Advanced UV Mapping (Sprite Sheets / Atlas)”Sometimes, you might use a single image file containing multiple smaller images (a sprite sheet or texture atlas). To map specific parts of this atlas to different faces of a mesh (like a box), you can use the faceUV option.
faceUV expects an array of BABYLON.Vector4 objects, one for each face of the mesh (6 for a box). Each Vector4(x, y, z, w) defines the rectangular region of the texture to use for that face, where:
- x: U coordinate of the bottom-left corner (0.0 to 1.0)
- y: V coordinate of the bottom-left corner (0.0 to 1.0)
- z: U coordinate of the top-right corner (0.0 to 1.0)
- w: V coordinate of the top-right corner (0.0 to 1.0)
Imagine your texture atlas is divided into a grid. If you have a 3x2 grid (3 columns, 2 rows):
// Inside createScene function...
const atlasTexture = new BABYLON.Texture("path/to/your/atlas.png", scene);
const atlasMat = new BABYLON.PBRMaterial("atlasMat", scene);atlasMat.albedoTexture = atlasTexture;atlasMat.metallic = 0;atlasMat.roughness = 1.0; // Less reflective for clear texture view
const hSpriteNb = 3; // Number of horizontal sprites (columns)const vSpriteNb = 2; // Number of vertical sprites (rows)
const faceUV = new Array(6);
// Example: Map the first 6 sprites from the 3x2 grid to the 6 faces of the box// Face 0: Top-left sprite (col 0, row 0)faceUV[0] = new BABYLON.Vector4(0 / hSpriteNb, 0 / vSpriteNb, (0 + 1) / hSpriteNb, (0 + 1) / vSpriteNb);// Face 1: Middle-top sprite (col 1, row 0)faceUV[1] = new BABYLON.Vector4(1 / hSpriteNb, 0 / vSpriteNb, (1 + 1) / hSpriteNb, (0 + 1) / vSpriteNb);// Face 2: Right-top sprite (col 2, row 0)faceUV[2] = new BABYLON.Vector4(2 / hSpriteNb, 0 / vSpriteNb, (2 + 1) / hSpriteNb, (0 + 1) / vSpriteNb);// Face 3: Bottom-left sprite (col 0, row 1)faceUV[3] = new BABYLON.Vector4(0 / hSpriteNb, 1 / vSpriteNb, (0 + 1) / hSpriteNb, (1 + 1) / vSpriteNb);// Face 4: Bottom-middle sprite (col 1, row 1)faceUV[4] = new BABYLON.Vector4(1 / hSpriteNb, 1 / vSpriteNb, (1 + 1) / hSpriteNb, (1 + 1) / vSpriteNb);// Face 5: Bottom-right sprite (col 2, row 1)faceUV[5] = new BABYLON.Vector4(2 / hSpriteNb, 1 / vSpriteNb, (2 + 1) / hSpriteNb, (1 + 1) / vSpriteNb);
const atlasBoxOptions = { width: 2, height: 2, depth: 2, faceUV: faceUV};
const atlasBox = BABYLON.MeshBuilder.CreateBox("atlasBox", atlasBoxOptions, scene);atlasBox.material = atlasMat;atlasBox.position.y = 1;
// ... rest of scene setup ...This technique requires careful calculation of UV coordinates based on your atlas layout. Tools exist to help generate these coordinates automatically from texture packing software.
Creating a Cylinder
Section titled “Creating a Cylinder”Creating other shapes like cylinders is similar, using the corresponding MeshBuilder method.
Cylinder Example
Section titled “Cylinder Example”// Inside createScene function...
// Material for the cylinder (e.g., wireframe)const cylinderMat = new BABYLON.PBRMaterial("cylinderMat", scene);cylinderMat.albedoColor = new BABYLON.Color3(0.8, 0.8, 1.0); // Light bluecylinderMat.wireframe = true; // Show wireframe
// Cylinder optionsconst cylinderOptions = { height: 4, diameterTop: 1, diameterBottom: 2, tessellation: 24 // Number of segments around the circumference};
const cylinder = BABYLON.MeshBuilder.CreateCylinder("myCylinder", cylinderOptions, scene);cylinder.material = cylinderMat;cylinder.position = new BABYLON.Vector3(-3, 2, 0); // Position it
// ... rest of scene setup ...Here, CreateCylinder takes options like height, diameterTop, diameterBottom (allowing for cones if diameters differ), and tessellation (controlling smoothness). We’ve applied a wireframe material for visualization.
Explore the BabylonJS documentation for other MeshBuilder methods like CreateSphere, CreatePlane, CreateTorus, CreateIcoSphere, etc., and their respective options.
Mesh Interactions: Intersection Checks
Section titled “Mesh Interactions: Intersection Checks”Detecting when meshes overlap (intersect) is crucial for game logic, physics, and interactions. BabylonJS provides the intersectsMesh method.
Intersection Example
Section titled “Intersection Example”// Inside createScene function...
// Create a target mesh (e.g., a static box)const targetBox = BABYLON.MeshBuilder.CreateBox("target", {size: 3}, scene);targetBox.position = new BABYLON.Vector3(5, 1.5, 0);const targetMat = new BABYLON.PBRMaterial("targetMat", scene);targetMat.albedoColor = new BABYLON.Color3(1, 0.5, 0); // OrangetargetBox.material = targetMat;
// Create moving spheresconst sphereMat = new BABYLON.PBRMaterial("sphereMat", scene);sphereMat.albedoColor = new BABYLON.Color3(0, 1, 1); // Cyan
const sphere1 = BABYLON.MeshBuilder.CreateSphere("sphere1", {diameter: 1}, scene);sphere1.material = sphereMat;sphere1.position = new BABYLON.Vector3(0, 1, 0);
// Animation / Movement Logiclet angle = 0;scene.onBeforeRenderObservable.add(() => { // Move sphere1 towards the target box sphere1.position.x += 0.02;
// Check for intersection if (sphere1.intersectsMesh(targetBox, false)) { // Precise check = false (uses bounding boxes first, faster) // Precise check = true (uses geometry, slower but more accurate) sphere1.material.emissiveColor = new BABYLON.Color3(1, 0, 0); // Glow red on intersect targetBox.material.emissiveColor = new BABYLON.Color3(1, 0, 0); } else { sphere1.material.emissiveColor = BABYLON.Color3.Black(); // Reset glow targetBox.material.emissiveColor = BABYLON.Color3.Black(); }
// Simple rotation example (unrelated to intersection) targetBox.rotation.y += 0.01;});
// ... rest of scene setup ...In this example, sphere1 moves towards targetBox. Inside the scene.onBeforeRenderObservable, which runs every frame before rendering, we call sphere1.intersectsMesh(targetBox, false). The second parameter (false) indicates a less precise but faster check using bounding boxes. Setting it to true performs a more accurate check based on the actual mesh geometry. We change the emissiveColor (making the objects glow) to visually indicate intersection.
Intersecting with a Point
Section titled “Intersecting with a Point”You can also check if a specific point in 3D space lies within a mesh’s bounds using intersectsPoint.
// Inside scene.onBeforeRenderObservable...
const checkPoint = new BABYLON.Vector3(5.1, 1.6, 0.1); // A point near the target box
// Visualize the point (optional)// const pointMarker = BABYLON.MeshBuilder.CreateSphere("marker", {diameter: 0.2}, scene);// pointMarker.position = checkPoint;// pointMarker.material = new BABYLON.StandardMaterial("markerMat", scene);// pointMarker.material.emissiveColor = BABYLON.Color3.Yellow();
if (targetBox.intersectsPoint(checkPoint)) { console.log("Point is inside the target box!"); // You could trigger an action here}mesh.intersectsPoint(vector3) returns true if the given BABYLON.Vector3 is inside the mesh’s bounding box, false otherwise.
Mesh Picking (Mouse Interaction)
Section titled “Mesh Picking (Mouse Interaction)”Picking allows you to determine which mesh (if any) is under the mouse cursor. This is fundamental for user interaction, like selecting objects or placing items in a scene.
BabylonJS uses the scene.onPointerObservable for handling pointer events (mouse clicks, taps, moves).
Picking Example
Section titled “Picking Example”// Inside createScene function...
// Create a ground plane to click onconst ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 10, height: 10}, scene);const groundMat = new BABYLON.PBRMaterial("groundMat", scene);groundMat.albedoColor = new BABYLON.Color3(0.3, 0.5, 0.3); // Greenishground.material = groundMat;
// Create a small sphere to place on clickconst placementSphere = BABYLON.MeshBuilder.CreateSphere("placementSphere", {diameter: 0.5}, scene);const placementMat = new BABYLON.PBRMaterial("placementMat", scene);placementMat.albedoColor = new BABYLON.Color3(1, 0, 0); // RedplacementSphere.material = placementMat;placementSphere.position = new BABYLON.Vector3(0, -10, 0); // Initially hidden
// Pointer observable for clicksscene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case BABYLON.PointerEventTypes.POINTERDOWN: console.log("Pointer Down"); // Perform a pick operation const pickResult = scene.pick(scene.pointerX, scene.pointerY);
if (pickResult.hit) { console.log("Hit mesh:", pickResult.pickedMesh.name); console.log("Hit point coordinates:", pickResult.pickedPoint);
// If we hit the ground, move the placement sphere to the hit point if (pickResult.pickedMesh.name === "ground") { placementSphere.position = pickResult.pickedPoint.clone(); // Use clone() placementSphere.position.y += 0.25; // Place it slightly above the ground } } break;
case BABYLON.PointerEventTypes.POINTERUP: console.log("Pointer Up"); break;
case BABYLON.PointerEventTypes.POINTERMOVE: // Can be used for hover effects, drag and drop, etc. break; }});
// ... rest of scene setup ...We add an observer to scene.onPointerObservable. When a POINTERDOWN event occurs, we call scene.pick(scene.pointerX, scene.pointerY) to get information about what’s under the cursor at that screen coordinate. The pickResult object contains:
hit: A boolean indicating if a mesh was hit.pickedMesh: The mesh that was hit (ifhitis true).pickedPoint: AVector3representing the exact 3D coordinate on the surface of thepickedMeshwhere the hit occurred.
In the example, if the ground is clicked (pickResult.pickedMesh.name === "ground"), we move the red placementSphere to the pickedPoint. Note the use of .clone() when assigning the position to avoid modifying the original pickedPoint vector indirectly.
Raycasting
Section titled “Raycasting”Raycasting involves projecting an invisible line (a ray) from a point in a specific direction and detecting which meshes it intersects. It’s used for things like AI line-of-sight, simulated projectiles, or more complex picking scenarios.
You create a BABYLON.Ray object and use scene.pickWithRay.
Raycasting Example
Section titled “Raycasting Example”// Inside createScene function...
// A central box that will cast the rayconst casterBox = BABYLON.MeshBuilder.CreateBox("casterBox", {size: 1}, scene);casterBox.position.y = 0.5;const casterMat = new BABYLON.PBRMaterial("casterMat", scene);casterMat.albedoColor = new BABYLON.Color3(0.5, 0.5, 1); // BluecasterBox.material = casterMat;casterBox.isPickable = false; // Usually, the caster itself shouldn't be picked by its own ray
// Some target boxesconst targetMatRed = new BABYLON.PBRMaterial("targetRed", scene);targetMatRed.albedoColor = new BABYLON.Color3(1, 0, 0);const targetMatGreen = new BABYLON.PBRMaterial("targetGreen", scene);targetMatGreen.albedoColor = new BABYLON.Color3(0, 1, 0);
const target1 = BABYLON.MeshBuilder.CreateBox("target1", {size: 2}, scene);target1.position = new BABYLON.Vector3(5, 1, 0);target1.material = targetMatRed;
const target2 = BABYLON.MeshBuilder.CreateBox("target2", {size: 2}, scene);target2.position = new BABYLON.Vector3(-5, 1, 0);target2.material = targetMatGreen;
// Ray and RayHelper for visualizationconst ray = new BABYLON.Ray();const rayHelper = new BABYLON.RayHelper(ray);rayHelper.show(scene, new BABYLON.Color3(1, 1, 0)); // Yellow ray visualization
scene.onBeforeRenderObservable.add(() => { // Make the caster box rotate casterBox.rotation.y += 0.01;
// Define the ray properties relative to the caster box const origin = casterBox.position;
// Get the forward direction based on the caster's rotation const forward = new BABYLON.Vector3(0, 0, 1); // Local forward Z const worldMatrix = casterBox.getWorldMatrix(); const direction = BABYLON.Vector3.TransformNormal(forward, worldMatrix).normalize();
const length = 10; // How far the ray should travel
// Update the ray object ray.origin = origin; ray.direction = direction; ray.length = length;
// Perform the raycast // Optional predicate function to filter meshes: // const predicate = function(mesh) { // return mesh.isPickable && mesh.isEnabled() && mesh.name !== "ground"; // } // const pickInfo = scene.pickWithRay(ray, predicate);
const pickInfo = scene.pickWithRay(ray);
// Reset target colors/effects target1.scaling.y = 1; target2.scaling.y = 1; target1.material.emissiveColor = BABYLON.Color3.Black(); target2.material.emissiveColor = BABYLON.Color3.Black();
if (pickInfo.hit && pickInfo.pickedMesh) { //console.log("Ray hit:", pickInfo.pickedMesh.name); // Apply an effect to the hit mesh pickInfo.pickedMesh.scaling.y = 1.5; pickInfo.pickedMesh.material.emissiveColor = new BABYLON.Color3(0.8, 0.8, 0); // Yellow glow }});
// ... rest of scene setup ...Key elements:
BABYLON.Ray(origin, direction, length): Defines the ray.originanddirectionareVector3,lengthis a number.BABYLON.RayHelper: A utility to visualize the ray in the scene (useful for debugging).BABYLON.Vector3.TransformNormal(localVector, worldMatrix): Used here to convert the caster’s local forward direction (0,0,1) into world space direction based on its current rotation.scene.pickWithRay(ray, predicate): Performs the cast. It returns aPickingInfoobject similar toscene.pick. The optionalpredicatefunction receives each potential mesh hit by the ray and should returntrueif the mesh should be considered pickable by this ray,falseotherwise. This allows filtering (e.g., ignoring the ground or invisible objects).
In the example, the central box rotates, and the ray points forward from it. When the ray hits target1 or target2, that target scales up slightly and glows yellow.
Mesh Shadows
Section titled “Mesh Shadows”Shadows add realism by showing how objects block light. Setting up basic shadows involves a light source capable of casting shadows and a ShadowGenerator.
Shadows Example
Section titled “Shadows Example”// Inside createScene function...
// Create a light capable of casting shadows (Directional or SpotLight)const light = new BABYLON.DirectionalLight("dirLight", new BABYLON.Vector3(-0.5, -1, -0.5), scene);light.position = new BABYLON.Vector3(10, 20, 10);light.intensity = 0.8;
// Create a Shadow Generator// The first parameter is the shadow map size (power of 2, e.g., 1024, 2048)// Higher values mean sharper shadows but use more resources.const shadowGenerator = new BABYLON.ShadowGenerator(1024, light);
// Configure shadow properties (optional)shadowGenerator.useBlurExponentialShadowMap = true; // Softer shadows (other options exist)// shadowGenerator.usePoissonSampling = true;shadowGenerator.blurKernel = 32;shadowGenerator.darkness = 0.5; // How dark the shadows are// shadowGenerator.bias = 0.01; // Helps prevent shadow acne (self-shadowing artifacts)
// Create a ground that receives shadowsconst ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 10, height: 10}, scene);const groundMat = new BABYLON.PBRMaterial("groundMat", scene);groundMat.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5);// *** Crucial: Enable shadow receiving on the ground material ***// ground.receiveShadows = true; // For StandardMaterial// PBRMaterial handles receiving automatically if lighting is enabled.// If you are using PBR, ensure scene environment texture or lights are set up correctly.// For simplicity here, we might force it with StandardMaterial or ensure PBR setup is complete.
// Let's use StandardMaterial for a clear receiveShadows example:const groundStdMat = new BABYLON.StandardMaterial("groundStdMat", scene);groundStdMat.diffuseColor = new BABYLON.Color3(0.5, 0.5, 0.5);ground.material = groundStdMat;ground.receiveShadows = true; // Enable shadow receiving
// Create a box that casts shadowsconst shadowBox = BABYLON.MeshBuilder.CreateBox("shadowBox", {size: 2}, scene);shadowBox.position = new BABYLON.Vector3(0, 1.1, 0);const boxMat = new BABYLON.PBRMaterial("boxMat", scene);boxMat.albedoColor = new BABYLON.Color3(1, 0, 0); // RedshadowBox.material = boxMat;
// *** Add the box to the shadow caster list ***shadowGenerator.addShadowCaster(shadowBox);// Or add multiple meshes: shadowGenerator.getShadowMap().renderList.push(mesh1, mesh2);
// Create another object casting shadow (e.g., sphere)const shadowSphere = BABYLON.MeshBuilder.CreateSphere("shadowSphere", {diameter: 1.5}, scene);shadowSphere.position = new BABYLON.Vector3(3, 0.8, 2);const sphereMat = new BABYLON.PBRMaterial("sphereMat", scene);sphereMat.albedoColor = new BABYLON.Color3(0, 0, 1); // BlueshadowSphere.material = sphereMat;shadowGenerator.addShadowCaster(shadowSphere);
// Animate the box (optional)scene.onBeforeRenderObservable.add(() => { shadowBox.rotation.y += 0.01; shadowBox.rotation.x += 0.005;});
// ... rest of scene setup (camera needed) ...const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2.2, Math.PI / 2.5, 15, new BABYLON.Vector3(0,1,0), scene);camera.attachControl(canvas, true);Shadow Setup Steps:
- Create a suitable light (
DirectionalLightorSpotLight).PointLightcan cast shadows but requires cube textures (more complex).HemisphericLightcannot cast shadows. - Instantiate
BABYLON.ShadowGenerator, passing the shadow map size and the light. - For meshes that should receive shadows (like the ground), set
mesh.receiveShadows = true;. (Note: This is mainly forStandardMaterial.PBRMaterialtypically handles this based on lighting setup, but explicit setting might be needed in some configurations). - For meshes that should cast shadows, add them to the shadow generator’s list using
shadowGenerator.addShadowCaster(mesh)or by pushing toshadowGenerator.getShadowMap().renderList. - Adjust shadow generator properties (
useBlurExponentialShadowMap,blurKernel,darkness,bias) to fine-tune the appearance and performance.
Further Mesh Concepts (Brief Overview)
Section titled “Further Mesh Concepts (Brief Overview)”Beyond basic creation and interaction, BabylonJS offers many advanced mesh features:
- Advanced Materials: Explore
PBRMaterialproperties (metallic, roughness, clearcoat, subsurface scattering),NodeMaterialfor custom visual shader creation, andShaderMaterialfor coding shaders directly in GLSL. - Level of Detail (LOD): Display simpler versions of a mesh when it’s far from the camera to improve performance (
mesh.addLODLevel). - Instances: Efficiently render many identical copies of a mesh with variations in position, rotation, and scale (
mesh.createInstance). - Morph Targets: Deform a mesh smoothly between different shapes, often used for facial animation or shape-shifting effects.
- Asset Loading: Import complex meshes created in 3D modeling software (like Blender, Maya) using formats like glTF/GLB (
SceneLoader.ImportMeshAsync). - Solid Particle System (SPS): Manage and animate thousands of meshes (particles) efficiently.
- Highlight Layer: Easily add outlines or highlights to meshes for selection or emphasis (
HighlightLayer).
These topics represent the next steps in mastering mesh manipulation and rendering in BabylonJS. Refer to the official BabylonJS documentation for in-depth explanations and examples.