Skip to main content

Overview

Proper materials and lighting are essential for creating realistic 3D scenes. Threebox provides multiple lighting options and supports all Three.js material types.

Materials

Material Types

Threebox supports all Three.js materials. Choose based on your rendering needs:
MaterialUse CaseLighting Required
MeshBasicMaterialFlat colors, overlays, UI elementsNo
MeshLambertMaterialMatte surfaces, simple objectsYes
MeshPhongMaterialShiny surfaces, metalsYes
MeshStandardMaterialRealistic PBR rendering (recommended)Yes
MeshPhysicalMaterialAdvanced PBR with clearcoat, transmissionYes

Specifying Materials

You can specify materials in three ways:
// Use material name with basic properties
const sphere = tb.sphere({
  radius: 50,
  material: 'MeshStandardMaterial',
  color: 'red',
  opacity: 1
});

Material Properties

Common properties available when using material strings:
PropertyTypeDefaultDescription
colorstring | number'black'Material color
opacitynumber1Opacity (0-1, sets transparent=true if < 1)
sideTHREE.DoubleSideundefinedRender side(s)
src/utils/material.js:18-50
function material (options) {
  var output;
  
  if (options) {
    options = utils._validate(options, defaults);
    
    // check if user provided material object
    if (options.material && options.material.isMaterial) 
      output = options.material;
    
    // check if user provided any material parameters
    else if (options.material || options.color || options.opacity){
      output = new THREE[options.material]({
        color: options.color, 
        transparent: options.opacity<1
      });
    }
    
    // if neither, return default material
    else output = generateDefaultMaterial();
    
    output.opacity = options.opacity;
    if (options.side) output.side = options.side
  }
  
  // if no options, return default
  else output = generateDefaultMaterial();
  
  function generateDefaultMaterial(){
    return new THREE[defaults.material]({color: defaults.color});
  }
  
  return output
}

Lighting

Default Lights

Enable basic scene lighting with the defaultLights option:
window.tb = new Threebox(
  map,
  gl,
  { defaultLights: true }
);
This creates:
  • Ambient Light: Soft overall illumination (75% intensity, white)
  • Directional Light 1: Front-left lighting (25% intensity)
  • Directional Light 2: Back-right lighting (25% intensity)
src/Threebox.js:1117-1131
defaultLights: function () {
  this.lights.ambientLight = new THREE.AmbientLight(
    new THREE.Color('hsl(0, 0%, 100%)'), 
    0.75
  );
  this.scene.add(this.lights.ambientLight);
  
  this.lights.dirLightBack = new THREE.DirectionalLight(
    new THREE.Color('hsl(0, 0%, 100%)'), 
    0.25
  );
  this.lights.dirLightBack.position.set(30, 100, 100);
  this.scene.add(this.lights.dirLightBack);
  
  this.lights.dirLight = new THREE.DirectionalLight(
    new THREE.Color('hsl(0, 0%, 100%)'), 
    0.25
  );
  this.lights.dirLight.position.set(-30, 100, -100);
  this.scene.add(this.lights.dirLight);
}
Default lights provide balanced illumination suitable for most scenes. They don’t cast shadows.

Real Sunlight

Enable realistic sun-based lighting with the realSunlight option:
window.tb = new Threebox(
  map,
  gl,
  { 
    realSunlight: true,
    realSunlightHelper: true  // Optional: show sun position helper
  }
);
This creates:
  • Directional Light: Positioned based on sun calculations (100% intensity)
  • Hemisphere Light: Sky and ground color simulation (60% intensity)
  • Shadow Mapping: Enabled with high-quality settings
