最新代码

This commit is contained in:
Teo
2025-07-29 11:22:30 +08:00
parent d503e64098
commit b01d143ea6
1490 changed files with 680232 additions and 28 deletions

View File

@ -0,0 +1,27 @@
import LightingNode from './LightingNode.js';
import { addNodeClass } from '../core/Node.js';
class AONode extends LightingNode {
constructor( aoNode = null ) {
super();
this.aoNode = aoNode;
}
setup( builder ) {
const aoIntensity = 1;
const aoNode = this.aoNode.x.sub( 1.0 ).mul( aoIntensity ).add( 1.0 );
builder.context.ambientOcclusion.mulAssign( aoNode );
}
}
export default AONode;
addNodeClass( 'AONode', AONode );

View File

@ -0,0 +1,27 @@
import AnalyticLightNode from './AnalyticLightNode.js';
import { addLightNode } from './LightsNode.js';
import { addNodeClass } from '../core/Node.js';
import { AmbientLight } from 'three';
class AmbientLightNode extends AnalyticLightNode {
constructor( light = null ) {
super( light );
}
setup( { context } ) {
context.irradiance.addAssign( this.colorNode );
}
}
export default AmbientLightNode;
addNodeClass( 'AmbientLightNode', AmbientLightNode );
addLightNode( AmbientLight, AmbientLightNode );

View File

