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 c86c0f3b4f980f..00000000000000 Binary files a/examples/screenshots/webgpu_lights_tiled.jpg and /dev/null differ diff --git a/examples/webgpu_lights_tiled.html b/examples/webgpu_lights_tiled.html deleted file mode 100644 index f7818fa5303b99..00000000000000 --- a/examples/webgpu_lights_tiled.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - three.js webgpu - tiled lighting - - - - - - - - - - -
- - -
- three.jsTiled Lighting -
- - - Custom compute-based Tiled Lighting. - -
- - - - - - 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,