Skip to content

Movement Regression

High-end 3D platforms maintain fluid 60 FPS experiences across all devices by implementing performance regression. This technique temporarily reduces quality of effects, textures, and shadows during movement to maintain responsiveness.

Play with the example below to see the effect of this technique.

// Credits: https://codesandbox.io/p/sandbox/pz0q6?file=/src/App.js:1,1-115,1
// This demo shows how to use react-three-fibers regression system
// When call call state.regress() nothing really will happen, all it
// does is setting a flag. But parts of the scene graph can now respond
// to it in whatever way the want. Here we cause regression on mouse-move
// and scale the pixel ratio as well as skipping some post-processing effects.
@Component({
selector: 'app-scene-graph',
template: `
<ngt-color *args="['lightblue']" attach="background" />
<ngt-fog *args="['#000', 0.8, 1]" attach="fog" />
<app-lights />
<app-ybot [position]="[0, -1.3, 0]" />
<ngts-text text="angular" [options]="{ position: [0, 0, -0.15], fontSize: 0.5, letterSpacing: 0 }">
<ngt-mesh-standard-material [fog]="false" emissive="white" [emissiveIntensity]="1.01" [toneMapped]="false" />
</ngts-text>
<ngt-mesh [scale]="4" [position]="[0, 1, -0.2]">
<ngt-plane-geometry />
<ngt-mesh-standard-material color="lightblue" [toneMapped]="false" [fog]="false" [envMapIntensity]="0" />
</ngt-mesh>
<ngts-adaptive-dpr pixelated />
<app-effects />
`,
imports: [NgtArgs, Lights, Effects, YBot, NgtsText, NgtsAdaptiveDpr],
hostDirectives: [LerpedMouse],
changeDetection: ChangeDetectionStrategy.OnPush,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class SceneGraph {}

Credits: R3F Movement Regression

Performance State

Angular Three’s store provides a performance object accessible through injectNgtStore():

interface NgtPerformance {
current: number; // Alternates between min and max
min: number; // Lower bound (< 1)
max: number; // Upper bound (≤ 1)
debounce: number; // Milliseconds until returning to max
regress(): void; // Trigger temporary regression
}

You can configure default performance settings on NgtCanvas

<ngt-canvas [performance]="{ min: 0.5, max: 1, debounce: 200 }" />

Implementing Regression

Triggering Regression

Call regress() when you want to temporarily reduce quality. Common triggers include mouse movement or camera controls

@Component({
template: `
<ngt-orbit-controls (change)="onControlsChange()" />
`
})
export class SceneComponent {
private store = injectNgtStore();
onControlsChange() {
this.store.snapshot.performance.regress();
}
}

Responding to Performance Changes

Simply calling regress() won’t affect anything by itself. Your components need to listen to and react to the current performance value:

  • 1 (max) indicates optimal performance
  • < 1 indicates regression is needed

The value determines how much to scale down

Here’s a directive that adapts pixel ratio based on performance:

adaptive-pixel-ratio.ts
@Directive({ selector: 'adaptive-pixel-ratio' })
export class AdaptivePixelRatio {
constructor() {
const store = injectNgtStore();
effect(() => {
store.snapshot.setDpr(window.devicePixelRatio * store.performance.current());
});
}
}

Then you can drop it on your scene graph

<adaptive-pixel-ratio />

angular-three-soba provides a pre-made directive NgtsAdaptiveDpr for this purpose.