@ -0,0 +1,255 @@
import LightingNode from './LightingNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { uniform } from '../core/UniformNode.js';
import { addNodeClass } from '../core/Node.js';
import { /*vec2,*/ vec3, vec4 } from '../shadernode/ShaderNode.js';
import { reference } from '../accessors/ReferenceNode.js';
import { texture } from '../accessors/TextureNode.js';
import { positionWorld } from '../accessors/PositionNode.js';
import { normalWorld } from '../accessors/NormalNode.js';
import { WebGPUCoordinateSystem } from 'three';
//import { add } from '../math/OperatorNode.js';
import { Color, DepthTexture, NearestFilter, LessCompare, NoToneMapping } from 'three';
let overrideMaterial = null;
class AnalyticLightNode extends LightingNode {
constructor( light = null ) {
super();
this.updateType = NodeUpdateType.FRAME;
this.light = light;
this.rtt = null;
this.shadowNode = null;
this.shadowMaskNode = null;
this.color = new Color();
this._defaultColorNode = uniform( this.color );
this.colorNode = this._defaultColorNode;
this.isAnalyticLightNode = true;
}
getCacheKey() {
return super.getCacheKey() + '-' + ( this.light.id + '-' + ( this.light.castShadow ? '1' : '0' ) );
}
getHash() {
return this.light.uuid;
}
setupShadow( builder ) {
const { object } = builder;
if ( object.receiveShadow === false ) return;
let shadowNode = this.shadowNode;
if ( shadowNode === null ) {
if ( overrideMaterial === null ) {
overrideMaterial = builder.createNodeMaterial();
overrideMaterial.fragmentNode = vec4( 0, 0, 0, 1 );
overrideMaterial.isShadowNodeMaterial = true; // Use to avoid other overrideMaterial override material.fragmentNode unintentionally when using material.shadowNode
}
const shadow = this.light.shadow;
const rtt = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height );
const depthTexture = new DepthTexture();
depthTexture.minFilter = NearestFilter;
depthTexture.magFilter = NearestFilter;
depthTexture.image.width = shadow.mapSize.width;
depthTexture.image.height = shadow.mapSize.height;
depthTexture.compareFunction = LessCompare;
rtt.depthTexture = depthTexture;
shadow.camera.updateProjectionMatrix();
//
const bias = reference( 'bias', 'float', shadow );
const normalBias = reference( 'normalBias', 'float', shadow );
const position = object.material.shadowPositionNode || positionWorld;
let shadowCoord = uniform( shadow.matrix ).mul( position.add( normalWorld.mul( normalBias ) ) );
shadowCoord = shadowCoord.xyz.div( shadowCoord.w );
const frustumTest = shadowCoord.x.greaterThanEqual( 0 )
.and( shadowCoord.x.lessThanEqual( 1 ) )
.and( shadowCoord.y.greaterThanEqual( 0 ) )
.and( shadowCoord.y.lessThanEqual( 1 ) )
.and( shadowCoord.z.lessThanEqual( 1 ) );
let coordZ = shadowCoord.z.add( bias );
if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem ) {
coordZ = coordZ.mul( 2 ).sub( 1 ); // WebGPU: Convertion [ 0, 1 ] to [ - 1, 1 ]
}
shadowCoord = vec3(
shadowCoord.x,
shadowCoord.y.oneMinus(), // follow webgpu standards
coordZ
);
const textureCompare = ( depthTexture, shadowCoord, compare ) => texture( depthTexture, shadowCoord ).compare( compare );
//const textureCompare = ( depthTexture, shadowCoord, compare ) => compare.step( texture( depthTexture, shadowCoord ) );
// BasicShadowMap
shadowNode = textureCompare( depthTexture, shadowCoord.xy, shadowCoord.z );
// PCFShadowMap
/*
const mapSize = reference( 'mapSize', 'vec2', shadow );
const radius = reference( 'radius', 'float', shadow );
const texelSize = vec2( 1 ).div( mapSize );
const dx0 = texelSize.x.negate().mul( radius );
const dy0 = texelSize.y.negate().mul( radius );
const dx1 = texelSize.x.mul( radius );
const dy1 = texelSize.y.mul( radius );
const dx2 = dx0.mul( 2 );
const dy2 = dy0.mul( 2 );
const dx3 = dx1.mul( 2 );
const dy3 = dy1.mul( 2 );
shadowNode = add(
textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx0, dy0 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( 0, dy0 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx1, dy0 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx2, dy2 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( 0, dy2 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx3, dy2 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx0, 0 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx2, 0 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy, shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx3, 0 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx1, 0 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx2, dy3 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( 0, dy3 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx3, dy3 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx0, dy1 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( 0, dy1 ) ), shadowCoord.z ),
textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx1, dy1 ) ), shadowCoord.z )
).mul( 1 / 17 );
*/
//
const shadowColor = texture( rtt.texture, shadowCoord );
const shadowMaskNode = frustumTest.mix( 1, shadowNode.mix( shadowColor.a.mix( 1, shadowColor ), 1 ) );
this.rtt = rtt;
this.colorNode = this.colorNode.mul( shadowMaskNode );
this.shadowNode = shadowNode;
this.shadowMaskNode = shadowMaskNode;
//
this.updateBeforeType = NodeUpdateType.RENDER;
}
}
setup( builder ) {
if ( this.light.castShadow ) this.setupShadow( builder );
else if ( this.shadowNode !== null ) this.disposeShadow();
}
updateShadow( frame ) {
const { rtt, light } = this;
const { renderer, scene } = frame;
const currentOverrideMaterial = scene.overrideMaterial;
scene.overrideMaterial = overrideMaterial;
rtt.setSize( light.shadow.mapSize.width, light.shadow.mapSize.height );
light.shadow.updateMatrices( light );
const currentToneMapping = renderer.toneMapping;
const currentRenderTarget = renderer.getRenderTarget();
const currentRenderObjectFunction = renderer.getRenderObjectFunction();
renderer.setRenderObjectFunction( ( object, ...params ) => {
if ( object.castShadow === true ) {
renderer.renderObject( object, ...params );
}
} );
renderer.setRenderTarget( rtt );
renderer.toneMapping = NoToneMapping;
renderer.render( scene, light.shadow.camera );
renderer.setRenderTarget( currentRenderTarget );
renderer.setRenderObjectFunction( currentRenderObjectFunction );
renderer.toneMapping = currentToneMapping;
scene.overrideMaterial = currentOverrideMaterial;
}
disposeShadow() {
this.rtt.dispose();
this.shadowNode = null;
this.shadowMaskNode = null;
this.rtt = null;
this.colorNode = this._defaultColorNode;
}
updateBefore( frame ) {
const { light } = this;
if ( light.castShadow ) this.updateShadow( frame );
}
update( /*frame*/ ) {
const { light } = this;
this.color.copy( light.color ).multiplyScalar( light.intensity );
}
}
export default AnalyticLightNode;
addNodeClass( 'AnalyticLightNode', AnalyticLightNode );

