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 affect or show the mesh. The default value, 0x0FFFFFFF, causes the mesh to be illuminated and shown by any default light and camera. To determine whether 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 second camera to see ONLY the button. The button should also only be visible once.
Notice that the default layerMask starts with the first 4 bits set to 0, or off. If the second camera and button both have a layerMask with one of the four values below, then the second camera will 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 set up multiple cameras:
import { Mesh } from "@babylonjs/core/Meshes/mesh";if (scene.activeCameras.length === 0){ scene.activeCameras.push(scene.activeCamera);}const secondCamera = new Babylon.Camera(...);secondCamera.layerMask = 0x10000000;scene.activeCameras.push(secondCamera);
const Button = new Mesh(...);Button.layerMask = 0x10000000;You can click here to read on a more in-depth look at the layer mask.
Lights
Unless the material of the meshes for the second 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:
import { Light } from "@babylonjs/core/Lights/light";const light = new Light(...);light.includeOnlyWithLayerMask = 0x10000000;Finally, if more lights may be generated later, you can register a callback 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 second orthographic camera that shows a gun sight. To keep it simple, an emissive material was used so no lighting is needed. Just copy and paste it into any scene, then call it. The chosen layerMask also allows Babylon's Dialog extension to interoperate. Perhaps these could be combined to create 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 account for window size changes unless it is a tablet app.
import { Camera } from "@babylonjs/core/Cameras/camera";import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera";import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial";import { Color3 } from "@babylonjs/core/Maths/math.color";import { Vector3 } from "@babylonjs/core/Maths/math.vector";import { Mesh } from "@babylonjs/core/Meshes/mesh";import { MeshBuilder } from "@babylonjs/core/Meshes/meshBuilder";function addGunSight(scene) { if (scene.activeCameras.length === 0) { scene.activeCameras.push(scene.activeCamera); } const secondCamera = new FreeCamera("GunSightCamera", new Vector3(0, 0, -50), scene); secondCamera.mode = Camera.ORTHOGRAPHIC_CAMERA; secondCamera.layerMask = 0x20000000; scene.activeCameras.push(secondCamera); meshes = []; const h = 250; const w = 250; const y = MeshBuilder.CreateBox("y", { size: h * 0.2 }, scene); y.scaling = new Vector3(0.05, 1, 1); y.position = new Vector3(0, 0, 0); meshes.push(y); const x = MeshBuilder.CreateBox("x", { size: h * 0.2 }, scene); x.scaling = new Vector3(1, 0.05, 1); x.position = new Vector3(0, 0, 0); meshes.push(x); const lineTop = MeshBuilder.CreateBox("lineTop", { size: w * 0.8 }, scene); lineTop.scaling = new Vector3(1, 0.005, 1); lineTop.position = new Vector3(0, h * 0.5, 0); meshes.push(lineTop); const lineBottom = MeshBuilder.CreateBox("lineBottom", { size: w * 0.8 }, scene); lineBottom.scaling = new Vector3(1, 0.005, 1); lineBottom.position = new Vector3(0, h * -0.5, 0); meshes.push(lineBottom); const lineLeft = MeshBuilder.CreateBox("lineLeft", { size: h }, scene); lineLeft.scaling = new Vector3(0.01, 1, 1); lineLeft.position = new Vector3(w * -0.4, 0, 0); meshes.push(lineLeft); const lineRight = MeshBuilder.CreateBox("lineRight", { size: h }, scene); lineRight.scaling = new Vector3(0.01, 1, 1); lineRight.position = new Vector3(w * 0.4, 0, 0); meshes.push(lineRight); const gunSight = Mesh.MergeMeshes(meshes); gunSight.name = "gunSight"; gunSight.layerMask = 0x20000000; gunSight.freezeWorldMatrix(); const mat = new StandardMaterial("emissive mat", scene); mat.checkReadyOnlyOnce = true; mat.emissiveColor = new 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