Appearance
EnvironmentService
Manages scene lighting, HDRI environment maps, soft contact shadows, and viewer backgrounds. On init it creates the built-in "outdoor" preset, applies it as the default environment, sets up ambient lighting, and applies viewer.environment.background when that config is present. DOM-backed backgrounds are rendered behind the WebGL canvas for transparent, solid, gradient, and image backgrounds; THREE.Texture backgrounds are applied directly to the Three.js scene.
Implementation Layout
The environment service is split across src/services/environment:
| File | Responsibility |
|---|---|
EnvironmentService.ts | Public service API, preset/HDRI orchestration, lighting, event emission, and lifecycle coordination. |
ShadowSystem.ts | Contact-shadow render pipeline and shadow GPU resources. |
ShadowAutoUpdateController.ts | Event listeners, debounce/throttle state, and frame-callback scheduling for automatic shadow refreshes. |
EnvironmentBackgroundManager.ts | DOM background layer, background normalization, image loading, canvas export drawing. |
types.ts | EnvironmentPreset, LightingConfig, HDRIConfig, and ShadowConfig. |
Access
Registered under the name "environment". No service dependencies.
EnvironmentService is not exported from the SDK entry point. Reach it by its registered name and use the class only as a type.
ts
import type { EnvironmentService } from "@optellix/xviewr-sdk";
await viewer.ready();
const env = viewer.getService<EnvironmentService>("environment");
env?.setBackground({
type: "gradient",
shape: "radial",
startColor: "#1a1a2e",
endColor: "#16213e",
});Public API
Lifecycle
init(context: ServiceContext): Promise<void>destroy(): Promise<void>getStatus(): ServiceStatus
Environment Presets
setEnvironment(preset: string): Promise<void>- loads the named preset's HDRI when one is configured, updatesscene.environment, applies the preset intensity throughHDRIConfig, reapplies the active background mode, and emitsenvironment:changed. Built-in preset:"outdoor". Throws if the preset is not registered.registerPreset(name: string, preset: EnvironmentPreset): void- adds a custom preset.getPresets(): string[]- returns all registered preset names.getEnvironmentMap(): THREE.Texture | null- returns the current PMREM-generated environment texture.
HDRI
setHDRI(url: string): Promise<void>- loads a.hdrfile by URL, PMREM-processes it, replaces the current environment map, reapplies the active background mode, and emitsenvironment:hdri-changed.setHDRIConfig(config: Partial<HDRIConfig>): void- patches the stored HDRI config and applies intensity, rotation, background blurriness, and background intensity to the scene.getHDRIConfig(): HDRIConfigsetShowEnvironmentBackground(show: boolean): void- whentrue, renders the HDRI as the scene background. Whenfalse, the configured viewer background takes over.getShowEnvironmentBackground(): boolean
Lighting
setLighting(config: LightingConfig): void- updates ambient and directional lights. Creates lights if they do not yet exist.
Shadows
setShadows(enabled: boolean, config?: ShadowConfig): void- enables or disables contact shadows, configures auto-update behavior, creates the shadow system on first enable, updatesrenderer.shadowMap.enabled, and emitsenvironment:shadows-changed.requestShadowUpdate(reason?: string): void- requests a debounced contact-shadow refresh. This is useful for integrations that know a visible object changed but do not need an immediate render-target update.updateShadows(): void- immediately re-renders the contact-shadow map.
The shadow system uses a bottom-up depth projection from below the model, then applies separable horizontal and vertical Gaussian blur passes into ping-pong render targets. Existing config names remain supported:
intensitymaps to contact-shadow opacity.blurRadiusmaps to blur amount.shadowMapSizemaps to render-target resolution.
Auto-update is enabled by default. The service listens only to existing high-signal events that imply visible scene bounds may have changed:
- model finalization:
model:processing-complete,model:unloaded - transforms:
transformcontrols:transform-applied,transformcontrols:reset - visibility:
tree:node-visibility-changed - explosion transforms:
explosion:applied,explosion:reset,explosion:depth-applied - animation lifecycle events: starts/resumes mark animation as active; stops/pauses/completions trigger one final update
During active animations, shadows update on a throttled interval controlled by animationUpdateMs. Calling updateShadows() clears any pending debounced update before rendering immediately.
Background
setBackground(background: ViewerBackgroundInput): void- stores the viewer background, applies it when HDRI background mode is off, and emitsenvironment:background-changed. Accepts a config object,THREE.Color,THREE.Texture, ornull(transparent).getBackground(): ViewerBackgroundConfig | null- returns the normalized public background config.THREE.Colorinputs return a solid-color config;THREE.Textureinputs returnnull.drawBackgroundToCanvas(context: CanvasRenderingContext2D, width: number, height: number): void- draws the current solid, gradient, or image background into an off-screen canvas context. Transparent and texture backgrounds are skipped. Used by canvas export.
When HDRI background mode is enabled, setBackground() still stores the requested viewer background, but the HDRI remains visible until setShowEnvironmentBackground(false) is called.
Image backgrounds load asynchronously. Non-data: and non-blob: URLs are requested with crossOrigin = "anonymous". If loading or decoding fails, the manager keeps the fallback color when one is configured and emits environment:background-error.
Types
EnvironmentPreset
ts
interface EnvironmentPreset {
name: string;
hdriPath: string | null;
intensity: number;
}HDRIConfig
ts
interface HDRIConfig {
intensity: number;
rotation: number; // degrees
backgroundBlurriness: number;
backgroundIntensity: number;
}LightingConfig
ts
interface LightingConfig {
ambient?: {
color: string | number;
intensity: number;
};
directional?: Array<{
color: string | number;
intensity: number;
position: [number, number, number];
castShadow?: boolean;
shadowMapSize?: number;
}>;
}ShadowConfig
ts
interface ShadowConfig {
// Backward-compatible names
intensity?: number;
blurRadius?: number;
shadowMapSize?: number;
// Contact-shadow options
resolution?: number;
blur?: number;
opacity?: number;
smooth?: boolean;
color?: string | number | THREE.Color;
padding?: number;
groundOffset?: number;
ambientFill?: number;
ambientFalloff?: number;
maxPollAttempts?: number;
pollDelay?: number;
autoUpdate?: boolean;
updateDebounceMs?: number;
animationUpdateMs?: number;
}| Option | Default | Description |
|---|---|---|
intensity / opacity | 0.6 | Overall opacity of the rendered shadow plane. intensity is the legacy name. |
blurRadius / blur | 2.5 | Blur amount for the separable Gaussian blur pass. blurRadius is the legacy name. |
shadowMapSize / resolution | 512 | Width and height of the shadow render targets. shadowMapSize is the legacy name. |
smooth | true | Runs a second lighter blur pass for softer edges. |
color | "#000000" | Shadow and ambient-fill color. |
padding | 1.2 | Multiplier applied to model X/Z bounds when sizing the shadow receiver. |
groundOffset | 0.01 | Vertical offset above the model's minimum Y to avoid z-fighting. |
ambientFill | 0 | Optional radial fill under the contact shadow. |
ambientFalloff | 0.5 | Controls how quickly the ambient fill fades toward receiver edges. |
maxPollAttempts | 20 | Number of startup attempts to wait for valid model bounds. |
pollDelay | 500 | Delay in milliseconds between startup bounds checks. |
autoUpdate | true | Registers event listeners and a frame callback to refresh shadows automatically. |
updateDebounceMs | 75 | Debounce window for event-driven shadow refreshes. |
animationUpdateMs | 125 | Throttle interval for shadow refreshes while animation is active. |
ViewerBackgroundInput
ts
type ViewerBackgroundInput =
| { type: "transparent" }
| { type: "solid"; color: string }
| {
type: "gradient";
shape?: "vertical" | "radial";
startColor: string;
endColor: string;
}
| {
type: "image";
url: string;
mode?: "fit" | "fill" | "tile";
fallbackColor?: string;
}
| THREE.Color
| THREE.Texture
| null;Events
| Event | Payload |
|---|---|
environment:changed | { preset: string; intensity: number } |
environment:shadows-changed | { enabled: boolean } |
environment:background-changed | { background: ViewerBackgroundInput } |
The typed EventMap currently declares the three events above, but its environment:background-changed payload is narrower than the implementation. The following events are also emitted by the service implementation but are not currently declared in EventMap:
| Event | Payload |
|---|---|
environment:hdri-changed | { url: string } |
environment:hdri-config-changed | { config: HDRIConfig } |
environment:background-mode-changed | { showEnvironment: boolean } |
environment:background-error | { background: ViewerBackgroundConfig | null; error: Error } |
Example
ts
const env = viewer.getService<EnvironmentService>("environment");
if (!env) return;
env.setBackground({
type: "gradient",
shape: "radial",
startColor: "#ffffff",
endColor: "#cccccc",
});
viewer.on("model:loaded", () => {
env.setShadows(true, {
intensity: 0.45,
blurRadius: 4,
shadowMapSize: 1024,
padding: 1.2,
autoUpdate: true,
});
});
await env.setHDRI("/assets/studio.hdr");
env.setHDRIConfig({ intensity: 1.5, rotation: 45 });
env.setShowEnvironmentBackground(true);