View File

@ -0,0 +1,41 @@
import AnalyticLightNode from './AnalyticLightNode.js';
import { lightTargetDirection } from './LightNode.js';
import { addLightNode } from './LightsNode.js';
import { addNodeClass } from '../core/Node.js';
import { DirectionalLight } from 'three';
class DirectionalLightNode extends AnalyticLightNode {
constructor( light = null ) {
super( light );
}
setup( builder ) {
super.setup( builder );
const lightingModel = builder.context.lightingModel;
const lightColor = this.colorNode;
const lightDirection = lightTargetDirection( this.light );
const reflectedLight = builder.context.reflectedLight;
lightingModel.direct( {
lightDirection,
lightColor,
reflectedLight,
shadowMask: this.shadowMaskNode
}, builder.stack, builder );
}
}
export default DirectionalLightNode;
addNodeClass( 'DirectionalLightNode', DirectionalLightNode );
addLightNode( DirectionalLight, DirectionalLightNode );

View File

@ -0,0 +1,133 @@
import LightingNode from './LightingNode.js';
import { cache } from '../core/CacheNode.js';
import { context } from '../core/ContextNode.js';
import { roughness, clearcoatRoughness } from '../core/PropertyNode.js';
import { cameraViewMatrix } from '../accessors/CameraNode.js';
import { transformedClearcoatNormalView, transformedNormalView, transformedNormalWorld } from '../accessors/NormalNode.js';
import { positionViewDirection } from '../accessors/PositionNode.js';
import { addNodeClass } from '../core/Node.js';
import { float } from '../shadernode/ShaderNode.js';
import { reference } from '../accessors/ReferenceNode.js';
import { transformedBentNormalView } from '../accessors/AccessorsUtils.js';
import { pmremTexture } from '../pmrem/PMREMNode.js';
const envNodeCache = new WeakMap();
class EnvironmentNode extends LightingNode {
constructor( envNode = null ) {
super();
this.envNode = envNode;
}
setup( builder ) {
let envNode = this.envNode;
if ( envNode.isTextureNode ) {
let cacheEnvNode = envNodeCache.get( envNode.value );
if ( cacheEnvNode === undefined ) {
cacheEnvNode = pmremTexture( envNode.value );
envNodeCache.set( envNode.value, cacheEnvNode );
}
envNode = cacheEnvNode;
}
//
const { material } = builder;
const envMap = material.envMap;
const intensity = envMap ? reference( 'envMapIntensity', 'float', builder.material ) : reference( 'environmentIntensity', 'float', builder.scene ); // @TODO: Add materialEnvIntensity in MaterialNode
const useAnisotropy = material.useAnisotropy === true || material.anisotropy > 0;
const radianceNormalView = useAnisotropy ? transformedBentNormalView : transformedNormalView;
const radiance = context( envNode, createRadianceContext( roughness, radianceNormalView ) ).mul( intensity );
const irradiance = context( envNode, createIrradianceContext( transformedNormalWorld ) ).mul( Math.PI ).mul( intensity );
const isolateRadiance = cache( radiance );
//
builder.context.radiance.addAssign( isolateRadiance );
builder.context.iblIrradiance.addAssign( irradiance );
//
const clearcoatRadiance = builder.context.lightingModel.clearcoatRadiance;
if ( clearcoatRadiance ) {
const clearcoatRadianceContext = context( envNode, createRadianceContext( clearcoatRoughness, transformedClearcoatNormalView ) ).mul( intensity );
const isolateClearcoatRadiance = cache( clearcoatRadianceContext );
clearcoatRadiance.addAssign( isolateClearcoatRadiance );
}
}
}
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 = roughnessNode.mul( roughnessNode ).mix( reflectVec, normalViewNode ).normalize();
reflectVec = reflectVec.transformDirection( cameraViewMatrix );
}
return reflectVec;
},
getTextureLevel: () => {
return roughnessNode;
}
};
};
const createIrradianceContext = ( normalWorldNode ) => {
return {
getUV: () => {
return normalWorldNode;
},
getTextureLevel: () => {
return float( 1.0 );
}
};
};
export default EnvironmentNode;
addNodeClass( 'EnvironmentNode', EnvironmentNode );

