or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

accessibility.mdanimation.mdannotation.mdar.mdcontrols.mdenvironment.mdindex.mdloading.mdscene-graph.md
tile.json

environment.mddocs/

Environment and Lighting

Configure realistic lighting, shadows, and environment maps to enhance the visual quality and realism of 3D models.

Capabilities

Environment Image

Set environment maps for realistic lighting and reflections.

/**
 * URL to HDR environment image for lighting and reflections
 * Supports HDR (.hdr, .exr) and LDR (.jpg, .png) formats
 * Set to 'neutral' for built-in neutral lighting
 */
environmentImage: string;

Usage:

<!-- HDR environment for realistic lighting -->
<model-viewer 
  src="models/car.glb"
  environment-image="environments/studio.hdr">
</model-viewer>

<!-- Built-in neutral lighting -->
<model-viewer 
  src="models/product.glb"
  environment-image="neutral">
</model-viewer>
// Switch environments dynamically
const environments = [
  'environments/studio.hdr',
  'environments/outdoor.hdr', 
  'environments/indoor.hdr'
];

let currentEnv = 0;
function cycleEnvironment() {
  modelViewer.environmentImage = environments[currentEnv];
  currentEnv = (currentEnv + 1) % environments.length;
}

Skybox Background

Control the background environment visible behind the model.

/**
 * URL to skybox background image
 * Can be different from environmentImage for creative effects
 * Set to same as environmentImage for consistent lighting
 */
skyboxImage: string;

Usage:

<!-- Different skybox and environment -->
<model-viewer 
  src="models/spaceship.glb"
  environment-image="environments/studio.hdr"
  skybox-image="backgrounds/space.jpg">
</model-viewer>

<!-- Matching environment and skybox -->
<model-viewer 
  src="models/outdoor-scene.glb"
  environment-image="environments/park.hdr"
  skybox-image="environments/park.hdr">
</model-viewer>

Exposure Control

Adjust overall brightness and exposure of the scene.

/**
 * Scene exposure/brightness multiplier
 * Values > 1.0 increase brightness, < 1.0 decrease brightness
 * Typical range: 0.1 to 3.0
 */
exposure: number;

Usage:

<!-- Brighter exposure for dark models -->
<model-viewer 
  src="models/dark-material.glb"
  exposure="1.5">
</model-viewer>
// Dynamic exposure adjustment
const exposureSlider = document.getElementById('exposure');
exposureSlider.addEventListener('input', (e) => {
  modelViewer.exposure = parseFloat(e.target.value);
});

Shadow Configuration

Control shadow rendering for increased realism.

/**
 * Shadow intensity (0 = no shadows, 1 = full shadows)
 * Shadows are cast onto an invisible ground plane
 */
shadowIntensity: number;

/**
 * Shadow softness/blur amount
 * Higher values create softer, more diffused shadows
 * Range: 0.0 (sharp) to 2.0 (very soft)
 */
shadowSoftness: number;

Usage:

<!-- Soft, realistic shadows -->
<model-viewer 
  src="models/furniture.glb"
  shadow-intensity="0.8"
  shadow-softness="1.2">
</model-viewer>

<!-- Sharp shadows for technical visualization -->
<model-viewer 
  src="models/machine.glb"
  shadow-intensity="1.0"
  shadow-softness="0.1">
</model-viewer>

Neutral Lighting

Enable simplified, consistent lighting for product visualization.

/**
 * Use neutral lighting setup instead of environment-based lighting
 * Provides consistent, diffused lighting ideal for product shots
 * Overrides environmentImage when enabled
 */
neutralLighting: boolean;

/**
 * Tone mapping method for HDR rendering
 */
toneMapping: 'auto' | 'aces' | 'agx' | 'commerce' | 'neutral' | 'reinhard' | 'cineon' | 'linear' | 'none';

/**
 * Height positioning for grounded skybox
 */
skyboxHeight: string;

Usage:

<!-- Consistent product lighting -->
<model-viewer 
  src="models/jewelry.glb"
  neutral-lighting>
</model-viewer>

<!-- Compare neutral vs environment lighting -->
<button onclick="toggleLighting()">Toggle Lighting</button>
<script>
function toggleLighting() {
  modelViewer.neutralLighting = !modelViewer.neutralLighting;
}
</script>

Lighting Presets

Studio Lighting

Professional studio setup with controlled lighting:

<model-viewer 
  src="models/product.glb"
  environment-image="environments/studio_small_03_1k.hdr"
  exposure="1.0"
  shadow-intensity="0.7"
  shadow-softness="1.0">
</model-viewer>

Outdoor Lighting

Natural outdoor lighting with soft shadows:

<model-viewer 
  src="models/architecture.glb"
  environment-image="environments/kloofendal_48d_partly_cloudy_1k.hdr"
  exposure="1.2"
  shadow-intensity="0.9"
  shadow-softness="1.5">
</model-viewer>

Dramatic Lighting

High contrast lighting for artistic effects:

<model-viewer 
  src="models/sculpture.glb"
  environment-image="environments/venice_sunset_1k.hdr"
  exposure="0.8"
  shadow-intensity="1.0"
  shadow-softness="0.5">
</model-viewer>

Neutral Product Lighting

Clean, consistent lighting for e-commerce:

<model-viewer 
  src="models/product.glb"
  neutral-lighting
  shadow-intensity="0.3"
  shadow-softness="1.8">
</model-viewer>

Advanced Usage Examples

Dynamic Lighting Control

Create interactive lighting controls:

<div class="lighting-controls">
  <label>
    Exposure: <input type="range" id="exposure" min="0.1" max="3.0" step="0.1" value="1.0">
  </label>
  <label>
    Shadow Intensity: <input type="range" id="shadow" min="0" max="1" step="0.1" value="0.7">
  </label>
  <label>
    Shadow Softness: <input type="range" id="softness" min="0" max="2" step="0.1" value="1.0">
  </label>
</div>

<script>
document.getElementById('exposure').addEventListener('input', (e) => {
  modelViewer.exposure = parseFloat(e.target.value);
});

document.getElementById('shadow').addEventListener('input', (e) => {
  modelViewer.shadowIntensity = parseFloat(e.target.value);
});

document.getElementById('softness').addEventListener('input', (e) => {
  modelViewer.shadowSoftness = parseFloat(e.target.value);
});
</script>

Environment Presets

Create preset lighting environments:

const lightingPresets = {
  studio: {
    environmentImage: 'environments/studio.hdr',
    exposure: 1.0,
    shadowIntensity: 0.7,
    shadowSoftness: 1.0,
    neutralLighting: false
  },
  outdoor: {
    environmentImage: 'environments/park.hdr', 
    exposure: 1.2,
    shadowIntensity: 0.9,
    shadowSoftness: 1.5,
    neutralLighting: false
  },
  neutral: {
    environmentImage: null,
    exposure: 1.0,
    shadowIntensity: 0.3,
    shadowSoftness: 1.8,
    neutralLighting: true
  }
};

function applyLightingPreset(presetName) {
  const preset = lightingPresets[presetName];
  if (!preset) return;
  
  Object.keys(preset).forEach(key => {
    if (preset[key] !== null) {
      modelViewer[key] = preset[key];
    }
  });
}

// UI for preset selection
document.getElementById('preset-studio').addEventListener('click', 
  () => applyLightingPreset('studio'));
document.getElementById('preset-outdoor').addEventListener('click', 
  () => applyLightingPreset('outdoor'));
document.getElementById('preset-neutral').addEventListener('click', 
  () => applyLightingPreset('neutral'));

Responsive Lighting

Adjust lighting based on screen size or device capabilities:

function setupResponsiveLighting() {
  const isMobile = window.innerWidth < 768;
  const hasHDRSupport = checkHDRSupport();
  
  if (isMobile) {
    // Lighter lighting processing for mobile
    modelViewer.neutralLighting = true;
    modelViewer.shadowIntensity = 0.3;
    modelViewer.shadowSoftness = 1.0;
  } else if (hasHDRSupport) {
    // Full HDR environment for capable devices
    modelViewer.environmentImage = 'environments/studio_4k.hdr';
    modelViewer.shadowIntensity = 0.8;
    modelViewer.shadowSoftness = 1.2;
  } else {
    // Fallback to LDR environment
    modelViewer.environmentImage = 'environments/studio.jpg';
    modelViewer.shadowIntensity = 0.6;
    modelViewer.shadowSoftness = 1.0;
  }
}

function checkHDRSupport() {
  // Simplified HDR detection
  return window.screen && window.screen.colorGamut === 'p3';
}

window.addEventListener('resize', setupResponsiveLighting);
modelViewer.addEventListener('load', setupResponsiveLighting);

Lighting Animation

Animate lighting changes for dynamic effects:

class LightingAnimator {
  constructor(modelViewer) {
    this.modelViewer = modelViewer;
    this.isAnimating = false;
  }
  
  async animateToPreset(targetPreset, duration = 1000) {
    if (this.isAnimating) return;
    this.isAnimating = true;
    
    const startValues = {
      exposure: this.modelViewer.exposure,
      shadowIntensity: this.modelViewer.shadowIntensity,
      shadowSoftness: this.modelViewer.shadowSoftness
    };
    
    const startTime = performance.now();
    
    const animate = (currentTime) => {
      const elapsed = currentTime - startTime;
      const progress = Math.min(elapsed / duration, 1);
      
      // Smooth easing function
      const eased = 1 - Math.pow(1 - progress, 3);
      
      // Interpolate values
      this.modelViewer.exposure = this.lerp(
        startValues.exposure, targetPreset.exposure, eased);
      this.modelViewer.shadowIntensity = this.lerp(
        startValues.shadowIntensity, targetPreset.shadowIntensity, eased);
      this.modelViewer.shadowSoftness = this.lerp(
        startValues.shadowSoftness, targetPreset.shadowSoftness, eased);
      
      if (progress < 1) {
        requestAnimationFrame(animate);
      } else {
        // Set final values and environment
        if (targetPreset.environmentImage) {
          this.modelViewer.environmentImage = targetPreset.environmentImage;
        }
        this.modelViewer.neutralLighting = targetPreset.neutralLighting;
        this.isAnimating = false;
      }
    };
    
    requestAnimationFrame(animate);
  }
  
  lerp(start, end, t) {
    return start + (end - start) * t;
  }
}

// Usage
const animator = new LightingAnimator(modelViewer);
animator.animateToPreset(lightingPresets.outdoor, 2000);