src/Threebox.js:1133-1164
realSunlight: function (helper = false) {
  this.renderer.shadowMap.enabled = true;
  
  this.lights.dirLight = new THREE.DirectionalLight(0xffffff, 1);
  this.scene.add(this.lights.dirLight);
  
  if (helper) {
    this.lights.dirLightHelper = new THREE.DirectionalLightHelper(
      this.lights.dirLight, 
      5
    );
    this.scene.add(this.lights.dirLightHelper);
  }
  
  // Shadow configuration
  let d2 = 1000; 
  let r2 = 2; 
  let mapSize2 = 8192;
  
  this.lights.dirLight.castShadow = true;
  this.lights.dirLight.shadow.radius = r2;
  this.lights.dirLight.shadow.mapSize.width = mapSize2;
  this.lights.dirLight.shadow.mapSize.height = mapSize2;
  this.lights.dirLight.shadow.camera.top = d2;
  this.lights.dirLight.shadow.camera.right = d2;
  this.lights.dirLight.shadow.camera.bottom = -d2;
  this.lights.dirLight.shadow.camera.left = -d2;
  this.lights.dirLight.shadow.camera.near = 1;
  this.lights.dirLight.shadow.camera.visible = true;
  this.lights.dirLight.shadow.camera.far = 400000000;
  
  this.lights.hemiLight = new THREE.HemisphereLight(
    new THREE.Color(0xffffff), 
    new THREE.Color(0xffffff), 
    0.6
  );
  this.lights.hemiLight.color.setHSL(0.661, 0.96, 0.12);
  this.lights.hemiLight.groundColor.setHSL(0.11, 0.96, 0.14);
  this.lights.hemiLight.position.set(0, 0, 50);
  this.scene.add(this.lights.hemiLight);
  
  this.setSunlight();
  
  this.map.once('idle', () => {
    this.setSunlight();
    this.repaint();
  });
}

Sunlight Features

Sun position is calculated based on:
  • Map center coordinates
  • Current date and time
  • Uses suncalc library for accurate astronomical calculations
Update sunlight position:
tb.setSunlight(new Date(), [-122.4194, 37.7749]);
High-quality shadows with:
  • 8192x8192 shadow map resolution
  • 2000m shadow camera frustum
  • Soft shadow radius
Enable shadows on objects:
tb.loadObj(options, function(model) {
  model.castShadow = true;
  model.receiveShadow = true;
  tb.add(model);
});
Hemisphere light simulates:
  • Sky color (HSL: 0.661, 0.96, 0.12)
  • Ground reflection (HSL: 0.11, 0.96, 0.14)
  • Natural ambient bounce light

Custom Lighting

Adding Custom Lights

You can add your own lights to the scene:
// Point light
const pointLight = new THREE.PointLight(0xff0000, 1, 100);
pointLight.position.set(50, 50, 50);
tb.scene.add(pointLight);

// Spot light with shadows
const spotLight = new THREE.SpotLight(0xffffff, 1);
spotLight.position.set(100, 100, 100);
spotLight.castShadow = true;
spotLight.shadow.mapSize.width = 2048;
spotLight.shadow.mapSize.height = 2048;
tb.scene.add(spotLight);

// Ambient light for overall brightness
const ambient = new THREE.AmbientLight(0x404040, 0.5);
tb.scene.add(ambient);

Accessing Light Objects

Threebox exposes light objects through tb.lights:
// Modify ambient light
if (tb.lights.ambientLight) {
  tb.lights.ambientLight.intensity = 0.5;
  tb.lights.ambientLight.color.setHex(0xffffcc);
}

// Modify directional light
if (tb.lights.dirLight) {
  tb.lights.dirLight.intensity = 1.5;
  tb.lights.dirLight.position.set(100, 200, 100);
}

// Update light helper
if (tb.lights.dirLightHelper) {
  tb.updateLightHelper();
}

Light Properties

PropertyTypeDescription
tb.lights.ambientLightTHREE.AmbientLightOverall scene illumination
tb.lights.dirLightTHREE.DirectionalLightMain directional light
tb.lights.dirLightBackTHREE.DirectionalLightBack directional light (defaultLights only)
tb.lights.hemiLightTHREE.HemisphereLightSky/ground hemisphere light (realSunlight only)
tb.lights.dirLightHelperTHREE.DirectionalLightHelperVisual helper for sun direction
tb.lights.pointLightTHREE.PointLightCustom point light (not set by default)

Material & Lighting Examples

Metallic Object

const metallicMaterial = new THREE.MeshStandardMaterial({
  color: 0xcccccc,
  metalness: 0.9,
  roughness: 0.1,
  envMapIntensity: 1.0
});

tb.loadObj({
  obj: '/models/car.glb',
  type: 'gltf',
  units: 'meters',
  material: metallicMaterial  // Override model materials
}, function(model) {
  model.setCoords([-122.4194, 37.7749]);
  model.castShadow = true;
  model.receiveShadow = true;
  tb.add(model);
});