View File

@ -0,0 +1,55 @@
import AnalyticLightNode from './AnalyticLightNode.js';
import { addLightNode } from './LightsNode.js';
import { uniform } from '../core/UniformNode.js';
import { mix } from '../math/MathNode.js';
import { normalView } from '../accessors/NormalNode.js';
import { objectPosition } from '../accessors/Object3DNode.js';
import { addNodeClass } from '../core/Node.js';
import { Color, HemisphereLight } from 'three';
class HemisphereLightNode extends AnalyticLightNode {
constructor( light = null ) {
super( light );
this.lightPositionNode = objectPosition( light );
this.lightDirectionNode = this.lightPositionNode.normalize();
this.groundColorNode = uniform( new Color() );
}
update( frame ) {
const { light } = this;
super.update( frame );
this.lightPositionNode.object3d = light;
this.groundColorNode.value.copy( light.groundColor ).multiplyScalar( light.intensity );
}
setup( builder ) {
const { colorNode, groundColorNode, lightDirectionNode } = this;
const dotNL = normalView.dot( lightDirectionNode );
const hemiDiffuseWeight = dotNL.mul( 0.5 ).add( 0.5 );
const irradiance = mix( groundColorNode, colorNode, hemiDiffuseWeight );
builder.context.irradiance.addAssign( irradiance );
}
}
export default HemisphereLightNode;
addNodeClass( 'HemisphereLightNode', HemisphereLightNode );
addLightNode( HemisphereLight, HemisphereLightNode );

View File

@ -0,0 +1,39 @@
import SpotLightNode from './SpotLightNode.js';
import { addLightNode } from './LightsNode.js';
import { texture } from '../accessors/TextureNode.js';
import { vec2 } from '../shadernode/ShaderNode.js';
import { addNodeClass } from '../core/Node.js';
import IESSpotLight from '../../lights/IESSpotLight.js';
class IESSpotLightNode extends SpotLightNode {
getSpotAttenuation( angleCosine ) {
const iesMap = this.light.iesMap;
let spotAttenuation = null;
if ( iesMap && iesMap.isTexture === true ) {
const angle = angleCosine.acos().mul( 1.0 / Math.PI );
spotAttenuation = texture( iesMap, vec2( angle, 0 ), 0 ).r;
} else {
spotAttenuation = super.getSpotAttenuation( angleCosine );
}
return spotAttenuation;
}
}
export default IESSpotLightNode;
addNodeClass( 'IESSpotLightNode', IESSpotLightNode );
addLightNode( IESSpotLight, IESSpotLightNode );

View File

@ -0,0 +1,24 @@
import LightingNode from './LightingNode.js';
import { addNodeClass } from '../core/Node.js';
class IrradianceNode extends LightingNode {
constructor( node ) {
super();
this.node = node;
}
setup( builder ) {
builder.context.irradiance.addAssign( this.node );
}
}
export default IrradianceNode;
addNodeClass( 'IrradianceNode', IrradianceNode );

View File

