Layer Masks and Multi-Cam Textures

Different meshes for multiple cameras using layer masks

A layerMask is a number assigned to each mesh and camera. It is used at the bit level to indicate whether lights and cameras should shine-upon or show the mesh. The default value, 0x0FFFFFFF, will cause the mesh to be illuminated and shown by any stock light and camera. To determine if a mesh is seen by a camera, a bitwise AND is performed and the result is compared to zero:

mesh.layerMask & camera.layerMask !== 0

The feature is used primarily when multiple cameras are active at the same time. If you wish to have a mesh that is always visible on the screen and pickable, e.g. a button, you might add a second camera and light to the scene to exclusively show and light it.

You'll need the 2nd camera to ONLY see the button. The button should also only be visible once.

Notice that the default layerMask starts with the first 4 bits being 0, or off. If the 2nd camera and button were to both have a layerMask with one of the 4 values below, then the 2nd camera would only see the button:

  • 0x10000000
  • 0x20000000
  • 0x40000000
  • 0x80000000

It should also be noted that a mesh with a layerMask of 0 can never be seen by anyone. This might be good for hiding things.

To setup for multi-cameras:

if (scene.activeCameras.length === 0){
const secondCamera = new Babylon.Camera(...);
secondCamera.layerMask = 0x10000000;
const Button = new BABYLON.Mesh(...);
Button.layerMask = 0x10000000;

You can click here to read on a more in-depth look at the layer mask.


Unless the material of the meshes for the 2nd camera is purely emissive, this still leaves any light for the button illuminating all the other meshes, and other lights in the scene illuminating the button. To keep scene lights from illuminating the button, loop through the existing lights, and set the excludeWithLayerMask value:

for (let i = scene.lights.length - 1; i >= 0; i--) {
scene.lights[i].excludeWithLayerMask = 0x10000000;

Then make the "button" light:

const light = new BABYLON.Light(...);
light.includeOnlyWithLayerMask = 0x10000000;

Finally, if there may be more lights generated later, you can register a call-back when a light is added:

scene.onNewLightAdded = onNewLight;

This could be:

onNewLight = function (newLight, positionInArray, scene) {
newLight.excludeWithLayerMask = 0x10000000;

Gun Sight Crosshair Example

Here is a simple example of using a 2nd orthographic camera that shows a gun sight. To keep it simple, emissive material was used to avoid lighting it. Just copy and paste it into any scene, then call it. The layerMask chosen also allows Babylon's Dialog extension to inter-operate. Perhaps these could be combined to do a heads-up tank sight with a rangefinder.

A commercial-quality implementation would probably not use CreateBox(), since it creates depth faces that cannot be seen straight-on anyway. It should also take into account a window size change unless it is a tablet app.

function addGunSight(scene) {
if (scene.activeCameras.length === 0) {
const secondCamera = new BABYLON.FreeCamera("GunSightCamera", new BABYLON.Vector3(0, 0, -50), scene);
secondCamera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA;
secondCamera.layerMask = 0x20000000;
meshes = [];
const h = 250;
const w = 250;
const y = BABYLON.MeshBuilder.CreateBox("y", { size: h * 0.2 }, scene);
y.scaling = new BABYLON.Vector3(0.05, 1, 1);
y.position = new BABYLON.Vector3(0, 0, 0);
const x = BABYLON.MeshBuilder.CreateBox("x", { size: h * 0.2 }, scene);
x.scaling = new BABYLON.Vector3(1, 0.05, 1);
x.position = new BABYLON.Vector3(0, 0, 0);
const lineTop = BABYLON.MeshBuilder.CreateBox("lineTop", { size: w * 0.8 }, scene);
lineTop.scaling = new BABYLON.Vector3(1, 0.005, 1);
lineTop.position = new BABYLON.Vector3(0, h * 0.5, 0);
const lineBottom = BABYLON.MeshBuilder.CreateBox("lineBottom", { size: w * 0.8 }, scene);
lineBottom.scaling = new BABYLON.Vector3(1, 0.005, 1);
lineBottom.position = new BABYLON.Vector3(0, h * -0.5, 0);
const lineLeft = BABYLON.MeshBuilder.CreateBox("lineLeft", { size: h }, scene);
lineLeft.scaling = new BABYLON.Vector3(0.01, 1, 1);
lineLeft.position = new BABYLON.Vector3(w * -0.4, 0, 0);
const lineRight = BABYLON.MeshBuilder.CreateBox("lineRight", { size: h }, scene);
lineRight.scaling = new BABYLON.Vector3(0.01, 1, 1);
lineRight.position = new BABYLON.Vector3(w * 0.4, 0, 0);
const gunSight = BABYLON.Mesh.MergeMeshes(meshes); = "gunSight";
gunSight.layerMask = 0x20000000;
const mat = new BABYLON.StandardMaterial("emissive mat", scene);
mat.checkReadyOnlyOnce = true;
mat.emissiveColor = new BABYLON.Color3(0, 1, 0);
gunSight.material = mat;

See it in action here: Gun Sight Crosshair Example

Using the information here and combining it with the viewport information from the previous section, we can create a more complex example that includes the option to omit meshes from specific cameras.

Picture in Picture Visual Camera

Further reading