three
Version:
JavaScript 3D library
162 lines (110 loc) • 4.71 kB
JavaScript
import { Color, Node, Vector3, Vector4 } from 'three/webgpu';
import { Loop, NodeUpdateType, getDistanceAttenuation, positionView, renderGroup, smoothstep, uniform, uniformArray, vec3 } from 'three/tsl';
const _lightPosition = /*@__PURE__*/ new Vector3();
const _targetPosition = /*@__PURE__*/ new Vector3();
const warn = ( message ) => {
console.warn( `THREE.SpotLightDataNode: ${ message }` );
};
/**
* Batched data node for simple spot lights in dynamic lighting mode.
*
* Projected spot lights keep the default per-light path.
*
* @augments Node
*/
class SpotLightDataNode extends Node {
static get type() {
return 'SpotLightDataNode';
}
constructor( maxCount = 16 ) {
super();
this.maxCount = maxCount;
this._lights = [];
this._colors = [];
this._positionsAndCutoff = [];
this._directionsAndDecay = [];
this._cones = [];
for ( let i = 0; i < maxCount; i ++ ) {
this._colors.push( new Color() );
this._positionsAndCutoff.push( new Vector4() );
this._directionsAndDecay.push( new Vector4() );
this._cones.push( new Vector4() );
}
this.colorsNode = uniformArray( this._colors, 'color' ).setGroup( renderGroup );
this.positionsAndCutoffNode = uniformArray( this._positionsAndCutoff, 'vec4' ).setGroup( renderGroup );
this.directionsAndDecayNode = uniformArray( this._directionsAndDecay, 'vec4' ).setGroup( renderGroup );
this.conesNode = uniformArray( this._cones, 'vec4' ).setGroup( renderGroup );
this.countNode = uniform( 0, 'int' ).setGroup( renderGroup );
this.updateType = NodeUpdateType.RENDER;
}
setLights( lights ) {
if ( lights.length > this.maxCount ) {
warn( `${ lights.length } lights exceed the configured max of ${ this.maxCount }. Excess lights are ignored.` );
}
this._lights = lights;
return this;
}
update( { camera } ) {
const count = Math.min( this._lights.length, this.maxCount );
this.countNode.value = count;
for ( let i = 0; i < count; i ++ ) {
const light = this._lights[ i ];
this._colors[ i ].copy( light.color ).multiplyScalar( light.intensity );
_lightPosition.setFromMatrixPosition( light.matrixWorld );
_lightPosition.applyMatrix4( camera.matrixWorldInverse );
const positionAndCutoff = this._positionsAndCutoff[ i ];
positionAndCutoff.x = _lightPosition.x;
positionAndCutoff.y = _lightPosition.y;
positionAndCutoff.z = _lightPosition.z;
positionAndCutoff.w = light.distance;
_lightPosition.setFromMatrixPosition( light.matrixWorld );
_targetPosition.setFromMatrixPosition( light.target.matrixWorld );
_lightPosition.sub( _targetPosition ).transformDirection( camera.matrixWorldInverse );
const directionAndDecay = this._directionsAndDecay[ i ];
directionAndDecay.x = _lightPosition.x;
directionAndDecay.y = _lightPosition.y;
directionAndDecay.z = _lightPosition.z;
directionAndDecay.w = light.decay;
const cone = this._cones[ i ];
cone.x = Math.cos( light.angle );
cone.y = Math.cos( light.angle * ( 1 - light.penumbra ) );
}
}
setup( builder ) {
const surfacePosition = builder.context.positionView || positionView;
const { lightingModel, reflectedLight } = builder.context;
const dynDiffuse = vec3( 0 ).toVar( 'dynSpotDiffuse' );
const dynSpecular = vec3( 0 ).toVar( 'dynSpotSpecular' );
Loop( this.countNode, ( { i } ) => {
const positionAndCutoff = this.positionsAndCutoffNode.element( i );
const lightViewPosition = positionAndCutoff.xyz;
const cutoffDistance = positionAndCutoff.w;
const directionAndDecay = this.directionsAndDecayNode.element( i );
const spotDirection = directionAndDecay.xyz;
const decayExponent = directionAndDecay.w;
const cone = this.conesNode.element( i );
const coneCos = cone.x;
const penumbraCos = cone.y;
const lightVector = lightViewPosition.sub( surfacePosition ).toVar();
const lightDirection = lightVector.normalize().toVar();
const lightDistance = lightVector.length();
const angleCos = lightDirection.dot( spotDirection );
const spotAttenuation = smoothstep( coneCos, penumbraCos, angleCos );
const distanceAttenuation = getDistanceAttenuation( {
lightDistance,
cutoffDistance,
decayExponent
} );
const lightColor = this.colorsNode.element( i ).mul( spotAttenuation ).mul( distanceAttenuation ).toVar();
lightingModel.direct( {
lightDirection,
lightColor,
lightNode: { light: {}, shadowNode: null },
reflectedLight: { directDiffuse: dynDiffuse, directSpecular: dynSpecular }
}, builder );
} );
reflectedLight.directDiffuse.addAssign( dynDiffuse );
reflectedLight.directSpecular.addAssign( dynSpecular );
}
}
export default SpotLightDataNode;