NgtsSoftShadows
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-soft-shadows', template: ` <ngt-canvas [shadows]="true" [camera]="{ position: [5, 5, 5], fov: 50 }"> <app-scene-graph *canvasContent [enabled]="enabled()" /> </ngt-canvas>
<tweakpane-pane title="Soft Shadows" [container]="host"> <tweakpane-checkbox [(value)]="enabled" label="enabled" /> </tweakpane-pane> `, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'soft-shadows-demo relative block h-full' }, imports: [NgtCanvas, SceneGraph, TweakpanePane, TweakpaneCheckbox],})export default class SoftShadows { static clientProviders = [provideNgtRenderer()];
protected host = inject(ElementRef); protected enabled = signal(true);}import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, input } from '@angular/core';import { extend, NgtArgs } from 'angular-three';import { NgtsOrbitControls } from 'angular-three-soba/controls';import { NgtsSoftShadows } from 'angular-three-soba/misc';import * as THREE from 'three';
@Component({ selector: 'app-scene-graph', template: ` <ngt-color attach="background" *args="['#f0f0f0']" />
<!-- Enable soft shadows with PCSS --> @if (enabled()) { <ngts-soft-shadows [options]="{ size: 25, samples: 17, focus: 0 }" /> }
<!-- 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" />
<!-- Floating sphere --> <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>
<!-- Floating box --> <ngt-mesh [castShadow]="true" [position]="[0, 1, 0]" [rotation]="[0.4, 0.4, 0]"> <ngt-box-geometry *args="[1, 1, 1]" /> <ngt-mesh-standard-material color="#4ecdc4" /> </ngt-mesh>
<!-- Floating torus --> <ngt-mesh [castShadow]="true" [position]="[1.5, 1.2, 0]" [rotation]="[Math.PI / 2, 0, 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, NgtsSoftShadows, NgtsOrbitControls],})export class SceneGraph { enabled = input(true);
protected readonly Math = Math;
constructor() { extend(THREE); }}NgtsSoftShadows is a directive that injects Percentage-Closer Soft Shadows (PCSS) into the scene. PCSS produces contact-hardening soft shadows where shadows are sharper near the contact point and softer further away, creating more realistic shadow effects.
This works by patching Three.jsâs shadow shader chunk at runtime. When the directive is destroyed or options change, it restores the original shader and recompiles affected materials.
Usage
<ngts-soft-shadows [options]="{ size: 25, samples: 10, focus: 0 }" />Basic Setup
For soft shadows to work, you need:
- A light source with
castShadowenabled - Objects with
castShadowandreceiveShadowproperties set - Shadow map enabled on the renderer (handled by
NgtCanvaswithshadowsinput)
<ngt-canvas [shadows]="true"> <ng-template canvasContent> <!-- Enable soft shadows --> <ngts-soft-shadows [options]="{ size: 25, samples: 10 }" />
<!-- Light that casts shadows --> <ngt-directional-light [castShadow]="true" [position]="[5, 5, 5]" />
<!-- Object that casts shadow --> <ngt-mesh [castShadow]="true" [position]="[0, 1, 0]"> <ngt-box-geometry /> <ngt-mesh-standard-material /> </ngt-mesh>
<!-- Ground that receives shadow --> <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>Adjusting Shadow Softness
The size option controls how soft the shadows appear. Larger values create softer, more diffuse shadows:
<!-- Sharp shadows --><ngts-soft-shadows [options]="{ size: 5 }" />
<!-- Medium soft shadows --><ngts-soft-shadows [options]="{ size: 25 }" />
<!-- Very soft shadows --><ngts-soft-shadows [options]="{ size: 50 }" />Performance Considerations
The samples option affects both quality and performance:
<!-- Lower quality, better performance --><ngts-soft-shadows [options]="{ samples: 5 }" />
<!-- Higher quality, more expensive --><ngts-soft-shadows [options]="{ samples: 20 }" />Options
Properties
| name | type | description |
|---|---|---|
| size | number | Size of the light source. The larger the value, the softer the shadows. Default: 25 |
| samples | number | Number of samples for shadow calculation. More samples = less noise but more expensive. Default: 10 |
| focus | number | Depth focus to shift the focal point where the shadow is sharpest. 0 means at the beginning. Default: 0 |