Skip to main content

Overview

Threadbox provides multiple lighting options including default lights, realistic sun simulation based on time and location, and dynamic building shadows that follow the sun’s position.

Default Lighting

Basic three-point lighting for your scene:
const tb = new Threebox(map, gl, {
  defaultLights: true
});
This creates:
  • Ambient Light: Soft overall illumination
  • Directional Light: Main key light
  • Directional Light (back): Fill/rim light

Manual Default Lights

const tb = new Threebox(map, gl);

// Add default lights after creation
tb.defaultLights();

// Access lights
console.log(tb.lights.ambientLight);
console.log(tb.lights.dirLight);
console.log(tb.lights.dirLightBack);

Real Sunlight

Basic Sun Simulation

Realistic sun position based on date, time, and location:
const tb = new Threebox(map, gl, {
  realSunlight: true,
  realSunlightHelper: true // Show light direction helper
});
This automatically:
  • Calculates sun position using map center coordinates
  • Uses current date/time
  • Updates lighting to match sun altitude and azimuth
  • Creates hemisphere light for sky illumination

Sun Position Calculation

Threadbox uses the SunCalc library to compute accurate sun positions:
// Get sun position for specific time and location
const date = new Date('2024-06-21T12:00:00');
const coords = [-122.4194, 37.7749]; // San Francisco

const sunPos = tb.getSunPosition(date, coords);
console.log('Altitude:', sunPos.altitude); // Angle above horizon (radians)
console.log('Azimuth:', sunPos.azimuth);   // Compass direction (radians)

Update Sun Position

Dynamically update sunlight:
// Update to specific time
const newDate = new Date('2024-12-21T15:30:00');
const newCoords = [-73.9352, 40.7306]; // New York

tb.setSunlight(newDate, newCoords);

Sun Times

Get sunrise, sunset, and other sun events:
const sunTimes = tb.getSunTimes(new Date(), [-122.4194, 37.7749]);

console.log('Sunrise:', sunTimes.sunrise);
console.log('Sunset:', sunTimes.sunset);
console.log('Solar Noon:', sunTimes.solarNoon);
console.log('Golden Hour:', sunTimes.goldenHour);
console.log('Dusk:', sunTimes.dusk);
console.log('Night:', sunTimes.night);
Sun times include: sunrise, sunriseEnd, goldenHourEnd, solarNoon, goldenHour, sunsetStart, sunset, dusk, nauticalDusk, night, nadir, nightEnd, nauticalDawn, dawn

Custom Lighting

Adding Custom Lights

const tb = new Threebox(map, gl);

// Point light
const pointLight = new THREE.PointLight(0xffffff, 1.0, 100);
pointLight.position.set(0, 0, 50);
tb.scene.add(pointLight);

// Spot light
const spotLight = new THREE.SpotLight(0xffffff, 1.0);
spotLight.position.set(0, 0, 100);
spotLight.castShadow = true;
tb.scene.add(spotLight);

// Access and modify default lights
if (tb.lights.ambientLight) {
  tb.lights.ambientLight.intensity = 0.5;
}

if (tb.lights.dirLight) {
  tb.lights.dirLight.color.setHex(0xffeecc); // Warm light
  tb.lights.dirLight.intensity = 1.2;
}

Light Helper

Visualize light direction:
const tb = new Threebox(map, gl, {
  realSunlight: true,
  realSunlightHelper: true
});

// Update helper position
tb.updateLightHelper();

// Access helper
if (tb.lights.dirLightHelper) {
  tb.lights.dirLightHelper.visible = true;
}

Shadows

Object Shadows

Enable shadows for 3D models:
tb.loadObj({
  obj: '/models/building.glb',
  type: 'gltf',
  scale: 1,
  units: 'meters'
}, (model) => {
  model.setCoords([-122.4194, 37.7749, 0]);
  
  // Enable shadow casting
  model.castShadow = true;
  
  // Enable shadow receiving
  model.receiveShadow = true;
  
  tb.add(model);
});

Shadow Properties

// Object casts shadows onto other objects
model.castShadow = true;

// Automatically adds shadow plane
if (model.shadowPlane) {
  console.log('Shadow plane created');
}

Configure Renderer for Shadows

// Enable shadow rendering
tb.renderer.shadowMap.enabled = true;
tb.renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Soft shadows

// Configure directional light for shadows
if (tb.lights.dirLight) {
  tb.lights.dirLight.castShadow = true;
  tb.lights.dirLight.shadow.mapSize.width = 2048;
  tb.lights.dirLight.shadow.mapSize.height = 2048;
  tb.lights.dirLight.shadow.camera.near = 0.5;
  tb.lights.dirLight.shadow.camera.far = 500;
}

BuildingShadows

Realistic shadows for Mapbox fill-extrusion buildings:

Setup

const tb = new Threebox(map, gl, {
  realSunlight: true
});

