123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- 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';
- 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' ).label( '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 );
- // 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;
- export const tiledLights = /*@__PURE__*/ nodeProxy( TiledLightsNode );
|