diff --git a/examples/jsm/loaders/EXRLoader.js b/examples/jsm/loaders/EXRLoader.js index 501b7907fee1d3..725be1bc040f75 100644 --- a/examples/jsm/loaders/EXRLoader.js +++ b/examples/jsm/loaders/EXRLoader.js @@ -3343,6 +3343,10 @@ class EXRLoader extends DataTextureLoader { format: EXRDecoder.format, colorSpace: EXRDecoder.colorSpace, type: this.type, + minFilter: LinearFilter, + magFilter: LinearFilter, + generateMipmaps: false, + flipY: false, }; } @@ -3386,24 +3390,6 @@ class EXRLoader extends DataTextureLoader { } - load( url, onLoad, onProgress, onError ) { - - function onLoadCallback( texture, texData ) { - - texture.colorSpace = texData.colorSpace; - texture.minFilter = LinearFilter; - texture.magFilter = LinearFilter; - texture.generateMipmaps = false; - texture.flipY = false; - - if ( onLoad ) onLoad( texture, texData ); - - } - - return super.load( url, onLoadCallback, onProgress, onError ); - - } - } export { EXRLoader }; diff --git a/examples/jsm/loaders/HDRLoader.js b/examples/jsm/loaders/HDRLoader.js index 0da755bc5db1ad..087b4c5caa835a 100644 --- a/examples/jsm/loaders/HDRLoader.js +++ b/examples/jsm/loaders/HDRLoader.js @@ -433,7 +433,12 @@ class HDRLoader extends DataTextureLoader { header: rgbe_header_info.string, gamma: rgbe_header_info.gamma, exposure: rgbe_header_info.exposure, - type: type + type: type, + colorSpace: LinearSRGBColorSpace, + minFilter: LinearFilter, + magFilter: LinearFilter, + generateMipmaps: false, + flipY: true }; } @@ -451,33 +456,6 @@ class HDRLoader extends DataTextureLoader { } - load( url, onLoad, onProgress, onError ) { - - function onLoadCallback( texture, texData ) { - - switch ( texture.type ) { - - case FloatType: - case HalfFloatType: - - texture.colorSpace = LinearSRGBColorSpace; - texture.minFilter = LinearFilter; - texture.magFilter = LinearFilter; - texture.generateMipmaps = false; - texture.flipY = true; - - break; - - } - - if ( onLoad ) onLoad( texture, texData ); - - } - - return super.load( url, onLoadCallback, onProgress, onError ); - - } - } export { HDRLoader }; diff --git a/examples/jsm/tsl/lighting/ClusteredLightsNode.js b/examples/jsm/tsl/lighting/ClusteredLightsNode.js index ccf606cca5e592..8ce1b03a30fe6f 100644 --- a/examples/jsm/tsl/lighting/ClusteredLightsNode.js +++ b/examples/jsm/tsl/lighting/ClusteredLightsNode.js @@ -79,7 +79,7 @@ class ClusteredLightsNode extends LightsNode { customCacheKey() { - return this._compute.getCacheKey() + super.customCacheKey(); + return ( this._compute ? this._compute.getCacheKey() : 0 ) + super.customCacheKey(); } diff --git a/examples/jsm/tsl/lighting/DynamicLightsNode.js b/examples/jsm/tsl/lighting/DynamicLightsNode.js index ec8649fdcbf6c1..fa6add3e689a80 100644 --- a/examples/jsm/tsl/lighting/DynamicLightsNode.js +++ b/examples/jsm/tsl/lighting/DynamicLightsNode.js @@ -230,7 +230,7 @@ class DynamicLightsNode extends LightsNode { } - this._lightNodes = lightNodes; + return lightNodes; } diff --git a/examples/webgl_materials_matcap.html b/examples/webgl_materials_matcap.html index 8840e3e0cef7c2..28bb3dd471db1e 100644 --- a/examples/webgl_materials_matcap.html +++ b/examples/webgl_materials_matcap.html @@ -190,27 +190,8 @@ function handleEXR( event ) { - const contents = event.target.result; - const loader = new EXRLoader(); - - loader.setDataType( THREE.HalfFloatType ); - - const texData = loader.parse( contents ); - - const texture = new THREE.DataTexture(); - - texture.image.width = texData.width; - texture.image.height = texData.height; - texture.image.data = texData.data; - - texture.format = texData.format; - texture.type = texData.type; - texture.colorSpace = THREE.LinearSRGBColorSpace; - texture.minFilter = THREE.LinearFilter; - texture.magFilter = THREE.LinearFilter; - texture.generateMipmaps = false; - texture.flipY = false; + const texture = loader.createDataTexture( event.target.result ); updateMatcap( texture ); diff --git a/examples/webgpu_materials_matcap.html b/examples/webgpu_materials_matcap.html index 220dfdb8ae719c..585f9699119bee 100644 --- a/examples/webgpu_materials_matcap.html +++ b/examples/webgpu_materials_matcap.html @@ -191,27 +191,8 @@ function handleEXR( event ) { - const contents = event.target.result; - const loader = new EXRLoader(); - - loader.setDataType( THREE.HalfFloatType ); - - const texData = loader.parse( contents ); - - const texture = new THREE.DataTexture(); - - texture.image.width = texData.width; - texture.image.height = texData.height; - texture.image.data = texData.data; - - texture.format = texData.format; - texture.type = texData.type; - texture.colorSpace = THREE.LinearSRGBColorSpace; - texture.minFilter = THREE.LinearFilter; - texture.magFilter = THREE.LinearFilter; - texture.generateMipmaps = false; - texture.flipY = false; + const texture = loader.createDataTexture( event.target.result ); updateMatcap( texture ); diff --git a/src/loaders/DataTextureLoader.js b/src/loaders/DataTextureLoader.js index af3d38fc71764e..db488684856cf8 100644 --- a/src/loaders/DataTextureLoader.js +++ b/src/loaders/DataTextureLoader.js @@ -74,77 +74,108 @@ class DataTextureLoader extends Loader { } - if ( texData.image !== undefined ) { + scope._applyTexData( texture, texData ); - texture.image = texData.image; + if ( onLoad ) onLoad( texture, texData ); - } else if ( texData.data !== undefined ) { + }, onProgress, onError ); - texture.image.width = texData.width; - texture.image.height = texData.height; - texture.image.data = texData.data; - } + return texture; - texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : ClampToEdgeWrapping; - texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : ClampToEdgeWrapping; + } - texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : LinearFilter; - texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : LinearFilter; + /** + * Parses the given buffer and returns a configured data texture. Use this method + * for parsing texture data that is already in memory (e.g. drag and drop or data + * loaded from a server) without going through {@link DataTextureLoader#load}. + * + * @param {ArrayBuffer} buffer - The raw texture data. + * @return {DataTexture} The data texture. + */ + createDataTexture( buffer ) { - texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1; + const texture = new DataTexture(); - if ( texData.colorSpace !== undefined ) { + this._applyTexData( texture, this.parse( buffer ) ); - texture.colorSpace = texData.colorSpace; + return texture; - } + } - if ( texData.flipY !== undefined ) { + /** + * Applies the given parsed texture data to the given data texture. + * + * @private + * @param {DataTexture} texture - The data texture. + * @param {DataTextureLoader~TexData} texData - The parsed texture data. + */ + _applyTexData( texture, texData ) { - texture.flipY = texData.flipY; + if ( texData.image !== undefined ) { - } + texture.image = texData.image; - if ( texData.format !== undefined ) { + } else if ( texData.data !== undefined ) { - texture.format = texData.format; + texture.image.width = texData.width; + texture.image.height = texData.height; + texture.image.data = texData.data; - } + } - if ( texData.type !== undefined ) { + texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : ClampToEdgeWrapping; + texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : ClampToEdgeWrapping; - texture.type = texData.type; + texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : LinearFilter; + texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : LinearFilter; - } + texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1; - if ( texData.mipmaps !== undefined ) { + if ( texData.colorSpace !== undefined ) { - texture.mipmaps = texData.mipmaps; - texture.minFilter = LinearMipmapLinearFilter; // presumably... + texture.colorSpace = texData.colorSpace; - } + } - if ( texData.mipmapCount === 1 ) { + if ( texData.flipY !== undefined ) { - texture.minFilter = LinearFilter; + texture.flipY = texData.flipY; - } + } - if ( texData.generateMipmaps !== undefined ) { + if ( texData.format !== undefined ) { - texture.generateMipmaps = texData.generateMipmaps; + texture.format = texData.format; - } + } - texture.needsUpdate = true; + if ( texData.type !== undefined ) { - if ( onLoad ) onLoad( texture, texData ); + texture.type = texData.type; - }, onProgress, onError ); + } + if ( texData.mipmaps !== undefined ) { - return texture; + texture.mipmaps = texData.mipmaps; + texture.minFilter = LinearMipmapLinearFilter; // presumably... + + } + + if ( texData.mipmapCount === 1 ) { + + texture.minFilter = LinearFilter; + + } + + if ( texData.generateMipmaps !== undefined ) { + + texture.generateMipmaps = texData.generateMipmaps; + + } + + texture.needsUpdate = true; } diff --git a/src/materials/nodes/Line2NodeMaterial.js b/src/materials/nodes/Line2NodeMaterial.js index 480c16a2cf0123..0d567e440d12d6 100644 --- a/src/materials/nodes/Line2NodeMaterial.js +++ b/src/materials/nodes/Line2NodeMaterial.js @@ -538,8 +538,6 @@ class Line2NodeMaterial extends NodeMaterial { */ get lineColorNode() { - warnOnce( 'Line2NodeMaterial: "lineColorNode" has been deprecated. Use "colorNode" instead.' ); // @deprecated r185 - return this.colorNode; } diff --git a/src/materials/nodes/NodeMaterial.js b/src/materials/nodes/NodeMaterial.js index 5963e91506ab43..db5bd80218e741 100644 --- a/src/materials/nodes/NodeMaterial.js +++ b/src/materials/nodes/NodeMaterial.js @@ -985,9 +985,9 @@ class NodeMaterial extends Material { * Setups the lights node based on the scene, environment and material. * * @param {NodeBuilder} builder - The current node builder. - * @return {LightsNode} The lights node. + * @return {LightingNode} The lights node. */ - setupLights( builder ) { + setupMaterialLightings( builder ) { const materialLightsNode = []; @@ -1029,15 +1029,7 @@ class NodeMaterial extends Material { } - let lightsN = this.lightsNode || builder.lightsNode; - - if ( materialLightsNode.length > 0 ) { - - lightsN = builder.renderer.lighting.createNode( [ ...lightsN.getLights(), ...materialLightsNode ] ); - - } - - return lightsN; + return materialLightsNode; } @@ -1070,15 +1062,16 @@ class NodeMaterial extends Material { const lights = this.lights === true || this.lightsNode !== null; - const lightsNode = lights ? this.setupLights( builder ) : null; + const materialLightings = this.lights === true ? this.setupMaterialLightings( builder ) : []; + const lightsNode = lights ? ( this.lightsNode || builder.lightsNode ) : null; let outgoingLightNode = this.setupOutgoingLight( builder ); - if ( lightsNode && lightsNode.getScope().hasLights ) { + if ( lightsNode && ( materialLightings.length > 0 || lightsNode.getScope().hasLights ) ) { const lightingModel = this.setupLightingModel( builder ) || null; - outgoingLightNode = lightingContext( lightsNode, lightingModel, backdropNode, backdropAlphaNode ); + outgoingLightNode = lightingContext( lightsNode, lightingModel, materialLightings, backdropNode, backdropAlphaNode ); } else if ( backdropNode !== null ) { diff --git a/src/nodes/core/VaryingNode.js b/src/nodes/core/VaryingNode.js index 4349d2ab100fcf..6dcbfc0736297f 100644 --- a/src/nodes/core/VaryingNode.js +++ b/src/nodes/core/VaryingNode.js @@ -170,8 +170,18 @@ class VaryingNode extends Node { const type = this.getNodeType( builder ); const propertyName = builder.getPropertyName( varying, NodeShaderStage.VERTEX ); - // force node run in vertex stage - builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, properties.node, type, propertyName ); + if ( builder.shaderStage === NodeShaderStage.VERTEX ) { + + const snippet = properties.node.build( builder, type ); + + builder.addLineFlowCode( `${ propertyName } = ${ snippet }`, this ); + + } else { + + // force node run in vertex stage + builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, properties.node, type, propertyName ); + + } properties[ propertyKey ] = propertyName; diff --git a/src/nodes/lighting/LightingContextNode.js b/src/nodes/lighting/LightingContextNode.js index f4b586c0139ccf..713351527c6b4c 100644 --- a/src/nodes/lighting/LightingContextNode.js +++ b/src/nodes/lighting/LightingContextNode.js @@ -21,10 +21,11 @@ class LightingContextNode extends ContextNode { * * @param {LightsNode} lightsNode - The lights node. * @param {?LightingModel} [lightingModel=null] - The current lighting model. + * @param {?Array} materialLightings - The material lightings nodes. * @param {?Node} [backdropNode=null] - A backdrop node. * @param {?Node} [backdropAlphaNode=null] - A backdrop alpha node. */ - constructor( lightsNode, lightingModel = null, backdropNode = null, backdropAlphaNode = null ) { + constructor( lightsNode, lightingModel = null, materialLightings = [], backdropNode = null, backdropAlphaNode = null ) { super( lightsNode ); @@ -36,6 +37,12 @@ class LightingContextNode extends ContextNode { */ this.lightingModel = lightingModel; + /** + * @type {?Array} + * @default [] + */ + this.materialLightings = materialLightings; + /** * A backdrop node. * @@ -71,7 +78,7 @@ class LightingContextNode extends ContextNode { */ getContext() { - const { backdropNode, backdropAlphaNode } = this; + const { materialLightings, backdropNode, backdropAlphaNode } = this; const directDiffuse = vec3().toVar( 'directDiffuse' ), directSpecular = vec3().toVar( 'directSpecular' ), @@ -91,6 +98,7 @@ class LightingContextNode extends ContextNode { iblIrradiance: vec3().toVar( 'iblIrradiance' ), ambientOcclusion: float( 1 ).toVar( 'ambientOcclusion' ), reflectedLight, + materialLightings, backdrop: backdropNode, backdropAlpha: backdropAlphaNode }; diff --git a/src/nodes/lighting/LightsNode.js b/src/nodes/lighting/LightsNode.js index 3d5dfc68a28fda..f20784d45c38df 100644 --- a/src/nodes/lighting/LightsNode.js +++ b/src/nodes/lighting/LightsNode.js @@ -1,14 +1,50 @@ import Node from '../core/Node.js'; -import { nodeObject, property, vec3 } from '../tsl/TSLBase.js'; +import { property, vec3 } from '../tsl/TSLBase.js'; import { hashArray } from '../core/NodeUtils.js'; import { warn } from '../../utils.js'; +/** + * A node representing the total diffuse light. + * + * @type {Node} + */ +const totalDiffuse = property( 'vec3', 'totalDiffuse' ); + +/** + * A node representing the total specular light. + * + * @type {Node} + */ +const totalSpecular = property( 'vec3', 'totalSpecular' ); + +/** + * A node representing the outgoing light. + * + * @type {Node} + */ +const outgoingLight = property( 'vec3', 'outgoingLight' ); + +/** + * Sorts an array of lights in ascending order by their IDs. + * + * @private + * @param {Array} lights - The array of lights to sort. + * @return {Array} The sorted array of lights. + */ const sortLights = ( lights ) => { return lights.sort( ( a, b ) => a.id - b.id ); }; +/** + * Finds and returns a lighting node associated with a specific light ID. + * + * @private + * @param {number} id - The ID of the light to search for. + * @param {Array} lightNodes - The array of lighting nodes to search within. + * @return {?LightingNode} The matching lighting node, or null if not found. + */ const getLightNodeById = ( id, lightNodes ) => { for ( const lightNode of lightNodes ) { @@ -25,7 +61,20 @@ const getLightNodeById = ( id, lightNodes ) => { }; +/** + * WeakMap cache mapping light objects to their corresponding lighting node instances. + * + * @private + * @type {WeakMap} + */ const _lightsNodeRef = /*@__PURE__*/ new WeakMap(); + +/** + * Array used to temporarily store light IDs and shadow casting states for hashing. + * + * @private + * @type {Array} + */ const _hashData = []; /** @@ -55,21 +104,21 @@ class LightsNode extends Node { * * @type {Node} */ - this.totalDiffuseNode = property( 'vec3', 'totalDiffuse' ); + this.totalDiffuseNode = totalDiffuse; /** * A node representing the total specular light. * * @type {Node} */ - this.totalSpecularNode = property( 'vec3', 'totalSpecular' ); + this.totalSpecularNode = totalSpecular; /** * A node representing the outgoing light. * * @type {Node} */ - this.outgoingLightNode = property( 'vec3', 'outgoingLight' ); + this.outgoingLightNode = outgoingLight; /** * An array representing the lights in the scene. @@ -79,25 +128,6 @@ class LightsNode extends Node { */ this._lights = []; - /** - * For each light in the scene, this node will create a - * corresponding light node. - * - * @private - * @type {?Array} - * @default null - */ - this._lightNodes = null; - - /** - * A hash for identifying the current light nodes setup. - * - * @private - * @type {?string} - * @default null - */ - this._lightNodesHash = null; - /** * `LightsNode` sets this property to `true` by default. * @@ -152,26 +182,36 @@ class LightsNode extends Node { */ getHash( builder ) { - if ( this._lightNodesHash === null ) { + const nodeData = builder.getDataFromNode( this ); + + if ( nodeData.lightNodesHash === undefined ) { - if ( this._lightNodes === null ) this.setupLightsNode( builder ); + const lightNodes = this.setupLightsNode( builder ); + + nodeData.lightNodes = lightNodes; const hash = []; - for ( const lightNode of this._lightNodes ) { + for ( const lightNode of lightNodes ) { hash.push( lightNode.getHash() ); } - this._lightNodesHash = 'lights-' + hash.join( ',' ); + nodeData.lightNodesHash = 'lights-' + hash.join( ',' ); } - return this._lightNodesHash; + return nodeData.lightNodesHash; } + /** + * Analyzes the node's dependencies by building all nested light nodes + * and the output node. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + */ analyze( builder ) { const properties = builder.getNodeProperties( this ); @@ -191,21 +231,24 @@ class LightsNode extends Node { * process lights in the node system. * * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {Array} The array of lighting nodes. */ setupLightsNode( builder ) { + const nodeData = builder.getDataFromNode( this ); const lightNodes = []; - const previousLightNodes = this._lightNodes; + const previousLightNodes = nodeData.lightNodes || null; + const materialLightings = builder.context.materialLightings; - const lights = sortLights( this._lights ); + const lights = sortLights( [ ...materialLightings, ...this._lights ] ); const nodeLibrary = builder.renderer.library; for ( const light of lights ) { if ( light.isNode ) { - lightNodes.push( nodeObject( light ) ); + lightNodes.push( light ); } else { @@ -244,7 +287,7 @@ class LightsNode extends Node { } - this._lightNodes = lightNodes; + return lightNodes; } @@ -267,6 +310,13 @@ class LightsNode extends Node { } + /** + * Sets up a direct rect area light in the lighting model. + * + * @param {Object} builder - The builder object containing the context and stack. + * @param {Object} lightNode - The light node. + * @param {Object} lightData - The light object containing color and area light properties. + */ setupDirectRectAreaLight( builder, lightNode, lightData ) { const { lightingModel, reflectedLight } = builder.context; @@ -298,9 +348,15 @@ class LightsNode extends Node { getLightNodes( builder ) { - if ( this._lightNodes === null ) this.setupLightsNode( builder ); + const nodeData = builder.getDataFromNode( this ); + + if ( nodeData.lightNodes === undefined ) { + + nodeData.lightNodes = this.setupLightsNode( builder ); - return this._lightNodes; + } + + return nodeData.lightNodes; } @@ -387,9 +443,6 @@ class LightsNode extends Node { this._lights = lights; - this._lightNodes = null; - this._lightNodesHash = null; - return this; } diff --git a/src/renderers/common/Lighting.js b/src/renderers/common/Lighting.js index b9685bd02ee504..6387832885059f 100644 --- a/src/renderers/common/Lighting.js +++ b/src/renderers/common/Lighting.js @@ -35,9 +35,8 @@ class Lighting { */ getNode( scene ) { - // ignore post-processing - - if ( scene.isQuadMesh ) return _defaultLights; + // Ignore renderable objects, e.g: Mesh, Sprite, etc. + if ( scene.isScene !== true && scene.isGroup !== true ) return _defaultLights; let node = _weakMap.get( scene ); diff --git a/src/renderers/common/RenderList.js b/src/renderers/common/RenderList.js index d49fe4c9ebbaea..8dc1dd7f7f847e 100644 --- a/src/renderers/common/RenderList.js +++ b/src/renderers/common/RenderList.js @@ -151,7 +151,7 @@ class RenderList { * * @type {LightsNode} */ - this.lightsNode = lighting.getNode( scene, camera ); + this.lightsNode = lighting.getNode( scene ); /** * The scene's lights stored in an array. This array