// Add 3D buildings layer first
map.addLayer({
  'id': '3d-buildings',
  'source': 'composite',
  'source-layer': 'building',
  'filter': ['==', 'extrude', 'true'],
  'type': 'fill-extrusion',
  'paint': {
    'fill-extrusion-color': '#aaa',
    'fill-extrusion-height': ['get', 'height'],
    'fill-extrusion-base': ['get', 'min_height'],
    'fill-extrusion-opacity': 0.6
  }
});

// Add building shadows layer
const BuildingShadows = require('./objects/effects/BuildingShadows');
const shadowLayer = new BuildingShadows({
  layerId: 'building-shadows',
  buildingsLayerId: '3d-buildings',
  minAltitude: 0.10 // Minimum sun altitude to show shadows
}, tb);

map.addLayer(shadowLayer, '3d-buildings');

BuildingShadows Options

OptionTypeDefaultDescription
layerIdstringRequiredID for the shadow layer
buildingsLayerIdstringRequiredID of the buildings layer
minAltitudenumber0.10Minimum sun altitude (radians) to show shadows

How It Works

BuildingShadows uses WebGL shaders to:
  1. Read sun position from Threebox (tb.getSunPosition)
  2. Project building geometry onto the ground plane
  3. Offset projection based on sun azimuth and altitude
  4. Render as semi-transparent black layer

Dynamic Sun Updates

// Update shadows when sun position changes
function updateSunPosition() {
  const now = new Date();
  const center = map.getCenter();
  
  tb.setSunlight(now, [center.lng, center.lat]);
  
  // Shadows automatically update via tb.getSunPosition
  map.triggerRepaint();
}

// Update every minute
setInterval(updateSunPosition, 60000);

Ground Layer Updates

Update terrain/satellite layer brightness based on sun:
const tb = new Threebox(map, gl, {
  realSunlight: true,
  terrain: true
});

// Automatically updates ground layer brightness
// based on sun altitude

// Manual update
const sunPos = tb.getSunPosition(new Date(), [lng, lat]);
tb.updateSunGround(sunPos);

Complete Lighting Example

map.on('load', () => {
  const tb = new Threebox(map, gl, {
    realSunlight: true,
    realSunlightHelper: false,
    terrain: true
  });
  
  // Enable shadows in renderer
  tb.renderer.shadowMap.enabled = true;
  tb.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  
  // Configure directional light shadows
  if (tb.lights.dirLight) {
    tb.lights.dirLight.castShadow = true;
    tb.lights.dirLight.shadow.mapSize.width = 2048;
    tb.lights.dirLight.shadow.mapSize.height = 2048;
  }
  
  // Add 3D buildings
  map.addLayer({
    'id': '3d-buildings',
    'source': 'composite',
    'source-layer': 'building',
    'filter': ['==', 'extrude', 'true'],
    'type': 'fill-extrusion',
    'paint': {
      'fill-extrusion-color': '#ccc',
      'fill-extrusion-height': ['get', 'height'],
      'fill-extrusion-base': ['get', 'min_height'],
      'fill-extrusion-opacity': 0.8
    }
  });
  
  // Add building shadows
  const BuildingShadows = require('./BuildingShadows');
  const shadows = new BuildingShadows({
    layerId: 'building-shadows',
    buildingsLayerId: '3d-buildings',
    minAltitude: 0.05
  }, tb);
  map.addLayer(shadows, '3d-buildings');
  
  // Add custom 3D layer
  map.addLayer({
    id: 'custom-layer',
    type: 'custom',
    renderingMode: '3d',
    onAdd: function(map, gl) {
      // Load model with shadows
      tb.loadObj({
        obj: '/models/monument.glb',
        type: 'gltf',
        scale: 2,
        units: 'meters',
        rotation: { x: 90, y: 0, z: 0 }
      }, (model) => {
        model.setCoords([-122.4194, 37.7749, 0]);
        model.castShadow = true;
        model.receiveShadow = true;
        tb.add(model);
      });
    },
    render: function(gl, matrix) {
      tb.update();
    }
  });
  
  // Time controls
  function setTime(hour) {
    const date = new Date();
    date.setHours(hour);
    date.setMinutes(0);
    
    const center = map.getCenter();
    tb.setSunlight(date, [center.lng, center.lat]);
    
    console.log('Sun position updated for', hour + ':00');
  }
  
  // Add UI controls
  document.getElementById('time-slider').addEventListener('input', (e) => {
    setTime(parseInt(e.target.value));
  });
});

Performance Tips

Shadow Map Size

Use 1024x1024 or 2048x2048 for shadow maps. Higher = better quality but slower.

Selective Shadows

Only enable castShadow for important objects, not all objects.

Update Frequency

Don’t update sun position every frame. Use intervals (1-5 minutes).

Shadow Distance

Limit shadow camera far distance to reduce calculations.

See Also

Build docs developers (and LLMs) love