Skip to main content

Overview

Threadbox integrates with Mapbox GL JS’s sky and terrain layers to create immersive 3D environments. The sky layer simulates atmospheric perspective and sun position, while terrain adds elevation data for realistic landscapes.

Sky Layer

Basic Sky Setup

Enable the atmospheric sky layer:
const tb = new Threebox(map, gl, {
  sky: true,
  realSunlight: true // Automatically sync sky with sun
});
This creates a built-in sky layer that:
  • Shows atmospheric gradient
  • Positions sun based on time and location
  • Automatically updates with realSunlight

Manual Sky Creation

const tb = new Threebox(map, gl);

// Create sky layer after initialization
tb.sky = true;

// Or use internal method
tb.createSkyLayer();

Sky Configuration

Threadbox uses Mapbox’s sky layer with automatic sun positioning:
map.on('load', () => {
  const tb = new Threebox(map, gl, {
    sky: true,
    realSunlight: true
  });
  
  // Sky layer is automatically added with these properties:
  // - Type: 'sky'
  // - ID: 'sky-layer' (tb.skyLayerName)
  // - Sun position: [azimuth, altitude] from tb.getSunSky()
});

Update Sky Position

Manually update sky with sun position:
// Get sun position for sky
const date = new Date('2024-06-21T16:00:00');
const coords = map.getCenter();

const sunPos = tb.getSunPosition(date, [coords.lng, coords.lat]);
const sunSky = tb.getSunSky(date, sunPos);

// Update sky layer
tb.updateSunSky(sunSky);

Get Sun Sky Position

// Get current sun position for sky layer
const sunSky = tb.getSunSky();
console.log('Azimuth:', sunSky[0]);
console.log('Altitude:', sunSky[1]);

// Get for specific date/position
const customDate = new Date('2024-12-21T12:00:00');
const customPos = tb.getSunPosition(customDate, [-122.4194, 37.7749]);
const customSky = tb.getSunSky(customDate, customPos);

Sky Layer Properties

Access the sky layer:
// Get sky layer ID
console.log(tb.skyLayerName); // 'sky-layer'

// Get layer from map
const skyLayer = map.getLayer(tb.skyLayerName);

if (skyLayer) {
  console.log('Sky layer exists');
}

Toggle Sky Layer

// Enable sky
tb.sky = true;

// Disable sky (removes layer)
tb.sky = false;

// Check if sky is enabled
if (tb.sky) {
  console.log('Sky layer is active');
}

Terrain Layer

Basic Terrain Setup

Enable 3D terrain with elevation:
const tb = new Threebox(map, gl, {
  terrain: true,
  realSunlight: true // Automatically adjust terrain lighting
});

Manual Terrain Creation

const tb = new Threebox(map, gl);

// Enable terrain after initialization
tb.terrain = true;

// Or use internal method
tb.createTerrainLayer();

Terrain Configuration

Threadbox uses Mapbox’s terrain with automatic source creation:
// Terrain source name
console.log(tb.terrainSourceName); // 'mapbox-dem'

// Terrain exaggeration
console.log(tb.terrainExaggeration); // 1.0

// Terrain raster layer (for lighting)
console.log(tb.terrainLayerName); // Auto-detected or ''

Custom Terrain Source

Add custom DEM (Digital Elevation Model) source:
map.on('load', () => {
  // Add custom terrain source
  map.addSource('custom-dem', {
    'type': 'raster-dem',
    'url': 'mapbox://mapbox.mapbox-terrain-dem-v1',
    'tileSize': 512,
    'maxzoom': 14
  });
  
  // Set terrain
  map.setTerrain({
    'source': 'custom-dem',
    'exaggeration': 1.5
  });
  
  const tb = new Threebox(map, gl, {
    realSunlight: true
  });
});

Update Terrain Lighting

Adjust terrain brightness based on sun:
const sunPos = tb.getSunPosition(new Date(), [lng, lat]);
tb.updateSunGround(sunPos);

Toggle Terrain

// Enable terrain
tb.terrain = true;

// Disable terrain
tb.terrain = false;

// Check if terrain is enabled
if (tb.terrain) {
  console.log('Terrain is active');
}

Combined Sky & Terrain

Complete Setup

map.on('load', () => {
  const tb = new Threebox(map, gl, {
    defaultLights: false,  // Use sun lighting instead
    realSunlight: true,    // Sun simulation
    sky: true,             // Atmospheric sky
    terrain: true          // 3D elevation
  });
  
  // Sky and terrain automatically sync with sun position
});

Time-of-Day Animation

Animate sun, sky, and terrain through the day:
let currentHour = 6;

function animateTimeOfDay() {
  // Create date for current hour
  const date = new Date();
  date.setHours(currentHour);
  date.setMinutes(0);
  
  // Get map center
  const center = map.getCenter();
  const coords = [center.lng, center.lat];
  
  // Update sun position
  const sunPos = tb.getSunPosition(date, coords);
  tb.setSunlight(date, coords);
  
  // Update sky
  const sunSky = tb.getSunSky(date, sunPos);
  tb.updateSunSky(sunSky);
  
  // Update terrain lighting
  tb.updateSunGround(sunPos);
  
  // Increment hour
  currentHour = (currentHour + 0.5) % 24;
  
  // Continue animation
  setTimeout(animateTimeOfDay, 1000); // Update every second
}

