Both libraries upload the same arrays. Three.js 30,815 separates a reusable BufferGeometry from the Mesh that pairs it with a material (Anatomy of a BufferGeometry); a Babylon.js 89,603 builder creates both at once.
| Task | Babylon.js | Three.js (Geometries and BufferGeometry) |
|---|---|---|
| Built-in shape | MeshBuilder.CreateBox(name, options, scene) | new Mesh(new BoxGeometry(w, h, d), material) |
| Custom arrays | VertexData + applyToMesh() | setAttribute('position', new BufferAttribute()) |
| Smooth normals | VertexData.ComputeNormals() | geometry.computeVertexNormals() |
| Merge | Mesh.MergeMeshes() in core | mergeGeometries() from an add-on |
| Booleans | CSG2 in core (Manifold) | Community libraries such as three-bvh-csg |
| Handedness | Left-handed by default | Right-handed |
Porting is mechanical: copy the arrays, flip z or set scene.useRightHandedSystem = true, and check the winding (Custom Geometry). Babylon's options objects read better than positional arguments, and mesh names serve getMeshByName() and the Inspector (Inspector and Tools).
<!doctype html>
<style>
body { margin: 0; background: #f7f3ec; }
canvas { width: 100%; max-width: 600px; height: 360px; display: block; touch-action: none; }
</style>
<canvas id="view"></canvas>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babylonjs-gui@9.28.0/babylon.gui.min.js"></script>
<script>
const canvas = document.getElementById('view');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
scene.clearColor = BABYLON.Color4.FromHexString('#f7f3ecff');
const camera = new BABYLON.ArcRotateCamera('cam', -Math.PI / 2 - 0.25, 1.2, 5.2, new BABYLON.Vector3(0, 0.3, 0), scene);
camera.attachControl(canvas, true);
new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0.3, 1, -0.4), scene);
const ui = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI('ui');
const cover = new BABYLON.StandardMaterial('cover', scene);
cover.diffuseColor = BABYLON.Color3.FromHexString('#1f5f8b');
// 1. Babylon.js: one builder call creates the geometry AND the mesh
const built = BABYLON.MeshBuilder.CreateBox('built', { width: 0.8, height: 1.2, depth: 0.2 }, scene);
built.position.x = -1.2;
built.material = cover;
// 2. Three.js style: flat arrays, like setAttribute('position', new BufferAttribute(...)).
// Here they go into a VertexData and applyToMesh; the corners are shared (8 vertices).
const [w, h, d] = [0.4, 0.6, 0.1];
const positions = [-w, -h, -d, w, -h, -d, w, h, -d, -w, h, -d, // front face (-z)
-w, -h, d, w, -h, d, w, h, d, -w, h, d]; // back face (+z)
const indices = [0, 2, 1, 0, 3, 2, 4, 5, 6, 4, 6, 7, 0, 1, 5, 0, 5, 4, // front, back, bottom
3, 6, 2, 3, 7, 6, 0, 4, 7, 0, 7, 3, 1, 2, 6, 1, 6, 5]; // top, left, right
const normals = [];
BABYLON.VertexData.ComputeNormals(positions, indices, normals); // like computeVertexNormals()
const custom = new BABYLON.Mesh('custom', scene);
Object.assign(new BABYLON.VertexData(), { positions, indices, normals }).applyToMesh(custom);
custom.position.x = 1.2;
custom.material = cover;
// Babylon is left-handed (+z into the screen); Three.js is right-handed (+z toward you)
const axes = new BABYLON.AxesViewer(scene, 0.7);
axes.update(new BABYLON.Vector3(-1.2, -0.7, -0.3), BABYLON.Axis.X, BABYLON.Axis.Y, BABYLON.Axis.Z);
const threeAxes = new BABYLON.AxesViewer(scene, 0.7);
threeAxes.update(new BABYLON.Vector3(1.2, -0.7, -0.3), BABYLON.Axis.X, BABYLON.Axis.Y, BABYLON.Axis.Z.negate());
label(built, 'MeshBuilder.CreateBox\n24 vertices (per-face normals)', -110);
label(custom, 'VertexData + applyToMesh\n8 shared vertices (smoothed)', -110);
const foot = label(null, 'left: Babylon axes, +z away right: Three.js axes, +z toward you', 0);
foot.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_BOTTOM;
foot.top = '-8px';
function label(mesh, text, offsetY) {
const block = new BABYLON.GUI.TextBlock('', text);
Object.assign(block, { color: '#333', fontSize: 13, resizeToFit: true });
ui.addControl(block);
if (mesh) { block.linkWithMesh(mesh); block.linkOffsetY = offsetY; }
return block;
}
scene.onBeforeRenderObservable.add(() => {
built.rotation.y = custom.rotation.y += engine.getDeltaTime() / 1500;
});
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>