Accueil / npm / three@0.186.1 / examples / jsm / tsl / lighting / data / SpotLightDataNode.js

SpotLightDataNode.js

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;