logo

Babylon.js Market

By Lawrence

8 minutes

Lighting Component System

Let's walk through building a lighting component system that adds ambient and directional lighting to your scene. These will be your work lights and are usually all someone needs to simulate the outdoors.

Understanding Babylon.js Lighting Classes

Before building our ECS lighting system, let's explore the different lighting options available in Babylon.js. Each light type serves different purposes and creates different visual effects in your 3D scenes.

HemisphericLight (Ambient Lighting)

HemisphericLight provides soft, even lighting that simulates light coming from all directions - like outdoor ambient light or light bouncing off surfaces. It's perfect for general scene illumination without harsh shadows.

HemisphericLight Example
import { HemisphericLight, Vector3, Color3 } from "@babylonjs/core";

// Basic ambient light
const ambientLight = new HemisphericLight("ambientLight", Vector3.Up(), scene);
ambientLight.intensity = 0.7;

// Customized ambient light with colors
const coloredAmbient = new HemisphericLight(
  "coloredAmbient",
  Vector3.Up(),
  scene,
);
coloredAmbient.intensity = 1.0;
coloredAmbient.diffuse = new Color3(1, 1, 0.8); // Warm white
coloredAmbient.specular = new Color3(0.2, 0.2, 0.2); // Subtle specular
coloredAmbient.groundColor = new Color3(0.2, 0.2, 0.6); // Blue ground reflection

Key Properties:

  • intensity: Controls brightness (0-10+)
  • diffuse: Main light color affecting object surfaces
  • specular: Color of reflective highlights
  • groundColor: Color of light coming from below

DirectionalLight (Sun-like Lighting)

DirectionalLight simulates parallel light rays coming from a specific direction - like sunlight. It's essential for realistic outdoor scenes and casting directional shadows.

DirectionalLight Example
import { DirectionalLight, Vector3 } from "@babylonjs/core";

// Basic directional light (like sun from above-left)
const sunLight = new DirectionalLight(
  "sunLight",
  new Vector3(-1, -1, 0),
  scene,
);
sunLight.intensity = 1.5;

// Advanced directional light with shadow setup
const shadowLight = new DirectionalLight(
  "shadowLight",
  new Vector3(-1, -2, -1),
  scene,
);
shadowLight.intensity = 2.0;
shadowLight.position = new Vector3(20, 40, 20); // Position for shadow calculations
shadowLight.autoCalcShadowZBounds = true; // Optimize shadow boundaries

// Control shadow quality
shadowLight.shadowMinZ = 1;
shadowLight.shadowMaxZ = 100;

Key Properties:

  • direction: Vector pointing in the light direction
  • position: Used for shadow calculations (not light direction)
  • autoCalcShadowZBounds: Automatically optimizes shadow rendering
  • shadowMinZ/shadowMaxZ: Control shadow rendering distance

SpotLight (Focused Cone Lighting)

SpotLight creates a cone of light from a specific position - like a flashlight, stage light, or car headlight. It's perfect for focused illumination and dramatic effects.

SpotLight Example
import { SpotLight, Vector3 } from "@babylonjs/core";

// Basic spotlight
const spotlight = new SpotLight(
  "spotlight",
  new Vector3(0, 10, 0), // Position
  new Vector3(0, -1, 0), // Direction (pointing down)
  Math.PI / 3, // Angle (60 degrees)
  2, // Exponent (light falloff)
  scene,
);
spotlight.intensity = 2.0;

// Flashlight-style spotlight
const flashlight = new SpotLight(
  "flashlight",
  new Vector3(0, 1, -5), // Position
  new Vector3(0, -0.2, 1), // Direction (slightly down, forward)
  Math.PI / 4, // Narrow cone (45 degrees)
  10, // Sharp falloff
  scene,
);
flashlight.intensity = 3.0;
flashlight.range = 50; // Maximum light distance

// Stage lighting with color
const stageLight = new SpotLight(
  "stageLight",
  new Vector3(-5, 8, 0),
  new Vector3(0.5, -1, 0),
  Math.PI / 6, // Tight beam
  5,
  scene,
);
stageLight.diffuse = new Color3(1, 0.8, 0.4); // Warm stage lighting
stageLight.intensity = 4.0;

Key Properties:

  • position: Where the light is located
  • direction: Which way the light points
  • angle: Width of the light cone (in radians)
  • exponent: How sharply the light fades at edges
  • range: Maximum distance the light travels

PointLight (Omnidirectional Lighting)

PointLight radiates light in all directions from a single point - like a light bulb, candle, or lamp. It's ideal for interior lighting and local illumination.

PointLight Example
import { PointLight, Vector3, Color3 } from "@babylonjs/core";

// Basic point light (like a light bulb)
const bulb = new PointLight("bulb", new Vector3(0, 5, 0), scene);
bulb.intensity = 2.0;

// Colored point lights for atmosphere
const redLight = new PointLight("redLight", new Vector3(-3, 2, 0), scene);
redLight.diffuse = new Color3(1, 0.2, 0.2); // Red light
redLight.intensity = 1.5;
redLight.range = 10; // Limit light reach

const blueLight = new PointLight("blueLight", new Vector3(3, 2, 0), scene);
blueLight.diffuse = new Color3(0.2, 0.4, 1); // Blue light
blueLight.intensity = 1.5;
blueLight.range = 10;

// Flickering candle effect
const candle = new PointLight("candle", new Vector3(0, 1, 0), scene);
candle.diffuse = new Color3(1, 0.7, 0.3); // Warm flame color
candle.intensity = 1.0;
candle.range = 8;

// Animate intensity for flickering
scene.registerBeforeRender(() => {
  candle.intensity = 0.8 + Math.random() * 0.4; // Random flicker
});