// Start animation
animateTimeOfDay();

Location-Based Sky

Update sky when map moves:
map.on('moveend', () => {
  if (tb.sky) {
    const center = map.getCenter();
    const sunSky = tb.getSunSky(new Date(), tb.getSunPosition(new Date(), [center.lng, center.lat]));
    tb.updateSunSky(sunSky);
  }
});

Advanced Configuration

Custom Sky Properties

Modify Mapbox sky layer directly:
map.on('load', () => {
  const tb = new Threebox(map, gl, { sky: true });
  
  // Get sky layer
  const skyLayer = map.getLayer(tb.skyLayerName);
  
  if (skyLayer) {
    // Customize sky appearance
    map.setPaintProperty(tb.skyLayerName, 'sky-atmosphere-sun-intensity', 5);
    map.setPaintProperty(tb.skyLayerName, 'sky-atmosphere-color', 'rgba(135, 206, 235, 1)');
  }
});

Terrain Exaggeration

Increase elevation for dramatic effect:
map.on('load', () => {
  const tb = new Threebox(map, gl);
  
  // Add terrain with exaggeration
  map.setTerrain({
    'source': 'mapbox-dem',
    'exaggeration': 2.0 // 2x elevation
  });
  
  tb.terrain = true;
  tb.terrainExaggeration = 2.0;
});

Sky Gradient

Customize atmospheric gradient:
map.setPaintProperty(tb.skyLayerName, 'sky-gradient', [
  'interpolate',
  ['linear'],
  ['sky-radial-progress'],
  0.8, 'rgba(135, 206, 235, 1)',  // Horizon
  1, 'rgba(0, 0, 50, 1)'           // Zenith
]);

Performance Optimization

Conditional Loading

Only enable sky/terrain when map pitch > 30° to save resources on 2D views

Update Frequency

Update sun position every 1-5 minutes, not every frame

Terrain LOD

Terrain automatically uses level-of-detail. Adjust maxzoom for performance.

Disable When Hidden

Set sky: false and terrain: false when not visible

Complete Example

map.on('load', () => {
  // Initialize Threebox with sky and terrain
  const tb = new Threebox(map, gl, {
    defaultLights: false,
    realSunlight: true,
    realSunlightHelper: false,
    sky: true,
    terrain: true
  });
  
  // Set initial view
  map.flyTo({
    center: [-119.5383, 37.7153], // Yosemite
    zoom: 13,
    pitch: 75,
    bearing: 40
  });
  
  // Add custom layer with 3D objects
  map.addLayer({
    id: 'custom-layer',
    type: 'custom',
    renderingMode: '3d',
    onAdd: function(map, gl) {
      // Load 3D model
      tb.loadObj({
        obj: '/models/tree.glb',
        type: 'gltf',
        scale: 5,
        units: 'meters',
        rotation: { x: 90, y: 0, z: 0 }
      }, (tree) => {
        tree.setCoords([-119.5383, 37.7153, 1200]);
        tree.castShadow = true;
        tb.add(tree);
      });
    },
    render: function(gl, matrix) {
      tb.update();
    }
  });
  
  // Time control slider
  const timeSlider = document.getElementById('time-slider');
  timeSlider.addEventListener('input', (e) => {
    const hour = parseInt(e.target.value);
    const date = new Date();
    date.setHours(hour);
    date.setMinutes(0);
    
    const center = map.getCenter();
    const coords = [center.lng, center.lat];
    
    // Update everything
    const sunPos = tb.getSunPosition(date, coords);
    tb.setSunlight(date, coords);
    
    const sunSky = tb.getSunSky(date, sunPos);
    tb.updateSunSky(sunSky);
    
    tb.updateSunGround(sunPos);
    
    document.getElementById('time-display').textContent = 
      `${hour}:00 - ${sunPos.altitude > 0 ? 'Day' : 'Night'}`;
  });
  
  // Auto-update based on pitch
  map.on('pitch', () => {
    const pitch = map.getPitch();
    
    if (pitch < 30 && tb.sky) {
      tb.sky = false; // Disable sky for 2D view
    } else if (pitch >= 30 && !tb.sky) {
      tb.sky = true; // Enable sky for 3D view
    }
  });
});

Troubleshooting

  • Ensure map pitch is > 0 (sky only visible in 3D view)
  • Check tb.sky === true
  • Verify sky layer exists: map.getLayer(tb.skyLayerName)
  • Make sure Mapbox GL JS version supports sky (v2.0+)
  • Verify terrain source is added before setting terrain
  • Check Mapbox GL JS version supports terrain (v2.0+)
  • Ensure tb.terrain === true
  • Verify you have a valid Mapbox access token
  • Enable realSunlight: true in Threebox options
  • Manually call tb.updateSunSky() and tb.updateSunGround()
  • Verify date/coordinates are valid

See Also

Build docs developers (and LLMs) love