@ -0,0 +1,57 @@
import Node, { addNodeClass } from '../core/Node.js';
import { nodeProxy } from '../shadernode/ShaderNode.js';
import { objectPosition } from '../accessors/Object3DNode.js';
import { cameraViewMatrix } from '../accessors/CameraNode.js';
class LightNode extends Node {
constructor( scope = LightNode.TARGET_DIRECTION, light = null ) {
super();
this.scope = scope;
this.light = light;
}
setup() {
const { scope, light } = this;
let output = null;
if ( scope === LightNode.TARGET_DIRECTION ) {
output = cameraViewMatrix.transformDirection( objectPosition( light ).sub( objectPosition( light.target ) ) );
}
return output;
}
serialize( data ) {
super.serialize( data );
data.scope = this.scope;
}
deserialize( data ) {
super.deserialize( data );
this.scope = data.scope;
}
}
LightNode.TARGET_DIRECTION = 'targetDirection';
export default LightNode;
export const lightTargetDirection = nodeProxy( LightNode, LightNode.TARGET_DIRECTION );
addNodeClass( 'LightNode', LightNode );

View File

@ -0,0 +1,17 @@
import { tslFn } from '../shadernode/ShaderNode.js';
export const getDistanceAttenuation = tslFn( ( inputs ) => {
const { lightDistance, cutoffDistance, decayExponent } = inputs;
// based upon Frostbite 3 Moving to Physically-based Rendering
// page 32, equation 26: E[window1]
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
const distanceFalloff = lightDistance.pow( decayExponent ).max( 0.01 ).reciprocal();
return cutoffDistance.greaterThan( 0 ).cond(
distanceFalloff.mul( lightDistance.div( cutoffDistance ).pow4().oneMinus().clamp().pow2() ),
distanceFalloff
);
} ); // validated

View File

@ -0,0 +1,66 @@
import ContextNode from '../core/ContextNode.js';
import { addNodeClass } from '../core/Node.js';
import { addNodeElement, nodeProxy, float, vec3 } from '../shadernode/ShaderNode.js';
class LightingContextNode extends ContextNode {
constructor( node, lightingModel = null, backdropNode = null, backdropAlphaNode = null ) {
super( node );
this.lightingModel = lightingModel;
this.backdropNode = backdropNode;
this.backdropAlphaNode = backdropAlphaNode;
this._context = null;
}
getContext() {
const { backdropNode, backdropAlphaNode } = this;
const directDiffuse = vec3().temp( 'directDiffuse' ),
directSpecular = vec3().temp( 'directSpecular' ),
indirectDiffuse = vec3().temp( 'indirectDiffuse' ),
indirectSpecular = vec3().temp( 'indirectSpecular' );
const reflectedLight = {
directDiffuse,
directSpecular,
indirectDiffuse,
indirectSpecular
};
const context = {
radiance: vec3().temp( 'radiance' ),
irradiance: vec3().temp( 'irradiance' ),
iblIrradiance: vec3().temp( 'iblIrradiance' ),
ambientOcclusion: float( 1 ).temp( 'ambientOcclusion' ),
reflectedLight,
backdrop: backdropNode,
backdropAlpha: backdropAlphaNode
};
return context;
}
setup( builder ) {
this.context = this._context || ( this._context = this.getContext() );
this.context.lightingModel = this.lightingModel || builder.context.lightingModel;
return super.setup( builder );
}
}
export default LightingContextNode;
export const lightingContext = nodeProxy( LightingContextNode );
addNodeElement( 'lightingContext', lightingContext );
addNodeClass( 'LightingContextNode', LightingContextNode );

View File

@ -0,0 +1,21 @@
import Node, { addNodeClass } from '../core/Node.js';
class LightingNode extends Node {
constructor() {
super( 'vec3' );
}
generate( /*builder*/ ) {
console.warn( 'Abstract function.' );
}
}
export default LightingNode;
addNodeClass( 'LightingNode', LightingNode );

View File

