NgtsBakeShadows
import { ChangeDetectionStrategy, Component, ElementRef, inject, signal } from '@angular/core';import { TweakpaneCheckbox, TweakpanePane } from 'angular-three-tweakpane';import { NgtCanvas, provideNgtRenderer } from 'angular-three/dom';import { SceneGraph } from './scene-graph';
@Component({ selector: 'app-bake-shadows', template: ` <ngt-canvas [shadows]="true" [camera]="{ position: [5, 5, 5], fov: 50 }"> <app-scene-graph *canvasContent [enabled]="enabled()" /> </ngt-canvas>
<tweakpane-pane title="Bake Shadows" [container]="host"> <tweakpane-checkbox [(value)]="enabled" label="enabled" /> </tweakpane-pane> `, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'bake-shadows-demo relative block h-full' }, imports: [NgtCanvas, SceneGraph, TweakpanePane, TweakpaneCheckbox],})export default class BakeShadows { static clientProviders = [provideNgtRenderer()];
protected host = inject(ElementRef); protected enabled = signal(true);}import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, input, viewChild,} from '@angular/core';import { beforeRender, extend, NgtArgs } from 'angular-three';import { NgtsOrbitControls } from 'angular-three-soba/controls';import { NgtsBakeShadows } from 'angular-three-soba/misc';import * as THREE from 'three';
@Component({ selector: 'app-scene-graph', template: ` <ngt-color attach="background" *args="['#f0f0f0']" />
<!-- Bake shadows for static scene --> @if (enabled()) { <ngts-bake-shadows /> }
<!-- Directional light that casts shadows --> <ngt-directional-light [castShadow]="true" [position]="[5, 8, 5]" [intensity]="1.5" [shadow.mapSize.width]="1024" [shadow.mapSize.height]="1024" [shadow.camera.far]="50" [shadow.camera.left]="-10" [shadow.camera.right]="10" [shadow.camera.top]="10" [shadow.camera.bottom]="-10" />
<!-- Ambient light for fill --> <ngt-ambient-light [intensity]="0.4" />
<!-- Static sphere (doesn't spin) --> <ngt-mesh [castShadow]="true" [position]="[-1.5, 1.5, 0]"> <ngt-sphere-geometry *args="[0.5, 32, 32]" /> <ngt-mesh-standard-material color="#ff6b6b" /> </ngt-mesh>
<!-- Spinning box --> <ngt-mesh #box [castShadow]="true" [position]="[0, 1, 0]"> <ngt-box-geometry *args="[1, 1, 1]" /> <ngt-mesh-standard-material color="#4ecdc4" /> </ngt-mesh>
<!-- Spinning torus --> <ngt-mesh #torus [castShadow]="true" [position]="[1.5, 1.2, 0]"> <ngt-torus-geometry *args="[0.4, 0.15, 16, 32]" /> <ngt-mesh-standard-material color="#ffe66d" /> </ngt-mesh>
<!-- Ground plane that receives shadows --> <ngt-mesh [receiveShadow]="true" [rotation]="[-Math.PI / 2, 0, 0]" [position]="[0, 0, 0]"> <ngt-plane-geometry *args="[15, 15]" /> <ngt-mesh-standard-material color="#f8f8f8" /> </ngt-mesh>
<ngts-orbit-controls [options]="{ makeDefault: true }" /> `, schemas: [CUSTOM_ELEMENTS_SCHEMA], changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgtArgs, NgtsBakeShadows, NgtsOrbitControls],})export class SceneGraph { enabled = input(true);
protected readonly Math = Math;
private boxRef = viewChild<ElementRef<THREE.Mesh>>('box'); private torusRef = viewChild<ElementRef<THREE.Mesh>>('torus');
constructor() { extend(THREE);
beforeRender(({ delta }) => { const box = this.boxRef()?.nativeElement; if (box) { box.rotation.x += delta; box.rotation.y += delta * 0.5; }
const torus = this.torusRef()?.nativeElement; if (torus) { torus.rotation.x += delta * 0.5; torus.rotation.z += delta; } }); }}NgtsBakeShadows is a directive that disables automatic shadow map updates for performance optimization.
When added to the scene, this directive sets shadowMap.autoUpdate to false and triggers a single needsUpdate to bake the current shadow state. This is useful for static scenes where shadows don’t need to update every frame.
On cleanup, automatic shadow updates are restored.
Usage
<ngt-group> <ngts-bake-shadows /> <!-- static meshes with shadows --></ngt-group>When to Use
Use NgtsBakeShadows when:
- Your scene has static objects that don’t move
- Shadow-casting lights are stationary
- You want to improve performance by avoiding per-frame shadow calculations
Example with Static Scene
<ngt-canvas [shadows]="true"> <ng-template canvasContent> <!-- Bake shadows once --> <ngts-bake-shadows />
<!-- Static directional light --> <ngt-directional-light [castShadow]="true" [position]="[5, 5, 5]" />
<!-- Static objects --> <ngt-mesh [castShadow]="true" [position]="[0, 1, 0]"> <ngt-box-geometry /> <ngt-mesh-standard-material /> </ngt-mesh>
<ngt-mesh [receiveShadow]="true" [rotation]="[-Math.PI / 2, 0, 0]"> <ngt-plane-geometry *args="[10, 10]" /> <ngt-mesh-standard-material /> </ngt-mesh> </ng-template></ngt-canvas>Performance Considerations
- Shadows are only calculated once when the directive is initialized
- Moving objects or lights after baking will not update shadows
- Remove the directive if you need dynamic shadows again