A material is opaque, alpha-tested (fragments below alphaCutOff are discarded, the rest write depth) or alpha-blended (drawn after opaque meshes, back to front, without depth writes). Babylon.js 89,603 infers the mode from alpha < 1, an opacityTexture or a texture with hasAlpha; transparencyMode sets it outright: MATERIAL_OPAQUE (0), MATERIAL_ALPHATEST (1), MATERIAL_ALPHABLEND (2) or MATERIAL_ALPHATESTANDBLEND (3). BookNest's storefront needs a glass pane and an "OPEN" decal:
const sign = new BABYLON.DynamicTexture('open', { width: 256, height: 128 }, scene);
sign.hasAlpha = true; // transparent background
sign.drawText('OPEN', null, 90, 'bold 80px Georgia', '#fff8e1', 'transparent');
const decal = new BABYLON.StandardMaterial('decal', scene);
decal.diffuseTexture = sign;
decal.transparencyMode = BABYLON.Material.MATERIAL_ALPHATEST; // hard edges, writes depth
const glass = new BABYLON.StandardMaterial('glass', scene);
glass.alpha = 0.3; // mode inferred: blend
console.log(`decal: test ${decal.needAlphaTesting()}, blend ${decal.needAlphaBlending()}`);
console.log(`glass: test ${glass.needAlphaTesting()}, blend ${glass.needAlphaBlending()}`);decal: test true, blend false glass: test false, blend true
Alpha testing suits foliage, fences and decals: cheap, order-independent, correct in shadows, but hard-edged. Blending suits glass and fades. Blended submeshes sort by their bounding-sphere centers, so interpenetrating ones flicker: set mesh.alphaIndex, split the mesh, or set needDepthPrePass. alphaMode picks the blend equation (ALPHA_COMBINE by default, ALPHA_ADD for glows).
<!doctype html>
<style>
body { margin: 0; background: #f7f3ec; font: 13px system-ui, sans-serif; color: #333; }
canvas { width: 100%; max-width: 600px; height: 330px; display: block; touch-action: none; }
#info { max-width: 600px; margin: 0; padding: 4px 8px; font-family: monospace; }
</style>
<canvas id="view"></canvas>
<pre id="info"></pre>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.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('#cfd8dcff');
const camera = new BABYLON.ArcRotateCamera('cam', -Math.PI / 2 - 0.35, 1.4, 5, new BABYLON.Vector3(0, 1.1, 0), scene);
camera.attachControl(canvas, true);
new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0.3, 1, -0.5), scene);
const paint = (hex) => {
const m = new BABYLON.StandardMaterial(hex, scene);
m.diffuseColor = BABYLON.Color3.FromHexString(hex);
m.specularColor = BABYLON.Color3.Black();
return m;
};
BABYLON.MeshBuilder.CreateGround('floor', { width: 6, height: 4 }, scene).material = paint('#b08d6a');
// Books on display behind the window
['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'].forEach((hex, i) => {
const book = BABYLON.MeshBuilder.CreateBox('book' + i, { width: 0.45, height: 0.65, depth: 0.08 }, scene);
book.position.set(-1.25 + i * 0.5, 0.9, 0.7);
book.rotation.x = -0.15;
book.material = paint(hex);
});
const stand = BABYLON.MeshBuilder.CreateBox('stand', { width: 3.2, height: 0.55, depth: 0.5 }, scene);
stand.position.set(0, 0.28, 0.75);
stand.material = paint('#6d4c33');
// The window frame
const frameMat = paint('#fff3e0');
for (const [w, h, x, y] of [[3.6, 0.15, 0, 2.1], [3.6, 0.15, 0, 0.3], [0.15, 1.95, -1.8, 1.2], [0.15, 1.95, 1.8, 1.2]]) {
const bar = BABYLON.MeshBuilder.CreateBox('frame', { width: w, height: h, depth: 0.12 }, scene);
bar.position.set(x, y, 0);
bar.material = frameMat;
}
// Glass: alpha < 1, so Babylon infers alpha blending (drawn after opaque meshes, no depth write)
const glass = new BABYLON.StandardMaterial('glass', scene);
glass.diffuseColor = BABYLON.Color3.FromHexString('#bfe3f0');
glass.specularColor = new BABYLON.Color3(0.8, 0.8, 0.8);
glass.alpha = 0.3;
const pane = BABYLON.MeshBuilder.CreatePlane('pane', { width: 3.5, height: 1.65 }, scene);
pane.position.set(0, 1.2, 0);
pane.material = glass;
// Decal: a DynamicTexture with a transparent background, alpha-tested for hard edges
const sign = new BABYLON.DynamicTexture('open', { width: 256, height: 128 }, scene);
sign.hasAlpha = true; // transparent background
sign.drawText('OPEN', null, 90, 'bold 80px Georgia', '#b5452f', 'transparent');
const decal = new BABYLON.StandardMaterial('decal', scene);
decal.diffuseTexture = sign;
decal.emissiveColor = new BABYLON.Color3(0.4, 0.2, 0.15);
decal.transparencyMode = BABYLON.Material.MATERIAL_ALPHATEST; // hard edges, writes depth
decal.alphaCutOff = 0.5;
decal.useAlphaFromDiffuseTexture = true; // test against the texture's alpha
const label = BABYLON.MeshBuilder.CreatePlane('label', { width: 1, height: 0.5 }, scene);
label.position.set(1.05, 1.75, -0.01); // just in front of the glass
label.material = decal;
document.getElementById('info').textContent =
`decal: transparencyMode ${decal.transparencyMode} test ${decal.needAlphaTesting()} blend ${decal.needAlphaBlending()}\n` +
`glass: alpha ${glass.alpha} test ${glass.needAlphaTesting()} blend ${glass.needAlphaBlending()}`;
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>