@ -0,0 +1,198 @@
import Node from '../core/Node.js';
import AnalyticLightNode from './AnalyticLightNode.js';
import { nodeObject, nodeProxy, vec3 } from '../shadernode/ShaderNode.js';
const LightNodes = new WeakMap();
const sortLights = ( lights ) => {
return lights.sort( ( a, b ) => a.id - b.id );
};
class LightsNode extends Node {
constructor( lightNodes = [] ) {
super( 'vec3' );
this.totalDiffuseNode = vec3().temp( 'totalDiffuse' );
this.totalSpecularNode = vec3().temp( 'totalSpecular' );
this.outgoingLightNode = vec3().temp( 'outgoingLight' );
this.lightNodes = lightNodes;
this._hash = null;
}
get hasLight() {
return this.lightNodes.length > 0;
}
getHash() {
if ( this._hash === null ) {
const hash = [];
for ( const lightNode of this.lightNodes ) {
hash.push( lightNode.getHash() );
}
this._hash = 'lights-' + hash.join( ',' );
}
return this._hash;
}
setup( builder ) {
const context = builder.context;
const lightingModel = context.lightingModel;
let outgoingLightNode = this.outgoingLightNode;
if ( lightingModel ) {
const { lightNodes, totalDiffuseNode, totalSpecularNode } = this;
context.outgoingLight = outgoingLightNode;
const stack = builder.addStack();
//
lightingModel.start( context, stack, builder );
// lights
for ( const lightNode of lightNodes ) {
lightNode.build( builder );
}
//
lightingModel.indirectDiffuse( context, stack, builder );
lightingModel.indirectSpecular( context, stack, builder );
lightingModel.ambientOcclusion( context, stack, builder );
//
const { backdrop, backdropAlpha } = context;
const { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular } = context.reflectedLight;
let totalDiffuse = directDiffuse.add( indirectDiffuse );
if ( backdrop !== null ) {
if ( backdropAlpha !== null ) {
totalDiffuse = vec3( backdropAlpha.mix( totalDiffuse, backdrop ) );
} else {
totalDiffuse = vec3( backdrop );
}
context.material.transparent = true;
}
totalDiffuseNode.assign( totalDiffuse );
totalSpecularNode.assign( directSpecular.add( indirectSpecular ) );
outgoingLightNode.assign( totalDiffuseNode.add( totalSpecularNode ) );
//
lightingModel.finish( context, stack, builder );
//
outgoingLightNode = outgoingLightNode.bypass( builder.removeStack() );
}
return outgoingLightNode;
}
_getLightNodeById( id ) {
for ( const lightNode of this.lightNodes ) {
if ( lightNode.isAnalyticLightNode && lightNode.light.id === id ) {
return lightNode;
}
}
return null;
}
fromLights( lights = [] ) {
const lightNodes = [];
lights = sortLights( lights );
for ( const light of lights ) {
let lightNode = this._getLightNodeById( light.id );
if ( lightNode === null ) {
const lightClass = light.constructor;
const lightNodeClass = LightNodes.has( lightClass ) ? LightNodes.get( lightClass ) : AnalyticLightNode;
lightNode = nodeObject( new lightNodeClass( light ) );
}
lightNodes.push( lightNode );
}
this.lightNodes = lightNodes;
this._hash = null;
return this;
}
}
export default LightsNode;
export const lights = ( lights ) => nodeObject( new LightsNode().fromLights( lights ) );
export const lightsNode = nodeProxy( LightsNode );
export function addLightNode( lightClass, lightNodeClass ) {
if ( LightNodes.has( lightClass ) ) {
console.warn( `Redefinition of light node ${ lightNodeClass.type }` );
return;
}
if ( typeof lightClass !== 'function' ) throw new Error( `Light ${ lightClass.name } is not a class` );
if ( typeof lightNodeClass !== 'function' || ! lightNodeClass.type ) throw new Error( `Light node ${ lightNodeClass.type } is not a class` );
LightNodes.set( lightClass, lightNodeClass );
}

View File

