For regular shadows (not contact shadows) a trick is to patch ShadowNode.prototype.setupShadowFilter with TSL (when using WebGPURenderer) to implement custom logic for the shadow (return a node that calculates values from 0 (shadow) to 1 (no shadow):
const originalSetupShadowFilter = THREE.ShadowNode.prototype.setupShadowFilter;
THREE.ShadowNode.prototype.setupShadowFilter = function ( builder, inputs ) {
// base is the current value 0 to 1 for the current shadow fragment
const base = originalSetupShadowFilter.call( this, builder, inputs );
return yourCalculation( base, 123 )
};
Here’s an example.