diff --git a/src/Three.TSL.js b/src/Three.TSL.js index 80ab8d59baf425..abb05231f7a44b 100644 --- a/src/Three.TSL.js +++ b/src/Three.TSL.js @@ -47,6 +47,7 @@ export const addNodeElement = TSL.addNodeElement; export const agxToneMapping = TSL.agxToneMapping; export const all = TSL.all; export const alphaT = TSL.alphaT; +export const ambientOcclusion = TSL.ambientOcclusion; export const and = TSL.and; export const anisotropy = TSL.anisotropy; export const anisotropyB = TSL.anisotropyB; diff --git a/src/materials/nodes/NodeMaterial.js b/src/materials/nodes/NodeMaterial.js index b2278a3bd3df99..bd3ed45a6ba8e6 100644 --- a/src/materials/nodes/NodeMaterial.js +++ b/src/materials/nodes/NodeMaterial.js @@ -1,7 +1,7 @@ import { Material } from '../Material.js'; import { hashArray, hashString } from '../../nodes/core/NodeUtils.js'; -import { output, diffuseColor, emissive } from '../../nodes/core/PropertyNode.js'; +import { output, diffuseColor, ambientOcclusion, emissive } from '../../nodes/core/PropertyNode.js'; import { materialAlphaTest, materialColor, materialOpacity, materialEmissive, materialNormal, materialLightMap, materialAO } from '../../nodes/accessors/MaterialNode.js'; import { modelViewProjection } from '../../nodes/accessors/ModelViewProjectionNode.js'; import { normalLocal } from '../../nodes/accessors/Normal.js'; @@ -523,6 +523,7 @@ class NodeMaterial extends Material { if ( this.fragmentNode === null ) { this.setupDiffuseColor( builder ); + this.setupAmbientOcclusion( builder ); this.setupVariants( builder ); const outgoingLightNode = this.setupLighting( builder ); @@ -1015,6 +1016,24 @@ class NodeMaterial extends Material { } + if ( builder.context.ambientOcclusion ) { + + materialLightsNode.push( new AONode( builder.context.ambientOcclusion ) ); + + } + + return materialLightsNode; + + } + + /** + * Setups the ambient occlusion node from the material. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The ambient occlusion node. + */ + setupAmbientOcclusion( builder ) { + let aoNode = this.aoNode; if ( aoNode === null && builder.material.aoMap ) { @@ -1029,13 +1048,13 @@ class NodeMaterial extends Material { } - if ( aoNode ) { + if ( aoNode !== null ) { - materialLightsNode.push( new AONode( aoNode ) ); + ambientOcclusion.assign( aoNode ); - } + builder.context.ambientOcclusion = ambientOcclusion; - return materialLightsNode; + } } diff --git a/src/nodes/accessors/MaterialNode.js b/src/nodes/accessors/MaterialNode.js index c6f09a2e862313..cfde539dc6aa7b 100644 --- a/src/nodes/accessors/MaterialNode.js +++ b/src/nodes/accessors/MaterialNode.js @@ -388,11 +388,27 @@ class MaterialNode extends Node { } else if ( scope === MaterialNode.LIGHT_MAP ) { - node = this.getTexture( scope ).rgb.mul( this.getFloat( 'lightMapIntensity' ) ); + if ( material.lightMap ) { + + node = this.getTexture( scope ).rgb.mul( this.getFloat( 'lightMapIntensity' ) ); + + } else { + + node = vec3( 0.0 ); + + } } else if ( scope === MaterialNode.AO ) { - node = this.getTexture( scope ).r.sub( 1.0 ).mul( this.getFloat( 'aoMapIntensity' ) ).add( 1.0 ); + if ( material.aoMap ) { + + node = this.getTexture( scope ).r.sub( 1.0 ).mul( this.getFloat( 'aoMapIntensity' ) ).add( 1.0 ); + + } else { + + node = float( 1.0 ); + + } } else if ( scope === MaterialNode.LINE_DASH_OFFSET ) { diff --git a/src/nodes/core/NodeBuilder.js b/src/nodes/core/NodeBuilder.js index 96792c432a9bf0..deba7df69d5cd3 100644 --- a/src/nodes/core/NodeBuilder.js +++ b/src/nodes/core/NodeBuilder.js @@ -49,7 +49,7 @@ const typeFromArray = new Map( [ [ Float32Array, 'float' ] ] ); -const toFloat = ( value ) => { +const _toFloat = ( value ) => { if ( /e/g.test( value ) ) { @@ -65,6 +65,28 @@ const toFloat = ( value ) => { }; +const _checkWriteUsage = ( data ) => { + + if ( data.writeUsageCount > 0 ) return true; + + if ( data.subBuildsCache !== undefined ) { + + for ( const subBuild in data.subBuildsCache ) { + + if ( _checkWriteUsage( data.subBuildsCache[ subBuild ] ) ) { + + return true; + + } + + } + + } + + return false; + +}; + /** * Base class for builders which generate a shader program based * on a 3D object and its node material definition. @@ -1208,6 +1230,17 @@ class NodeBuilder { } + /** + * Returns whether the builder is currently in an assignment context. + * + * @return {boolean} Whether the builder is in an assignment context. + */ + isContextAssign() { + + return this.context.assign === true; + + } + /** * Calling this method increases the usage count for the given node by one. * @@ -1219,10 +1252,50 @@ class NodeBuilder { const nodeData = this.getDataFromNode( node ); nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; + if ( this.isContextAssign() ) { + + nodeData.writeUsageCount = nodeData.writeUsageCount === undefined ? 1 : nodeData.writeUsageCount + 1; + + } else { + + nodeData.readUsageCount = nodeData.readUsageCount === undefined ? 1 : nodeData.readUsageCount + 1; + + } + return nodeData.usageCount; } + /** + * Returns whether the given node has been written to in any shader stage. + * + * @param {Node} node - The node to check. + * @return {boolean} Whether the node has been written to. + */ + hasWriteUsage( node ) { + + const refNode = node.getShared( this ); + const cache = refNode.isGlobal( this ) ? this.globalCache : this.cache; + const nodeData = cache.getData( refNode ); + + if ( nodeData !== undefined ) { + + for ( const shaderStage in nodeData ) { + + if ( _checkWriteUsage( nodeData[ shaderStage ] ) ) { + + return true; + + } + + } + + } + + return false; + + } + /** * Generates a texture sample shader string for the given texture data. * @@ -1356,11 +1429,11 @@ class NodeBuilder { } - if ( type === 'float' ) return toFloat( value ); + if ( type === 'float' ) return _toFloat( value ); if ( type === 'int' ) return `${ Math.round( value ) }`; if ( type === 'uint' ) return value >= 0 ? `${ Math.round( value ) }u` : '0u'; if ( type === 'bool' ) return value ? 'true' : 'false'; - if ( type === 'color' ) return `${ this.getType( 'vec3' ) }( ${ toFloat( value.r ) }, ${ toFloat( value.g ) }, ${ toFloat( value.b ) } )`; + if ( type === 'color' ) return `${ this.getType( 'vec3' ) }( ${ _toFloat( value.r ) }, ${ _toFloat( value.g ) }, ${ _toFloat( value.b ) } )`; const typeLength = this.getTypeLength( type ); diff --git a/src/nodes/core/PropertyNode.js b/src/nodes/core/PropertyNode.js index 1c379328ae2f8c..828a76881057ed 100644 --- a/src/nodes/core/PropertyNode.js +++ b/src/nodes/core/PropertyNode.js @@ -1,5 +1,5 @@ import Node from './Node.js'; -import { nodeImmutable } from '../tsl/TSLCore.js'; +import { nodeImmutable, nodeObject } from '../tsl/TSLCore.js'; import { hashString } from './NodeUtils.js'; /** @@ -28,8 +28,9 @@ class PropertyNode extends Node { * @param {string} nodeType - The type of the node. * @param {?string} [name=null] - The name of the property in the shader. * @param {boolean} [varying=false] - Whether this property is a varying or not. + * @param {?Node} [placeholderNode=null] - The placeholder node if not assigned. */ - constructor( nodeType, name = null, varying = false ) { + constructor( nodeType, name = null, varying = false, placeholderNode = null ) { super( nodeType ); @@ -50,6 +51,14 @@ class PropertyNode extends Node { */ this.varying = varying; + /** + * The placeholder node of the property if it is not assigned. + * + * @type {?Node} + * @default null + */ + this.placeholderNode = nodeObject( placeholderNode ); + /** * This flag can be used for type testing. * @@ -108,6 +117,18 @@ class PropertyNode extends Node { nodeVar = builder.getVarFromNode( this, this.name ); + if ( this.placeholderNode !== null ) { + + if ( builder.hasWriteUsage( this ) === false ) { + + const snippet = this.placeholderNode.build( builder, this.getNodeType( builder ) ); + + builder.addLineFlowCode( `${ builder.getPropertyName( nodeVar ) } = ${ snippet }`, this ); + + } + + } + } return builder.getPropertyName( nodeVar ); @@ -125,9 +146,10 @@ export default PropertyNode; * @function * @param {string} type - The type of the node. * @param {?string} [name=null] - The name of the property in the shader. + * @param {?Node} [placeholderNode=null] - The placeholder node if not assigned. * @returns {PropertyNode} */ -export const property = ( type, name ) => new PropertyNode( type, name ); +export const property = ( type, name, placeholderNode = null ) => new PropertyNode( type, name, false, placeholderNode ); /** * TSL function for creating a varying property node. @@ -136,9 +158,10 @@ export const property = ( type, name ) => new PropertyNode( type, name ); * @function * @param {string} type - The type of the node. * @param {?string} [name=null] - The name of the varying in the shader. + * @param {?Node} [placeholderNode=null] - The placeholder node if not assigned. * @returns {PropertyNode} */ -export const varyingProperty = ( type, name ) => new PropertyNode( type, name, true ); +export const varyingProperty = ( type, name, placeholderNode = null ) => new PropertyNode( type, name, true, placeholderNode ); /** * TSL object that represents the shader variable `DiffuseColor`. @@ -379,3 +402,12 @@ export const attenuationColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'colo * @type {PropertyNode} */ export const dispersion = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Dispersion' ); + +/** + * TSL object that represents the shader variable `AmbientOcclusion`. + * If no value is assigned to this property, it defaults to a placeholder value of `1.0`. + * + * @tsl + * @type {PropertyNode} + */ +export const ambientOcclusion = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'AmbientOcclusion', false, 1 ); diff --git a/src/nodes/gpgpu/WorkgroupInfoNode.js b/src/nodes/gpgpu/WorkgroupInfoNode.js index 3507f44b9f8902..65902459eb3b6a 100644 --- a/src/nodes/gpgpu/WorkgroupInfoNode.js +++ b/src/nodes/gpgpu/WorkgroupInfoNode.js @@ -35,7 +35,7 @@ class WorkgroupInfoElementNode extends ArrayElementNode { let snippet; - const isAssignContext = builder.context.assign; + const isAssignContext = builder.isContextAssign(); snippet = super.generate( builder ); if ( isAssignContext !== true ) { diff --git a/src/nodes/utils/StorageArrayElementNode.js b/src/nodes/utils/StorageArrayElementNode.js index c6109c4e2b0160..117d77aa7406c8 100644 --- a/src/nodes/utils/StorageArrayElementNode.js +++ b/src/nodes/utils/StorageArrayElementNode.js @@ -93,7 +93,7 @@ class StorageArrayElementNode extends ArrayElementNode { let snippet; - const isAssignContext = builder.context.assign; + const isAssignContext = builder.isContextAssign(); //