Accueil / npm / three@0.186.1 / src / nodes / lighting / EnvironmentNode.js

EnvironmentNode.js

import LightingNode from './LightingNode.js';
import { isolate } from '../core/IsolateNode.js';
import { roughness, clearcoatRoughness, retroreflectivity } from '../core/PropertyNode.js';
import { cameraWorldMatrix } from '../accessors/Camera.js';
import { normalView, clearcoatNormalView, normalWorld } from '../accessors/Normal.js';
import { positionViewDirection } from '../accessors/Position.js';
import { float, pow4 } from '../tsl/TSLBase.js';
import { bentNormalView } from '../accessors/AccessorsUtils.js';
import { pmremTexture } from '../pmrem/PMREMNode.js';
import { materialEnvIntensity } from '../accessors/MaterialProperties.js';

const _rendererCache = new WeakMap();

/**
 * Represents a physical model for Image-based lighting (IBL). The environment
 * is defined via environment maps in the equirectangular, cube map or cubeUV (PMREM) format.
 * `EnvironmentNode` is intended for PBR materials like {@link MeshStandardNodeMaterial}.
 *
 * @augments LightingNode
 */
class EnvironmentNode extends LightingNode {

	static get type() {

		return 'EnvironmentNode';

	}

	/**
	 * Constructs a new environment node.
	 *
	 * @param {Node} [envNode=null] - A node representing the environment.
	 */
	constructor( envNode = null ) {

		super();

		/**
		 * A node representing the environment.
		 *
		 * @type {?Node}
		 * @default null
		 */
		this.envNode = envNode;

	}

	setup( builder ) {

		const { material } = builder;

		let envNode = this.envNode;

		if ( envNode.isTextureNode || envNode.isMaterialReferenceNode ) {

			const value = ( envNode.isTextureNode ) ? envNode.value : material[ envNode.property ];

			const cache = this._getPMREMNodeCache( builder.renderer );

			let cacheEnvNode = cache.get( value );

			if ( cacheEnvNode === undefined ) {

				cacheEnvNode = pmremTexture( value );

				cache.set( value, cacheEnvNode );

			}

			envNode	= cacheEnvNode;

		}

		//

		const useAnisotropy = material.useAnisotropy === true || material.anisotropy > 0;
		const radianceNormalView = useAnisotropy ? bentNormalView : normalView;

		let radiance = isolate( envNode.context( createRadianceContext( roughness, radianceNormalView ) ).mul( materialEnvIntensity ) );

		if ( material.useRetroreflection === true || material.retroreflectivity > 0 ) {

			const retroRadiance = isolate( envNode.context( createRetroRadianceContext( roughness, radianceNormalView ) ).mul( materialEnvIntensity ) );

			radiance = retroreflectivity.clamp().mix( radiance, retroRadiance );

		}

		const irradiance = envNode.context( createIrradianceContext( normalWorld ) ).mul( Math.PI ).mul( materialEnvIntensity );

		const isolateIrradiance = isolate( irradiance );

		//

		builder.context.radiance.addAssign( radiance );

		builder.context.iblIrradiance.addAssign( isolateIrradiance );

		//

		const clearcoatRadiance = builder.context.lightingModel.clearcoatRadiance;

		if ( clearcoatRadiance ) {

			const clearcoatRadianceContext = envNode.context( createRadianceContext( clearcoatRoughness, clearcoatNormalView ) ).mul( materialEnvIntensity );
			const isolateClearcoatRadiance = isolate( clearcoatRadianceContext );

			clearcoatRadiance.addAssign( isolateClearcoatRadiance );

		}

	}

	/**
	 * Returns the PMREM node cache of the current renderer.
	 *
	 * @private
	 * @param {Renderer} renderer - The current renderer.
	 * @return {WeakMap} The node cache.
	 */
	_getPMREMNodeCache( renderer ) {

		let pmremCache = _rendererCache.get( renderer );

		if ( pmremCache === undefined ) {

			pmremCache = new WeakMap();

			_rendererCache.set( renderer, pmremCache );

		}

		return pmremCache;

	}

}

export default EnvironmentNode;

const createRadianceContext = ( roughnessNode, normalViewNode ) => {

	let reflectVec = null;

	return {
		getUV: () => {

			if ( reflectVec === null ) {

				reflectVec = positionViewDirection.negate().reflect( normalViewNode );

				// Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane.
				reflectVec = pow4( roughnessNode ).mix( reflectVec, normalViewNode ).normalize();

				reflectVec = reflectVec.transformDirection( cameraWorldMatrix );

			}

			return reflectVec;

		},
		getTextureLevel: () => {

			return roughnessNode;

		}
	};

};

const createRetroRadianceContext = ( roughnessNode, normalViewNode ) => {

	let retroVec = null;

	return {
		getUV: () => {

			if ( retroVec === null ) {

				// The retroreflective lobe returns light toward its source, so the environment is sampled along the view direction
				retroVec = pow4( roughnessNode ).mix( positionViewDirection, normalViewNode ).normalize();

				retroVec = retroVec.transformDirection( cameraWorldMatrix );

			}

			return retroVec;

		},
		getTextureLevel: () => {

			return roughnessNode;

		}
	};

};

const createIrradianceContext = ( normalWorldNode ) => {

	return {
		getUV: () => {

			return normalWorldNode;

		},
		getTextureLevel: () => {

			return float( 1.0 );

		}
	};

};

Exemple