Key Properties:

  • position: Location of the light source
  • range: Maximum distance the light reaches
  • diffuse/specular: Color properties like other lights
  • Perfect for creating multiple colored lights in a scene

Light Animation and Control

All lights can be animated and controlled dynamically:

Light Animation Examples
// Rotating directional light (moving sun)
const sun = new DirectionalLight("sun", new Vector3(-1, -1, 0), scene);
let sunAngle = 0;

scene.registerBeforeRender(() => {
  sunAngle += 0.01;
  sun.direction.x = Math.cos(sunAngle);
  sun.direction.y = -0.5 - Math.abs(Math.sin(sunAngle)) * 0.5;
  sun.intensity = 0.5 + Math.abs(Math.sin(sunAngle)) * 1.5;
});

// Moving spotlight
const movingSpot = new SpotLight(
  "movingSpot",
  new Vector3(0, 10, 0),
  new Vector3(0, -1, 0),
  Math.PI / 4,
  2,
  scene,
);
let spotTime = 0;

scene.registerBeforeRender(() => {
  spotTime += 0.02;
  movingSpot.position.x = Math.sin(spotTime) * 5;
  movingSpot.position.z = Math.cos(spotTime) * 5;
  movingSpot.direction.x = -movingSpot.position.x * 0.1;
  movingSpot.direction.z = -movingSpot.position.z * 0.1;
});

Light Performance Considerations

Light Limits: Most devices support 4-8 dynamic lights per material. Use light limiting or light baking for scenes with many lights.

Shadow Performance: Only DirectionalLight and SpotLight support shadows efficiently. Limit shadow-casting lights for better performance.

Light Culling: Lights outside the camera view or object influence range are automatically culled by Babylon.js.

Performance Optimization
// Limit lights affecting a material
material.maxSimultaneousLights = 4;

// Disable lights when not needed
light.setEnabled(false); // Turn off temporarily
light.dispose(); // Remove completely

// Use lower intensity instead of many lights
const efficientLight = new HemisphericLight("efficient", Vector3.Up(), scene);
efficientLight.intensity = 2.0; // One bright light vs multiple dim lights

Let's start by creating the new Component file.

shell
touch src/Components/Lighting.ts

Lighting in Babylon.js

First, we need to import everything we'll use from Babylon.js and our ECS framework:

src/components/Lighting.ts
import { Entity, World, System, Component } from "~/lib/ECS";
import { HemisphericLight, Vector3, DirectionalLight } from "@babylonjs/core";

We're pulling in our ECS building blocks and two key lighting types: HemisphericLight for ambient lighting and DirectionalLight for shadows and directional illumination.

Lighting Component Options

Next, we define what configuration our lighting component needs:

src/components/Lighting.ts
export interface LightingComponentInput {
  types: ["ambient", "directional"];
  offset: [number, number, number];
}

We specify which light types to create and the position offset for our lights.

Creating the Lighting Component

Now we build the component class that holds our lighting configuration:

src/components/Lighting.ts
export class LightingComponent extends Component {
  types = ["ambient", "directional"];
  lights = [] as any[];
  offset = [-2, -2, 0];

  constructor(data: LightingComponentInput) {
    super(data);
    this.types = data.types;
    this.offset = data.offset;
  }
}

The component stores the light types we want, an array to hold the created lights, and the offset position. We provide sensible defaults for a typical lighting setup.

Setting Up the Lighting System

The system handles the lighting logic. Let's start with the constructor:

src/components/Lighting.ts
export class LightingSystem extends System {
  constructor(world: World, componentClasses = [LightingComponent]) {
    super(world, componentClasses);
  }

This tells the system to process any entity that has a LightingComponent attached.

Loading the Lighting Entity

When an entity with a lighting component gets added, this method creates the actual lights:

src/components/Lighting.ts
loadEntity(entity: Entity) {
  const lightingComponent = entity.getComponent(LightingComponent);
  lightingComponent.loading = true;
  const { lights, types, offset } = lightingComponent;
  const ov = Vector3.FromArray(offset);

We grab the lighting component, set the loading flag, and convert our offset array into a proper Babylon.js Vector3 for positioning.

Creating Each Light Type

Now we iterate through the requested light types and create them:

src/components/Lighting.ts
types.forEach((type, index) => {
  if (type === "ambient") {
    const light = new HemisphericLight(
      `HemisphericLight-${lights.length}`,
      ov,
      this.scene,
    );
    light.intensity = types.length > 1 ? 0.6 : 5;
    lightingComponent.lights.push(light);
  }

For ambient lights, we create a HemisphericLight at our offset position. The intensity is adjusted based on whether we have multiple lights - lower intensity when combined with other lights to avoid over-brightening.

Adding Directional Lighting

Next, we handle directional lights for shadows and realistic lighting:

src/components/Lighting.ts
if (type === "directional") {
  const light = new DirectionalLight(`DirectionalLight`, ov, this.scene);
  light.intensity = 3;
  light.autoCalcShadowZBounds = true;
  lightingComponent.lights.push(light);
}

Directional lights provide parallel light rays (like sunlight) and support shadow casting. The autoCalcShadowZBounds setting automatically optimizes shadow rendering boundaries.

Finishing the Loading Process

Finally, we mark the lighting as loaded and provide confirmation:

src/components/Lighting.ts
lightingComponent.loading = false;
lightingComponent.loaded = true;
console.log("Lighting initialized");
return;

We clear the loading flag, set the loaded flag, and log confirmation that our lighting system is ready.

In Sum

This lighting system provides flexible illumination for your 3D scenes. It supports both ambient lighting (for general scene brightness) and directional lighting (for realistic shadows and depth). We'll get to the shadows later.

The system automatically adjusts light intensities when multiple lights are used together, preventing over-exposure while maintaining good visual quality.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search