@ -0,0 +1,69 @@
import AnalyticLightNode from './AnalyticLightNode.js';
import { addLightNode } from './LightsNode.js';
import { getDistanceAttenuation } from './LightUtils.js';
import { uniform } from '../core/UniformNode.js';
import { objectViewPosition } from '../accessors/Object3DNode.js';
import { positionView } from '../accessors/PositionNode.js';
import { addNodeClass } from '../core/Node.js';
import { PointLight } from 'three';
class PointLightNode extends AnalyticLightNode {
constructor( light = null ) {
super( light );
this.cutoffDistanceNode = uniform( 0 );
this.decayExponentNode = uniform( 0 );
}
update( frame ) {
const { light } = this;
super.update( frame );
this.cutoffDistanceNode.value = light.distance;
this.decayExponentNode.value = light.decay;
}
setup( builder ) {
const { colorNode, cutoffDistanceNode, decayExponentNode, light } = this;
const lightingModel = builder.context.lightingModel;
const lVector = objectViewPosition( light ).sub( positionView ); // @TODO: Add it into LightNode
const lightDirection = lVector.normalize();
const lightDistance = lVector.length();
const lightAttenuation = getDistanceAttenuation( {
lightDistance,
cutoffDistance: cutoffDistanceNode,
decayExponent: decayExponentNode
} );
const lightColor = colorNode.mul( lightAttenuation );
const reflectedLight = builder.context.reflectedLight;
lightingModel.direct( {
lightDirection,
lightColor,
reflectedLight,
shadowMask: this.shadowMaskNode
}, builder.stack, builder );
}
}
export default PointLightNode;
addNodeClass( 'PointLightNode', PointLightNode );
addLightNode( PointLight, PointLightNode );

View File

@ -0,0 +1,90 @@
import AnalyticLightNode from './AnalyticLightNode.js';
import { lightTargetDirection } from './LightNode.js';
import { addLightNode } from './LightsNode.js';
import { getDistanceAttenuation } from './LightUtils.js';
import { uniform } from '../core/UniformNode.js';
import { smoothstep } from '../math/MathNode.js';
import { objectViewPosition } from '../accessors/Object3DNode.js';
import { positionView } from '../accessors/PositionNode.js';
import { addNodeClass } from '../core/Node.js';
import { SpotLight } from 'three';
class SpotLightNode extends AnalyticLightNode {
constructor( light = null ) {
super( light );
this.coneCosNode = uniform( 0 );
this.penumbraCosNode = uniform( 0 );
this.cutoffDistanceNode = uniform( 0 );
this.decayExponentNode = uniform( 0 );
}
update( frame ) {
super.update( frame );
const { light } = this;
this.coneCosNode.value = Math.cos( light.angle );
this.penumbraCosNode.value = Math.cos( light.angle * ( 1 - light.penumbra ) );
this.cutoffDistanceNode.value = light.distance;
this.decayExponentNode.value = light.decay;
}
getSpotAttenuation( angleCosine ) {
const { coneCosNode, penumbraCosNode } = this;
return smoothstep( coneCosNode, penumbraCosNode, angleCosine );
}
setup( builder ) {
super.setup( builder );
const lightingModel = builder.context.lightingModel;
const { colorNode, cutoffDistanceNode, decayExponentNode, light } = this;
const lVector = objectViewPosition( light ).sub( positionView ); // @TODO: Add it into LightNode
const lightDirection = lVector.normalize();
const angleCos = lightDirection.dot( lightTargetDirection( light ) );
const spotAttenuation = this.getSpotAttenuation( angleCos );
const lightDistance = lVector.length();
const lightAttenuation = getDistanceAttenuation( {
lightDistance,
cutoffDistance: cutoffDistanceNode,
decayExponent: decayExponentNode
} );
const lightColor = colorNode.mul( spotAttenuation ).mul( lightAttenuation );
const reflectedLight = builder.context.reflectedLight;
lightingModel.direct( {
lightDirection,
lightColor,
reflectedLight,
shadowMask: this.shadowMaskNode
}, builder.stack, builder );
}
}
export default SpotLightNode;
addNodeClass( 'SpotLightNode', SpotLightNode );
addLightNode( SpotLight, SpotLightNode );