|
|
@@ -1,442 +0,0 @@
|
|
|
-import { DataTexture, FloatType, RGBAFormat, Vector2, Vector3, LightsNode, NodeUpdateType } from 'three/webgpu';
|
|
|
-
|
|
|
-import {
|
|
|
- attributeArray, nodeProxy, int, float, vec2, ivec2, ivec4, uniform, Break, Loop, positionView,
|
|
|
- Fn, If, Return, textureLoad, instanceIndex, screenCoordinate, directPointLight
|
|
|
-} from 'three/tsl';
|
|
|
-
|
|
|
-/**
|
|
|
- * TSL function that checks if a circle intersects with an axis-aligned bounding box (AABB).
|
|
|
- *
|
|
|
- * @tsl
|
|
|
- * @function
|
|
|
- * @param {Node<vec2>} circleCenter - The center of the circle.
|
|
|
- * @param {Node<float>} radius - The radius of the circle.
|
|
|
- * @param {Node<vec2>} minBounds - The minimum bounds of the AABB.
|
|
|
- * @param {Node<vec2>} maxBounds - The maximum bounds of the AABB.
|
|
|
- * @return {Node<bool>} True if the circle intersects the AABB.
|
|
|
- */
|
|
|
-export const circleIntersectsAABB = /*@__PURE__*/ Fn( ( [ circleCenter, radius, minBounds, maxBounds ] ) => {
|
|
|
-
|
|
|
- // Find the closest point on the AABB to the circle's center using method chaining
|
|
|
- const closestX = minBounds.x.max( circleCenter.x.min( maxBounds.x ) );
|
|
|
- const closestY = minBounds.y.max( circleCenter.y.min( maxBounds.y ) );
|
|
|
-
|
|
|
- // Compute the distance between the circle's center and the closest point
|
|
|
- const distX = circleCenter.x.sub( closestX );
|
|
|
- const distY = circleCenter.y.sub( closestY );
|
|
|
-
|
|
|
- // Calculate the squared distance
|
|
|
- const distSquared = distX.mul( distX ).add( distY.mul( distY ) );
|
|
|
-
|
|
|
- return distSquared.lessThanEqual( radius.mul( radius ) );
|
|
|
-
|
|
|
-} ).setLayout( {
|
|
|
- name: 'circleIntersectsAABB',
|
|
|
- type: 'bool',
|
|
|
- inputs: [
|
|
|
- { name: 'circleCenter', type: 'vec2' },
|
|
|
- { name: 'radius', type: 'float' },
|
|
|
- { name: 'minBounds', type: 'vec2' },
|
|
|
- { name: 'maxBounds', type: 'vec2' }
|
|
|
- ]
|
|
|
-} );
|
|
|
-
|
|
|
-const _vector3 = /*@__PURE__*/ new Vector3();
|
|
|
-const _size = /*@__PURE__*/ new Vector2();
|
|
|
-
|
|
|
-/**
|
|
|
- * A custom version of `LightsNode` implementing tiled lighting. This node is used in
|
|
|
- * {@link TiledLighting} to overwrite the renderer's default lighting with
|
|
|
- * a custom implementation.
|
|
|
- *
|
|
|
- * @augments LightsNode
|
|
|
- * @three_import import { tiledLights } from 'three/addons/tsl/lighting/TiledLightsNode.js';
|
|
|
- */
|
|
|
-class TiledLightsNode extends LightsNode {
|
|
|
-
|
|
|
- static get type() {
|
|
|
-
|
|
|
- return 'TiledLightsNode';
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Constructs a new tiled lights node.
|
|
|
- *
|
|
|
- * @param {number} [maxLights=1024] - The maximum number of lights.
|
|
|
- * @param {number} [tileSize=32] - The tile size.
|
|
|
- */
|
|
|
- constructor( maxLights = 1024, tileSize = 32 ) {
|
|
|
-
|
|
|
- super();
|
|
|
-
|
|
|
- this.materialLights = [];
|
|
|
- this.tiledLights = [];
|
|
|
-
|
|
|
- /**
|
|
|
- * The maximum number of lights.
|
|
|
- *
|
|
|
- * @type {number}
|
|
|
- * @default 1024
|
|
|
- */
|
|
|
- this.maxLights = maxLights;
|
|
|
-
|
|
|
- /**
|
|
|
- * The tile size.
|
|
|
- *
|
|
|
- * @type {number}
|
|
|
- * @default 32
|
|
|
- */
|
|
|
- this.tileSize = tileSize;
|
|
|
-
|
|
|
- this._bufferSize = null;
|
|
|
- this._lightIndexes = null;
|
|
|
- this._screenTileIndex = null;
|
|
|
- this._compute = null;
|
|
|
- this._lightsTexture = null;
|
|
|
-
|
|
|
- this._lightsCount = uniform( 0, 'int' );
|
|
|
- this._tileLightCount = 8;
|
|
|
- this._screenSize = uniform( new Vector2() );
|
|
|
- this._cameraProjectionMatrix = uniform( 'mat4' );
|
|
|
- this._cameraViewMatrix = uniform( 'mat4' );
|
|
|
-
|
|
|
- this.updateBeforeType = NodeUpdateType.RENDER;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- customCacheKey() {
|
|
|
-
|
|
|
- return this._compute.getCacheKey() + super.customCacheKey();
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- updateLightsTexture() {
|
|
|
-
|
|
|
- const { _lightsTexture: lightsTexture, tiledLights } = this;
|
|
|
-
|
|
|
- const data = lightsTexture.image.data;
|
|
|
- const lineSize = lightsTexture.image.width * 4;
|
|
|
-
|
|
|
- this._lightsCount.value = tiledLights.length;
|
|
|
-
|
|
|
- for ( let i = 0; i < tiledLights.length; i ++ ) {
|
|
|
-
|
|
|
- const light = tiledLights[ i ];
|
|
|
-
|
|
|
- // world position
|
|
|
-
|
|
|
- _vector3.setFromMatrixPosition( light.matrixWorld );
|
|
|
-
|
|
|
- // store data
|
|
|
-
|
|
|
- const offset = i * 4;
|
|
|
-
|
|
|
- data[ offset + 0 ] = _vector3.x;
|
|
|
- data[ offset + 1 ] = _vector3.y;
|
|
|
- data[ offset + 2 ] = _vector3.z;
|
|
|
- data[ offset + 3 ] = light.distance;
|
|
|
-
|
|
|
- data[ lineSize + offset + 0 ] = light.color.r * light.intensity;
|
|
|
- data[ lineSize + offset + 1 ] = light.color.g * light.intensity;
|
|
|
- data[ lineSize + offset + 2 ] = light.color.b * light.intensity;
|
|
|
- data[ lineSize + offset + 3 ] = light.decay;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- lightsTexture.needsUpdate = true;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- updateBefore( frame ) {
|
|
|
-
|
|
|
- const { renderer, camera } = frame;
|
|
|
-
|
|
|
- this.updateProgram( renderer );
|
|
|
-
|
|
|
- this.updateLightsTexture( camera );
|
|
|
-
|
|
|
- this._cameraProjectionMatrix.value = camera.projectionMatrix;
|
|
|
- this._cameraViewMatrix.value = camera.matrixWorldInverse;
|
|
|
-
|
|
|
- renderer.getDrawingBufferSize( _size );
|
|
|
- this._screenSize.value.copy( _size );
|
|
|
-
|
|
|
- renderer.compute( this._compute );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- setLights( lights ) {
|
|
|
-
|
|
|
- const { tiledLights, materialLights } = this;
|
|
|
-
|
|
|
- let materialindex = 0;
|
|
|
- let tiledIndex = 0;
|
|
|
-
|
|
|
- for ( const light of lights ) {
|
|
|
-
|
|
|
- if ( light.isPointLight === true ) {
|
|
|
-
|
|
|
- tiledLights[ tiledIndex ++ ] = light;
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- materialLights[ materialindex ++ ] = light;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- materialLights.length = materialindex;
|
|
|
- tiledLights.length = tiledIndex;
|
|
|
-
|
|
|
- return super.setLights( materialLights );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- getBlock( block = 0 ) {
|
|
|
-
|
|
|
- return this._lightIndexes.element( this._screenTileIndex.mul( int( 2 ).add( int( block ) ) ) );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- getTile( element ) {
|
|
|
-
|
|
|
- element = int( element );
|
|
|
-
|
|
|
- const stride = int( 4 );
|
|
|
- const tileOffset = element.div( stride );
|
|
|
- const tileIndex = this._screenTileIndex.mul( int( 2 ) ).add( tileOffset );
|
|
|
-
|
|
|
- return this._lightIndexes.element( tileIndex ).element( element.mod( stride ) );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- getLightData( index ) {
|
|
|
-
|
|
|
- index = int( index );
|
|
|
-
|
|
|
- const dataA = textureLoad( this._lightsTexture, ivec2( index, 0 ) );
|
|
|
- const dataB = textureLoad( this._lightsTexture, ivec2( index, 1 ) );
|
|
|
-
|
|
|
- const position = dataA.xyz;
|
|
|
- const viewPosition = this._cameraViewMatrix.mul( position );
|
|
|
- const distance = dataA.w;
|
|
|
- const color = dataB.rgb;
|
|
|
- const decay = dataB.w;
|
|
|
-
|
|
|
- return {
|
|
|
- position,
|
|
|
- viewPosition,
|
|
|
- distance,
|
|
|
- color,
|
|
|
- decay
|
|
|
- };
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- setupLights( builder, lightNodes ) {
|
|
|
-
|
|
|
- this.updateProgram( builder.renderer );
|
|
|
-
|
|
|
- //
|
|
|
-
|
|
|
- const lightingModel = builder.context.reflectedLight;
|
|
|
-
|
|
|
- // force declaration order, before of the loop
|
|
|
- lightingModel.directDiffuse.toStack();
|
|
|
- lightingModel.directSpecular.toStack();
|
|
|
-
|
|
|
- super.setupLights( builder, lightNodes );
|
|
|
-
|
|
|
- Fn( () => {
|
|
|
-
|
|
|
- Loop( this._tileLightCount, ( { i } ) => {
|
|
|
-
|
|
|
- const lightIndex = this.getTile( i );
|
|
|
-
|
|
|
- If( lightIndex.equal( int( 0 ) ), () => {
|
|
|
-
|
|
|
- Break();
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- const { color, decay, viewPosition, distance } = this.getLightData( lightIndex.sub( 1 ) );
|
|
|
-
|
|
|
- builder.lightsNode.setupDirectLight( builder, this, directPointLight( {
|
|
|
- color,
|
|
|
- lightVector: viewPosition.sub( positionView ),
|
|
|
- cutoffDistance: distance,
|
|
|
- decayExponent: decay
|
|
|
- } ) );
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- }, 'void' )();
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- getBufferFitSize( value ) {
|
|
|
-
|
|
|
- const multiple = this.tileSize;
|
|
|
-
|
|
|
- return Math.ceil( value / multiple ) * multiple;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- setSize( width, height ) {
|
|
|
-
|
|
|
- width = this.getBufferFitSize( width );
|
|
|
- height = this.getBufferFitSize( height );
|
|
|
-
|
|
|
- if ( ! this._bufferSize || this._bufferSize.width !== width || this._bufferSize.height !== height ) {
|
|
|
-
|
|
|
- this.create( width, height );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- updateProgram( renderer ) {
|
|
|
-
|
|
|
- renderer.getDrawingBufferSize( _size );
|
|
|
-
|
|
|
- const width = this.getBufferFitSize( _size.width );
|
|
|
- const height = this.getBufferFitSize( _size.height );
|
|
|
-
|
|
|
- if ( this._bufferSize === null ) {
|
|
|
-
|
|
|
- this.create( width, height );
|
|
|
-
|
|
|
- } else if ( this._bufferSize.width !== width || this._bufferSize.height !== height ) {
|
|
|
-
|
|
|
- this.create( width, height );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- create( width, height ) {
|
|
|
-
|
|
|
- const { tileSize, maxLights } = this;
|
|
|
-
|
|
|
- const bufferSize = new Vector2( width, height );
|
|
|
- const lineSize = Math.floor( bufferSize.width / tileSize );
|
|
|
- const count = Math.floor( ( bufferSize.width * bufferSize.height ) / tileSize );
|
|
|
-
|
|
|
- // buffers
|
|
|
-
|
|
|
- const lightsData = new Float32Array( maxLights * 4 * 2 ); // 2048 lights, 4 elements(rgba), 2 components, 1 component per line (position, distance, color, decay)
|
|
|
- const lightsTexture = new DataTexture( lightsData, lightsData.length / 8, 2, RGBAFormat, FloatType );
|
|
|
-
|
|
|
- const lightIndexesArray = new Int32Array( count * 4 * 2 );
|
|
|
- const lightIndexes = attributeArray( lightIndexesArray, 'ivec4' ).setName( 'lightIndexes' );
|
|
|
-
|
|
|
- // compute
|
|
|
-
|
|
|
- const getBlock = ( index ) => {
|
|
|
-
|
|
|
- const tileIndex = instanceIndex.mul( int( 2 ) ).add( int( index ) );
|
|
|
-
|
|
|
- return lightIndexes.element( tileIndex );
|
|
|
-
|
|
|
- };
|
|
|
-
|
|
|
- const getTile = ( elementIndex ) => {
|
|
|
-
|
|
|
- elementIndex = int( elementIndex );
|
|
|
-
|
|
|
- const stride = int( 4 );
|
|
|
- const tileOffset = elementIndex.div( stride );
|
|
|
- const tileIndex = instanceIndex.mul( int( 2 ) ).add( tileOffset );
|
|
|
-
|
|
|
- return lightIndexes.element( tileIndex ).element( elementIndex.mod( stride ) );
|
|
|
-
|
|
|
- };
|
|
|
-
|
|
|
- const compute = Fn( () => {
|
|
|
-
|
|
|
- const { _cameraProjectionMatrix: cameraProjectionMatrix, _bufferSize: bufferSize, _screenSize: screenSize } = this;
|
|
|
-
|
|
|
- const tiledBufferSize = bufferSize.clone().divideScalar( tileSize ).floor();
|
|
|
-
|
|
|
- const tileScreen = vec2(
|
|
|
- instanceIndex.mod( tiledBufferSize.width ),
|
|
|
- instanceIndex.div( tiledBufferSize.width )
|
|
|
- ).mul( tileSize ).div( screenSize );
|
|
|
-
|
|
|
- const blockSize = float( tileSize ).div( screenSize );
|
|
|
- const minBounds = tileScreen;
|
|
|
- const maxBounds = minBounds.add( blockSize );
|
|
|
-
|
|
|
- const index = int( 0 ).toVar();
|
|
|
-
|
|
|
- getBlock( 0 ).assign( ivec4( 0 ) );
|
|
|
- getBlock( 1 ).assign( ivec4( 0 ) );
|
|
|
-
|
|
|
- Loop( this.maxLights, ( { i } ) => {
|
|
|
-
|
|
|
- If( index.greaterThanEqual( this._tileLightCount ).or( int( i ).greaterThanEqual( int( this._lightsCount ) ) ), () => {
|
|
|
-
|
|
|
- Return();
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- const { viewPosition, distance } = this.getLightData( i );
|
|
|
-
|
|
|
- const projectedPosition = cameraProjectionMatrix.mul( viewPosition );
|
|
|
- const ndc = projectedPosition.div( projectedPosition.w );
|
|
|
- const screenPosition = ndc.xy.mul( 0.5 ).add( 0.5 ).flipY();
|
|
|
-
|
|
|
- const distanceFromCamera = viewPosition.z;
|
|
|
- const pointRadius = distance.div( distanceFromCamera );
|
|
|
-
|
|
|
- If( circleIntersectsAABB( screenPosition, pointRadius, minBounds, maxBounds ), () => {
|
|
|
-
|
|
|
- getTile( index ).assign( i.add( int( 1 ) ) );
|
|
|
- index.addAssign( int( 1 ) );
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- } )().compute( count ).setName( 'Update Tiled Lights' );
|
|
|
-
|
|
|
- // screen coordinate lighting indexes
|
|
|
-
|
|
|
- const screenTile = screenCoordinate.div( tileSize ).floor().toVar();
|
|
|
- const screenTileIndex = screenTile.x.add( screenTile.y.mul( lineSize ) );
|
|
|
-
|
|
|
- // assigns
|
|
|
-
|
|
|
- this._bufferSize = bufferSize;
|
|
|
- this._lightIndexes = lightIndexes;
|
|
|
- this._screenTileIndex = screenTileIndex;
|
|
|
- this._compute = compute;
|
|
|
- this._lightsTexture = lightsTexture;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- get hasLights() {
|
|
|
-
|
|
|
- return super.hasLights || this.tiledLights.length > 0;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-export default TiledLightsNode;
|
|
|
-
|
|
|
-/**
|
|
|
- * TSL function that creates a tiled lights node.
|
|
|
- *
|
|
|
- * @tsl
|
|
|
- * @function
|
|
|
- * @param {number} [maxLights=1024] - The maximum number of lights.
|
|
|
- * @param {number} [tileSize=32] - The tile size.
|
|
|
- * @return {TiledLightsNode} The tiled lights node.
|
|
|
- */
|
|
|
-export const tiledLights = /*@__PURE__*/ nodeProxy( TiledLightsNode );
|