From 8a03dbdfb898823686fd909f3b20ef661fa4d990 Mon Sep 17 00:00:00 2001 From: sunag Date: Tue, 9 Jun 2026 02:48:56 -0300 Subject: [PATCH 1/2] Addons: Removed `TiledLighting` (#33751) --- examples/files.json | 1 - examples/jsm/lighting/TiledLighting.js | 42 -- examples/jsm/tsl/lighting/TiledLightsNode.js | 442 ------------------- examples/screenshots/webgpu_lights_tiled.jpg | Bin 33669 -> 0 bytes examples/webgpu_lights_tiled.html | 240 ---------- 5 files changed, 725 deletions(-) delete mode 100644 examples/jsm/lighting/TiledLighting.js delete mode 100644 examples/jsm/tsl/lighting/TiledLightsNode.js delete mode 100644 examples/screenshots/webgpu_lights_tiled.jpg delete mode 100644 examples/webgpu_lights_tiled.html diff --git a/examples/files.json b/examples/files.json index b85dee58f3367b..9a473689d22e54 100644 --- a/examples/files.json +++ b/examples/files.json @@ -366,7 +366,6 @@ "webgpu_lights_rectarealight", "webgpu_lights_selective", "webgpu_lights_spotlight", - "webgpu_lights_tiled", "webgpu_lines_fat_raycasting", "webgpu_lines_fat_wireframe", "webgpu_lines_fat", diff --git a/examples/jsm/lighting/TiledLighting.js b/examples/jsm/lighting/TiledLighting.js deleted file mode 100644 index 2c4487524924c3..00000000000000 --- a/examples/jsm/lighting/TiledLighting.js +++ /dev/null @@ -1,42 +0,0 @@ -import { Lighting } from 'three/webgpu'; -import { tiledLights } from '../tsl/lighting/TiledLightsNode.js'; - -/** - * A custom lighting implementation based on Tiled-Lighting that overwrites the default - * implementation in {@link WebGPURenderer}. - * - * ```js - * const lighting = new TiledLighting(); - * renderer.lighting = lighting; // set lighting system - * ``` - * - * @augments Lighting - * @three_import import { TiledLighting } from 'three/addons/lighting/TiledLighting.js'; - */ -export class TiledLighting extends Lighting { - - /** - * Constructs a new lighting system. - */ - constructor() { - - super(); - - } - - /** - * Creates a new tiled lights node for the given array of lights. - * - * This method is called internally by the renderer and must be overwritten by - * all custom lighting implementations. - * - * @param {Array} lights - The render object. - * @return {TiledLightsNode} The tiled lights node. - */ - createNode( lights = [] ) { - - return tiledLights().setLights( lights ); - - } - -} diff --git a/examples/jsm/tsl/lighting/TiledLightsNode.js b/examples/jsm/tsl/lighting/TiledLightsNode.js deleted file mode 100644 index 494657728aa55e..00000000000000 --- a/examples/jsm/tsl/lighting/TiledLightsNode.js +++ /dev/null @@ -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} circleCenter - The center of the circle. - * @param {Node} radius - The radius of the circle. - * @param {Node} minBounds - The minimum bounds of the AABB. - * @param {Node} maxBounds - The maximum bounds of the AABB. - * @return {Node} 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 ); diff --git a/examples/screenshots/webgpu_lights_tiled.jpg b/examples/screenshots/webgpu_lights_tiled.jpg deleted file mode 100644 index c86c0f3b4f980f7655b149147e8902cda092a595..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33669 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�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 - - - three.js webgpu - tiled lighting - - - - - - - - - - -
- - -
- three.jsTiled Lighting -
- - - Custom compute-based Tiled Lighting. - -
- - - - - - From dc84b87c9f35ee2f0a79ea91ff3f3a8e7f9c7aff Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 9 Jun 2026 09:55:26 +0200 Subject: [PATCH 2/2] Object3D: Honor `matrixWorldNeedsUpdate` in `updateWorldMatrix()`. (#33746) --- src/cameras/Camera.js | 4 ++-- src/core/Object3D.js | 24 +++++++++++++++++------- src/helpers/DirectionalLightHelper.js | 2 ++ src/helpers/HemisphereLightHelper.js | 2 ++ src/helpers/PointLightHelper.js | 2 ++ src/helpers/SpotLightHelper.js | 2 +- test/unit/src/core/Object3D.tests.js | 1 + 7 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/cameras/Camera.js b/src/cameras/Camera.js index 2c2569639c4476..6a8005ece152d7 100644 --- a/src/cameras/Camera.js +++ b/src/cameras/Camera.js @@ -129,9 +129,9 @@ class Camera extends Object3D { } - updateWorldMatrix( updateParents, updateChildren ) { + updateWorldMatrix( updateParents, updateChildren, force = false ) { - super.updateWorldMatrix( updateParents, updateChildren ); + super.updateWorldMatrix( updateParents, updateChildren, force ); // exclude scale from view matrix to be glTF conform diff --git a/src/core/Object3D.js b/src/core/Object3D.js index 6499505ac5e579..a71aed581e2966 100644 --- a/src/core/Object3D.js +++ b/src/core/Object3D.js @@ -1208,8 +1208,10 @@ class Object3D extends EventDispatcher { * * @param {boolean} [updateParents=false] Whether ancestor nodes should be updated or not. * @param {boolean} [updateChildren=false] Whether descendant nodes should be updated or not. + * @param {boolean} [force=false] - When set to `true`, a recomputation of world matrices is forced even + * when {@link Object3D#matrixWorldNeedsUpdate} is `false`. */ - updateWorldMatrix( updateParents, updateChildren ) { + updateWorldMatrix( updateParents, updateChildren, force = false ) { const parent = this.parent; @@ -1221,18 +1223,26 @@ class Object3D extends EventDispatcher { if ( this.matrixAutoUpdate ) this.updateMatrix(); - if ( this.matrixWorldAutoUpdate === true ) { + if ( this.matrixWorldNeedsUpdate || force ) { + + if ( this.matrixWorldAutoUpdate === true ) { - if ( this.parent === null ) { + if ( this.parent === null ) { - this.matrixWorld.copy( this.matrix ); + this.matrixWorld.copy( this.matrix ); - } else { + } else { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + } } + this.matrixWorldNeedsUpdate = false; + + force = true; + } // make sure descendants are updated @@ -1245,7 +1255,7 @@ class Object3D extends EventDispatcher { const child = children[ i ]; - child.updateWorldMatrix( false, true ); + child.updateWorldMatrix( false, true, force ); } diff --git a/src/helpers/DirectionalLightHelper.js b/src/helpers/DirectionalLightHelper.js index fdd8350d0f5d7f..b8f5e71ee80918 100644 --- a/src/helpers/DirectionalLightHelper.js +++ b/src/helpers/DirectionalLightHelper.js @@ -116,6 +116,8 @@ class DirectionalLightHelper extends Object3D { */ update() { + this.matrixWorldNeedsUpdate = true; + this.light.updateWorldMatrix( true, false ); this.light.target.updateWorldMatrix( true, false ); diff --git a/src/helpers/HemisphereLightHelper.js b/src/helpers/HemisphereLightHelper.js index 69bab870851512..b64cbd004637d0 100644 --- a/src/helpers/HemisphereLightHelper.js +++ b/src/helpers/HemisphereLightHelper.js @@ -118,6 +118,8 @@ class HemisphereLightHelper extends Object3D { } + this.matrixWorldNeedsUpdate = true; + this.light.updateWorldMatrix( true, false ); mesh.lookAt( _vector.setFromMatrixPosition( this.light.matrixWorld ).negate() ); diff --git a/src/helpers/PointLightHelper.js b/src/helpers/PointLightHelper.js index a4fa5236d13a63..43793ab1260823 100644 --- a/src/helpers/PointLightHelper.js +++ b/src/helpers/PointLightHelper.js @@ -76,6 +76,8 @@ class PointLightHelper extends Mesh { */ update() { + this.matrixWorldNeedsUpdate = true; + this.light.updateWorldMatrix( true, false ); if ( this.color !== undefined ) { diff --git a/src/helpers/SpotLightHelper.js b/src/helpers/SpotLightHelper.js index a4b15bd1f17bbd..fef093d40d602e 100644 --- a/src/helpers/SpotLightHelper.js +++ b/src/helpers/SpotLightHelper.js @@ -125,7 +125,7 @@ class SpotLightHelper extends Object3D { } - this.matrixWorld.copy( this.light.matrixWorld ); + this.matrixWorldNeedsUpdate = true; const coneLength = this.light.distance ? this.light.distance : 1000; const coneWidth = coneLength * Math.tan( this.light.angle ); diff --git a/test/unit/src/core/Object3D.tests.js b/test/unit/src/core/Object3D.tests.js index 2e941d7035c088..b49f952c805536 100644 --- a/test/unit/src/core/Object3D.tests.js +++ b/test/unit/src/core/Object3D.tests.js @@ -1033,6 +1033,7 @@ export default QUnit.module( 'Core', () => { object.matrixWorld.identity(); object.matrixAutoUpdate = false; + object.matrixWorldNeedsUpdate = true; object.updateWorldMatrix( true, false ); assert.deepEqual( object.matrix.elements,