Glass Material

const glassMaterial = new THREE.MeshPhysicalMaterial({
  color: 0xffffff,
  metalness: 0,
  roughness: 0,
  transmission: 0.9,
  thickness: 0.5,
  transparent: true,
  opacity: 0.5
});

const glassBuilding = tb.extrusion({
  coordinates: [/* ... */],
  height: 200,
  materials: glassMaterial
});

Emissive (Glowing) Material

const glowMaterial = new THREE.MeshStandardMaterial({
  color: 0x00ff00,
  emissive: 0x00ff00,
  emissiveIntensity: 0.5
});

const beacon = tb.sphere({
  radius: 10,
  material: glowMaterial,
  units: 'meters'
});

beacon.setCoords([-122.4194, 37.7749, 100]);
tb.add(beacon);

Day/Night Lighting Transition

function updateLighting() {
  const now = new Date();
  const coords = map.getCenter();
  
  // Get sun position
  const sunPos = tb.getSunPosition(now, [coords.lng, coords.lat]);
  
  // Update sun
  tb.setSunlight(now, [coords.lng, coords.lat]);
  
  // Adjust ambient based on time of day
  const hour = now.getHours();
  const isNight = hour < 6 || hour > 20;
  
  if (tb.lights.ambientLight) {
    tb.lights.ambientLight.intensity = isNight ? 0.3 : 0.75;
    tb.lights.ambientLight.color.setHex(isNight ? 0x6666ff : 0xffffff);
  }
  
  tb.map.repaint = true;
}

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

Performance Tips

  • Reduce shadow map size for better performance
  • Limit number of shadow-casting lights (1-2 max)
  • Use smaller shadow camera frustum when possible
// Lower quality but faster
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
  • Use MeshBasicMaterial for UI and overlays (no lighting needed)
  • Use MeshLambertMaterial for simple lit objects (cheapest)
  • Reserve MeshStandardMaterial for hero objects
  • Avoid MeshPhysicalMaterial unless necessary
  • Minimize number of lights (2-4 total recommended)
  • Use baked lighting/shadows when possible
  • Consider hemisphere light instead of multiple directional lights
// Good: Simple lighting setup
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
const sun = new THREE.DirectionalLight(0xffffff, 0.8);

Common Patterns

Museum Lighting (Spotlights)

function addSpotlight(position, target, color = 0xffffff) {
  const spotlight = new THREE.SpotLight(color, 1, 200, Math.PI / 6, 0.5);
  spotlight.position.set(position[0], position[1], position[2]);
  spotlight.target.position.set(target[0], target[1], target[2]);
  spotlight.castShadow = true;
  tb.scene.add(spotlight);
  tb.scene.add(spotlight.target);
  return spotlight;
}

// Illuminate specific objects
addSpotlight([0, 0, 100], [0, 0, 0]);
addSpotlight([50, 50, 100], [50, 50, 0]);

Street Lighting

function createStreetLight(coords, height = 10) {
  // Light post
  const post = tb.tube({
    geometry: [[0, 0, 0], [0, 0, height]],
    radius: 0.5,
    color: 0x333333,
    units: 'meters'
  });
  post.setCoords(coords);
  tb.add(post);
  
  // Point light
  const light = new THREE.PointLight(0xffaa00, 1, 50);
  const worldPos = tb.projectToWorld(coords);
  light.position.set(worldPos.x, worldPos.y, height);
  tb.scene.add(light);
  
  return { post, light };
}

// Create street lights along a path
const lights = [];
for (let i = 0; i < 10; i++) {
  const lng = -122.419 + i * 0.0005;
  lights.push(createStreetLight([lng, 37.7749]));
}

Lighting Best Practices

Too many lights or high shadow map resolutions can severely impact performance. Start simple and optimize as needed.
For realistic outdoor scenes, use realSunlight combined with subtle ambient lighting. For indoor or stylized scenes, use defaultLights and add custom accent lights.

Next Steps

Loading Models

Apply materials and lighting to 3D models

Primitives

Use materials with primitive objects

Build docs developers (and LLMs) love