Babbling on about BabylonJS

January 25, 2023
Modern Digital Cinema Camera with Moto Zoom Telephoto Lens Attached

I recently had the opportunity to do some investigation / prototyping work on a website that needed to render interactive 3D models in mobile browsers.  After some investigation on available technologies, I decided to give BabylonJS a try.  BabylonJS has a comprehensive API, is well documented, and has an active support forum.  On top of that, it’s supported everywhere WebGL2 is supported, which means most modern browsers (including mobile browsers) are good to go.  From the documentation, it also appears that some functionality would still be available in browsers that only support WebGL1, which is also a bonus.  BabylonJS developers provide a playground that can be used to test out simple scenes without having to spin up a fully fledged website to test things.  It is licensed under the Apache License 2.0.

So let’s dive in and create a simple 3D application, and explain things as we go.

Project Setup

First thing’s first, we need to load BabylonJS into our project.  Rather than repeating steps that have been well documented, I’m just going to direct you to follow the BabylonJS Setup Steps that best applies to your project.

For simplicity sake, example code in this post assumes that we’ve installed the ES6 NPM modules.

Creating an Engine

Ultimately, BabylonJS will be using an HTML5 Canvas component in order to render its content.  To do this, we’ll be instantiating an Engine object, providing it the canvas we want to use to draw.  This configures the canvas for use by Babylon.  When creating an Engine, we’ll need to provide the appropriate WebGL component, namely the Canvas, that will handle the rendering.  In addition to the control, the Engine object can also also take in settings that will adjust the Engine and Canvas’s default behavior.

index.html:

<html>
  <body>
    <canvas id="babylonJSRender" />
  </body>
</html>

index.js

import { Engine } from '@babylonjs/core';

const canvas = document.getElementById('babylonJSRender');
const engine = new Engine(canvas);

Setting Up a Scene

Think of the last movie you watched.  Filmmakers painstakingly setup each scene with precise camera placement, lighting, actors, props and scenery.  In BabylonJS this continues to be the case.  Scene objects contain lighting controls, cameras, meshes, and backdrops that setup the stage for what can be interacted with for any given scenario.  At construction time, we’ll need to provide the Scene with its parent Engine.  Like Engines, Scenes can also take optional settings at construction time.  Be sure to check out the API to see what options are available.

So let’s create a basic Scene:

index.js

import { Engine, Scene } from '@babylonjs/core';

const canvas = document.getElementById('babylonJSRender');
const engine = new Engine(canvas);
const scene = new Scene(engine);

Rendering the Scene

So now we’ve got a scene, but the canvas is still just a blank screen.  Well, that’s because we haven’t rendered anything yet!  We need to tell the engine which scenes we want to have rendered.

We can do this by calling the following:

engine.runRenderLoop(() => {
  scene.render();
});

What this does is that it tells the Engine to continuously execute the provided function.  In this function we’re telling the scene to render itself.  So the engine will continuously update itself with whatever the scene’s current state is.

Setting up a Camera

Going back to the movie reference, cameras in BabylonJS play the same role as they do on the set.  Camera operators position their cameras facing the scene playing out, capturing everything within the camera’s field of view.  Ultimately what the camera has taken in is what the audiences will view in the final film.  Similarly, anything captured in the BabylonJS camera’s field of view will be presented to an audience, in this case, the end user.  Unlike on the movie set, however, the cameras in Babylonjs aren’t big bulky pieces of equipment.  Rather each camera is an invisible object located in the 3D space that makes up the scene, facing a direction in the 3D space.

BabylonJS provides multiple camera options to choose from.  Each have their own behaviors that might be useful as you are developing your scene.

In our case, let’s setup an ArcRotateCamera.  This camera will be able to orbit a fixed target.  This is a bit more complicated than just an Engine or a Scene.  It takes the following parameters:

  • name –  a friendly name in the BabylonJS ecosystem.
  • alpha – the rotation angle in radians on the longitudinal axis.
  • beta – the rotation angle in radians on the latitudinal axis.
  • radius – the distance from the target point that the camera should orbit.
  • target – a Vector3 object that represents the x, y, z coordinates that the camera should orbit.
  • scene – an optional parameter where you can specify the Scene object that contains the camera.
  • setActiveOnSceneIfNoneActive – an optional boolean that will set this camera to the actively viewing camera when no other camera is active (in the event that there are multiple cameras in the Scene)

