Skip to content

BabylonJS - Parallax Mapping

Parallax mapping (also known as offset mapping or virtual displacement mapping) is a technique used to enhance the illusion of depth and surface detail on a 3D model without actually adding more geometry. It works by displacing the texture coordinates based on a height map and the viewing angle. This makes surfaces like brick walls, cobblestone paths, or rocky terrain appear to have more ‘relief’ or bumpiness, especially when viewed at an angle.

In Babylon.js, parallax mapping is typically implemented as part of the StandardMaterial and PBRMaterial. It requires a ‘bump’ or ‘normal’ map that also contains height information (often stored in the alpha channel or as a separate height map).

To enable basic parallax mapping on a StandardMaterial or PBRMaterial, you need to assign a bump/normal texture and set the useParallax property to true.

import * as BABYLON from '@babylonjs/core';
// Assuming 'scene' and 'mesh' are defined
const material = new BABYLON.StandardMaterial("parallaxMat", scene);
// Assign a diffuse texture (the main color/pattern)
material.diffuseTexture = new BABYLON.Texture("textures/stone_diffuse.jpg", scene);
// Assign a bump texture containing height information
// This could be a normal map with height in alpha, or a dedicated height map used as bump
material.bumpTexture = new BABYLON.Texture("textures/stone_normal_height.png", scene);
// Enable parallax mapping
material.useParallax = true;
// Control the intensity/depth of the effect (adjust as needed)
material.parallaxScaleBias = 0.05; // Values typically between 0.02 and 0.1 are suitable
// Optional: Improve specular highlights
material.specularPower = 1000.0;
material.specularColor = new BABYLON.Color3(0.5, 0.5, 0.5);
mesh.material = material;

For a more advanced and realistic effect, you can enable Parallax Occlusion Mapping (POM). POM improves upon basic parallax mapping by simulating self-shadowing within the displaced details, making the depth illusion more convincing, especially at steep viewing angles.

// Building on the previous example:
// Enable Parallax Occlusion Mapping (requires useParallax = true)
material.useParallaxOcclusion = true;
// POM generally allows for a higher scale bias for a stronger depth effect
material.parallaxScaleBias = 0.1; // Values up to 0.2 or slightly more might work, depending on the texture
  • material.bumpTexture: The texture providing normal and/or height data. Essential for parallax.
  • material.useParallax = true;: Enables basic parallax mapping.
  • material.useParallaxOcclusion = true;: Enables the more advanced Parallax Occlusion Mapping (requires useParallax to be true).
  • material.parallaxScaleBias = 0.05;: Controls the depth or intensity of the parallax effect. Higher values mean more apparent depth. Adjust this carefully based on your height map.
  • material.specularPower / PBR properties: Ensure lighting and specular/roughness properties are set appropriately to complement the bump effect.

This demo creates a box and applies a material with parallax occlusion mapping enabled, using stone textures.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Babylon.js - Parallax Mapping Demo</title>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
<style>
html, body { overflow: hidden; width: 100%; height: 100%; margin: 0; padding: 0; }
#renderCanvas { width: 100%; height: 100%; touch-action: none; }
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script type="text/javascript">
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const createScene = function () {
const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(0.2, 0.2, 0.25, 1.0);
const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2.2, Math.PI / 2.5, 8, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
camera.wheelPrecision = 100;
camera.minZ = 0.1;
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0.5, 1, 0.25), scene);
light.intensity = 0.8;
const pointLight = new BABYLON.PointLight("pointLight", new BABYLON.Vector3(2, 3, -3), scene);
pointLight.intensity = 0.6;
const box = BABYLON.MeshBuilder.CreateBox("box", { size: 2 }, scene);
// Material Setup
const material = new BABYLON.StandardMaterial("parallaxMat", scene);
// Define texture paths (ensure these files exist in a 'textures' folder)
const stoneDiffURL = "textures/stone_diffuse.jpg"; // Color texture
const stoneBumpURL = "textures/stone_normal_height.png"; // Normal map + height map
try {
material.diffuseTexture = new BABYLON.Texture(stoneDiffURL, scene);
material.bumpTexture = new BABYLON.Texture(stoneBumpURL, scene);
console.log("Textures loaded successfully.");
// Enable Parallax Occlusion Mapping
material.useParallax = true;
material.useParallaxOcclusion = true;
material.parallaxScaleBias = 0.1; // Adjust for desired depth
// Adjust lighting interaction
material.specularColor = new BABYLON.Color3(0.3, 0.3, 0.3);
material.specularPower = 64;
} catch(err) {
console.error("Error loading textures: ", err);
// Fallback: Simple color if textures fail
material.diffuseColor = new BABYLON.Color3(0.5, 0.5, 0.5);
}
box.material = material;
// Rotate the box slowly
scene.onBeforeRenderObservable.add(() => {
box.rotation.y += 0.005;
box.rotation.x += 0.002;
});
return scene;
};
const scene = createScene();
engine.runRenderLoop(function () {
scene.render();
});
window.addEventListener("resize", function () {
engine.resize();
});
</script>
</body>
</html>

This demo displays a slowly rotating cube. If the textures stone_diffuse.jpg and stone_normal_height.png are correctly placed in a textures folder and load successfully, the cube will appear to be made of stone with significant surface depth and detail. As the cube rotates and you change the viewing angle with the mouse, the parallax occlusion effect will make the bumps and crevices look three-dimensional, with subtle self-shadowing visible within the details. If the textures fail to load, it will show a simple gray cube.

Parallax mapping relies heavily on good textures. You need a diffuse (color) map and a corresponding bump/normal map that accurately represents the surface details. For parallax occlusion, the bump map must contain height information, typically encoded in its alpha channel or provided as a separate grayscale height map.