index.js

import { Engine, Scene, ArcRotateCamera, Vector3 } from '@babylonjs/core';

const canvas = document.getElementById('babylonJSRender');
const engine = new Engine(canvas);
const scene = new Scene(engine);
const camera = new ArcRotateCamera('camera1', 0, 0, 10, new Vector3(0, 0, 0), scene);

engine.runRenderLoop(() => {
  scene.render();
});

Adding Lighting

You know the old saying “Lights, Camera, Action!”.  We’ve already created our camera, now we need to add the lighting to our Scene.  Lighting can be used to generate shadows, illuminate an area of the scene, or just brighten the scene in general.  Note that without at least one light source, your scene will just be filled with darkness.  Similar to cameras, we’ve got several options for lighting solutions to choose from.

For now, let’s just add some ambient lighting.  That means we’ll need to leverage a HemisphericLight.  This takes:

  • name – a friendly name in the BabylonJS ecosystem.
  • direction – the Vector direction that the light will reflect
  • scene – the Scene object that contains the light.

index.js

import { Engine, Scene, ArcRotateCamera, Vector3, HemisphericLight } from '@babylonjs/core';

const canvas = document.getElementById('babylonJSRender');
const engine = new Engine(canvas);
const scene = new Scene(engine);
const camera = new ArcRotateCamera('camera1', 0, 0, 10, new Vector3(0, 0, 0), scene);
const light = new HemisphericLight('light1', new Vector3(0, 1, 0), scene);

engine.runRenderLoop(() => {
  scene.render();
});

Rendering a Mesh

Ok, we’ve got our light, we’ve got our camera, now let’s render something.  To start, we’ll go simple and just render a simple box.  This takes the form of a MeshBuilder.CreateBox() command.

index.js

import { Engine, Scene, ArcRotateCamera, Vector3, HemisphericLight, MeshBuilder } from '@babylonjs/core';

const canvas = document.getElementById('babylonJSRender');
const engine = new Engine(canvas);
const scene = new Scene(engine);
const camera = new ArcRotateCamera('camera1', 0, 0, 10, new Vector3(0, 0, 0), scene);
const light = new HemisphericLight('light1', new Vector3(0, 1, 0), scene);
const box = MeshBuilder.CreateBox('box1', {size: 1}, scene);

engine.runRenderLoop(() => {
  scene.render();
});

By not specifying a position, this will create a new box and center it at position (0, 0, 0). Which is great, as that’s where our camera is pointed.

Passing Along Inputs

At this point we should be seeing a white square rendered in the middle of our Scene.  But remember the camera we chose?  The ArcRotateCamera should be able to orbit the box, but neither mouse nor keyboard controls affect it.  Well, that’s because the events aren’t being passed from the canvas to the camera yet.

But we can fix that!

We need to leverage the camera’s attachControl method to tell the camera to link with the canvas’s inputs.

camera.attachControl(canvas);
Simple right?  That means our javascript just became:

index.js

import { Engine, Scene, ArcRotateCamera, Vector3, HemisphericLight, MeshBuilder } from '@babylonjs/core';

const canvas = document.getElementById('babylonJSRender');
const engine = new Engine(canvas);
const scene = new Scene(engine);
const camera = new ArcRotateCamera('camera1', 0, 0, 10, new Vector3(0, 0, 0), scene);
camera.attachControl(canvas);
const light = new HemisphericLight('light1', new Vector3(0, 1, 0), scene);
const box = MeshBuilder.CreateBox('box1', {size: 1}, scene);

engine.runRenderLoop(() => {
  scene.render();
});

Now clicking and holding the mouse should rotate the camera around the box.  Arrow keys will also work.

Playground Example

As I mentioned before, BabylonJS provides a playground that lets you quickly setup a scene.  The big difference between the playground and your own site is that the playground handles all engine related needs, so all you need to worry about is your scene.  So the scene we just built would look like this:

https://www.babylonjs-playground.com/#JDV266

More Scene Configurations

Now that we’ve covered the basics, feel free to dive into some of the more complicated Scene features.  Babylon supports a lot of additional interactions, behaviors, models, and materials that can all be applied to Scenes and their contents.