| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271 |
- import { Material } from '../Material.js';
- import { NormalBlending } from '../../constants.js';
- import { getNodeChildren, getCacheKey } from '../../nodes/core/NodeUtils.js';
- import { output, diffuseColor, emissive, varyingProperty } 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';
- import { instancedMesh } from '../../nodes/accessors/InstancedMeshNode.js';
- import { batch } from '../../nodes/accessors/BatchNode.js';
- import { materialReference } from '../../nodes/accessors/MaterialReferenceNode.js';
- import { positionLocal, positionView } from '../../nodes/accessors/Position.js';
- import { skinning } from '../../nodes/accessors/SkinningNode.js';
- import { morphReference } from '../../nodes/accessors/MorphNode.js';
- import { mix } from '../../nodes/math/MathNode.js';
- import { float, vec3, vec4, bool } from '../../nodes/tsl/TSLBase.js';
- import AONode from '../../nodes/lighting/AONode.js';
- import { lightingContext } from '../../nodes/lighting/LightingContextNode.js';
- import IrradianceNode from '../../nodes/lighting/IrradianceNode.js';
- import { depth, viewZToLogarithmicDepth, viewZToOrthographicDepth } from '../../nodes/display/ViewportDepthNode.js';
- import { cameraFar, cameraNear, cameraProjectionMatrix } from '../../nodes/accessors/Camera.js';
- import { clipping, clippingAlpha, hardwareClipping } from '../../nodes/accessors/ClippingNode.js';
- import NodeMaterialObserver from './manager/NodeMaterialObserver.js';
- import getAlphaHashThreshold from '../../nodes/functions/material/getAlphaHashThreshold.js';
- import { modelViewMatrix } from '../../nodes/accessors/ModelNode.js';
- import { vertexColor } from '../../nodes/accessors/VertexColorNode.js';
- import { premultiplyAlpha } from '../../nodes/display/BlendModes.js';
- import { subBuild } from '../../nodes/core/SubBuildNode.js';
- import { warn } from '../../utils.js';
- /**
- * Base class for all node materials.
- *
- * @augments Material
- */
- class NodeMaterial extends Material {
- static get type() {
- return 'NodeMaterial';
- }
- /**
- * Represents the type of the node material.
- *
- * @type {string}
- */
- get type() {
- return this.constructor.type;
- }
- set type( _value ) { /* */ }
- /**
- * Constructs a new node material.
- */
- constructor() {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isNodeMaterial = true;
- /**
- * Whether this material is affected by fog or not.
- *
- * @type {boolean}
- * @default true
- */
- this.fog = true;
- /**
- * Whether this material is affected by lights or not.
- *
- * @type {boolean}
- * @default false
- */
- this.lights = false;
- /**
- * Whether this material uses hardware clipping or not.
- * This property is managed by the engine and should not be
- * modified by apps.
- *
- * @type {boolean}
- * @default false
- */
- this.hardwareClipping = false;
- /**
- * Node materials which set their `lights` property to `true`
- * are affected by all lights of the scene. Sometimes selective
- * lighting is wanted which means only _some_ lights in the scene
- * affect a material. This can be achieved by creating an instance
- * of {@link LightsNode} with a list of selective
- * lights and assign the node to this property.
- *
- * ```js
- * const customLightsNode = lights( [ light1, light2 ] );
- * material.lightsNode = customLightsNode;
- * ```
- *
- * @type {?LightsNode}
- * @default null
- */
- this.lightsNode = null;
- /**
- * The environment of node materials can be defined by an environment
- * map assigned to the `envMap` property or by `Scene.environment`
- * if the node material is a PBR material. This node property allows to overwrite
- * the default behavior and define the environment with a custom node.
- *
- * ```js
- * material.envNode = pmremTexture( renderTarget.texture );
- * ```
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.envNode = null;
- /**
- * The lighting of node materials might be influenced by ambient occlusion.
- * The default AO is inferred from an ambient occlusion map assigned to `aoMap`
- * and the respective `aoMapIntensity`. This node property allows to overwrite
- * the default and define the ambient occlusion with a custom node instead.
- *
- * If you don't want to overwrite the diffuse color but modify the existing
- * values instead, use {@link materialAO}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.aoNode = null;
- /**
- * The diffuse color of node materials is by default inferred from the
- * `color` and `map` properties. This node property allows to overwrite the default
- * and define the diffuse color with a node instead.
- *
- * ```js
- * material.colorNode = color( 0xff0000 ); // define red color
- * ```
- *
- * If you don't want to overwrite the diffuse color but modify the existing
- * values instead, use {@link materialColor}.
- *
- * ```js
- * material.colorNode = materialColor.mul( color( 0xff0000 ) ); // give diffuse colors a red tint
- * ```
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.colorNode = null;
- /**
- * The normals of node materials are by default inferred from the `normalMap`/`normalScale`
- * or `bumpMap`/`bumpScale` properties. This node property allows to overwrite the default
- * and define the normals with a node instead.
- *
- * If you don't want to overwrite the normals but modify the existing values instead,
- * use {@link materialNormal}.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.normalNode = null;
- /**
- * The opacity of node materials is by default inferred from the `opacity`
- * and `alphaMap` properties. This node property allows to overwrite the default
- * and define the opacity with a node instead.
- *
- * If you don't want to overwrite the normals but modify the existing
- * value instead, use {@link materialOpacity}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.opacityNode = null;
- /**
- * This node can be used to implement a variety of filter-like effects. The idea is
- * to store the current rendering into a texture e.g. via `viewportSharedTexture()`, use it
- * to create an arbitrary effect and then assign the node composition to this property.
- * Everything behind the object using this material will now be affected by a filter.
- *
- * ```js
- * const material = new NodeMaterial()
- * material.transparent = true;
- *
- * // everything behind the object will be monochromatic
- * material.backdropNode = saturation( viewportSharedTexture().rgb, 0 );
- * ```
- *
- * Backdrop computations are part of the lighting so only lit materials can use this property.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.backdropNode = null;
- /**
- * This node allows to modulate the influence of `backdropNode` to the outgoing light.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.backdropAlphaNode = null;
- /**
- * The alpha test of node materials is by default inferred from the `alphaTest`
- * property. This node property allows to overwrite the default and define the
- * alpha test with a node instead.
- *
- * If you don't want to overwrite the alpha test but modify the existing
- * value instead, use {@link materialAlphaTest}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.alphaTestNode = null;
- /**
- * Discards the fragment if the mask value is `false`.
- *
- * @type {?Node<bool>}
- * @default null
- */
- this.maskNode = null;
- /**
- * The local vertex positions are computed based on multiple factors like the
- * attribute data, morphing or skinning. This node property allows to overwrite
- * the default and define local vertex positions with nodes instead.
- *
- * If you don't want to overwrite the vertex positions but modify the existing
- * values instead, use {@link positionLocal}.
- *
- *```js
- * material.positionNode = positionLocal.add( displace );
- * ```
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.positionNode = null;
- /**
- * This node property is intended for logic which modifies geometry data once or per animation step.
- * Apps usually place such logic randomly in initialization routines or in the animation loop.
- * `geometryNode` is intended as a dedicated API so there is an intended spot where geometry modifications
- * can be implemented.
- *
- * The idea is to assign a `Fn` definition that holds the geometry modification logic. A typical example
- * would be a GPU based particle system that provides a node material for usage on app level. The particle
- * simulation would be implemented as compute shaders and managed inside a `Fn` function. This function is
- * eventually assigned to `geometryNode`.
- *
- * @type {?Function}
- * @default null
- */
- this.geometryNode = null;
- /**
- * Allows to overwrite depth values in the fragment shader.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.depthNode = null;
- /**
- * Allows to overwrite the position used for shadow map rendering which
- * is by default {@link positionWorld}, the vertex position
- * in world space.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.receivedShadowPositionNode = null;
- /**
- * Allows to overwrite the geometry position used for shadow map projection which
- * is by default {@link positionLocal}, the vertex position in local space.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.castShadowPositionNode = null;
- /**
- * This node can be used to influence how an object using this node material
- * receive shadows.
- *
- * ```js
- * const totalShadows = float( 1 ).toVar();
- * material.receivedShadowNode = Fn( ( [ shadow ] ) => {
- * totalShadows.mulAssign( shadow );
- * //return float( 1 ); // bypass received shadows
- * return shadow.mix( color( 0xff0000 ), 1 ); // modify shadow color
- * } );
- *
- * @type {?(Function|FunctionNode<vec4>)}
- * @default null
- */
- this.receivedShadowNode = null;
- /**
- * This node can be used to influence how an object using this node material
- * casts shadows. To apply a color to shadows, you can simply do:
- *
- * ```js
- * material.castShadowNode = vec4( 1, 0, 0, 1 );
- * ```
- *
- * Which can be nice to fake colored shadows of semi-transparent objects. It
- * is also common to use the property with `Fn` function so checks are performed
- * per fragment.
- *
- * ```js
- * materialCustomShadow.castShadowNode = Fn( () => {
- * hash( vertexIndex ).greaterThan( 0.5 ).discard();
- * return materialColor;
- * } )();
- * ```
- *
- * @type {?Node<vec4>}
- * @default null
- */
- this.castShadowNode = null;
- /**
- * This node can be used to define the final output of the material.
- *
- * TODO: Explain the differences to `fragmentNode`.
- *
- * @type {?Node<vec4>}
- * @default null
- */
- this.outputNode = null;
- /**
- * MRT configuration is done on renderer or pass level. This node allows to
- * overwrite what values are written into MRT targets on material level. This
- * can be useful for implementing selective FX features that should only affect
- * specific objects.
- *
- * @type {?MRTNode}
- * @default null
- */
- this.mrtNode = null;
- /**
- * This node property can be used if you need complete freedom in implementing
- * the fragment shader. Assigning a node will replace the built-in material
- * logic used in the fragment stage.
- *
- * @type {?Node<vec4>}
- * @default null
- */
- this.fragmentNode = null;
- /**
- * This node property can be used if you need complete freedom in implementing
- * the vertex shader. Assigning a node will replace the built-in material logic
- * used in the vertex stage.
- *
- * @type {?Node<vec4>}
- * @default null
- */
- this.vertexNode = null;
- // Deprecated properties
- Object.defineProperty( this, 'shadowPositionNode', { // @deprecated, r176
- get: () => {
- return this.receivedShadowPositionNode;
- },
- set: ( value ) => {
- warn( 'NodeMaterial: ".shadowPositionNode" was renamed to ".receivedShadowPositionNode".' );
- this.receivedShadowPositionNode = value;
- }
- } );
- }
- /**
- * Allows to define a custom cache key that influence the material key computation
- * for render objects.
- *
- * @return {string} The custom cache key.
- */
- customProgramCacheKey() {
- return this.type + getCacheKey( this );
- }
- /**
- * Builds this material with the given node builder.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- build( builder ) {
- this.setup( builder );
- }
- /**
- * Setups a node material observer with the given builder.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {NodeMaterialObserver} The node material observer.
- */
- setupObserver( builder ) {
- return new NodeMaterialObserver( builder );
- }
- /**
- * Setups the vertex and fragment stage of this node material.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setup( builder ) {
- builder.context.setupNormal = () => subBuild( this.setupNormal( builder ), 'NORMAL', 'vec3' );
- builder.context.setupPositionView = () => this.setupPositionView( builder );
- builder.context.setupModelViewProjection = () => this.setupModelViewProjection( builder );
- const renderer = builder.renderer;
- const renderTarget = renderer.getRenderTarget();
- // < VERTEX STAGE >
- builder.addStack();
- const mvp = subBuild( this.setupVertex( builder ), 'VERTEX' );
- const vertexNode = this.vertexNode || mvp;
- builder.stack.outputNode = vertexNode;
- this.setupHardwareClipping( builder );
- if ( this.geometryNode !== null ) {
- builder.stack.outputNode = builder.stack.outputNode.bypass( this.geometryNode );
- }
- builder.addFlow( 'vertex', builder.removeStack() );
- // < FRAGMENT STAGE >
- builder.addStack();
- let resultNode;
- const clippingNode = this.setupClipping( builder );
- if ( this.depthWrite === true || this.depthTest === true ) {
- // only write depth if depth buffer is configured
- if ( renderTarget !== null ) {
- if ( renderTarget.depthBuffer === true ) this.setupDepth( builder );
- } else {
- if ( renderer.depth === true ) this.setupDepth( builder );
- }
- }
- if ( this.fragmentNode === null ) {
- this.setupDiffuseColor( builder );
- this.setupVariants( builder );
- const outgoingLightNode = this.setupLighting( builder );
- if ( clippingNode !== null ) builder.stack.add( clippingNode );
- // force unsigned floats - useful for RenderTargets
- const basicOutput = vec4( outgoingLightNode, diffuseColor.a ).max( 0 );
- resultNode = this.setupOutput( builder, basicOutput );
- // OUTPUT NODE
- output.assign( resultNode );
- //
- const isCustomOutput = this.outputNode !== null;
- if ( isCustomOutput ) resultNode = this.outputNode;
- // MRT
- if ( renderTarget !== null ) {
- const mrt = renderer.getMRT();
- const materialMRT = this.mrtNode;
- if ( mrt !== null ) {
- if ( isCustomOutput ) output.assign( resultNode );
- resultNode = mrt;
- if ( materialMRT !== null ) {
- resultNode = mrt.merge( materialMRT );
- }
- } else if ( materialMRT !== null ) {
- resultNode = materialMRT;
- }
- }
- } else {
- let fragmentNode = this.fragmentNode;
- if ( fragmentNode.isOutputStructNode !== true ) {
- fragmentNode = vec4( fragmentNode );
- }
- resultNode = this.setupOutput( builder, fragmentNode );
- }
- builder.stack.outputNode = resultNode;
- builder.addFlow( 'fragment', builder.removeStack() );
- // < OBSERVER >
- builder.observer = this.setupObserver( builder );
- }
- /**
- * Setups the clipping node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {ClippingNode} The clipping node.
- */
- setupClipping( builder ) {
- if ( builder.clippingContext === null ) return null;
- const { unionPlanes, intersectionPlanes } = builder.clippingContext;
- let result = null;
- if ( unionPlanes.length > 0 || intersectionPlanes.length > 0 ) {
- const samples = builder.renderer.currentSamples;
- if ( this.alphaToCoverage && samples > 1 ) {
- // to be added to flow when the color/alpha value has been determined
- result = clippingAlpha();
- } else {
- builder.stack.add( clipping() );
- }
- }
- return result;
- }
- /**
- * Setups the hardware clipping if available on the current device.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setupHardwareClipping( builder ) {
- this.hardwareClipping = false;
- if ( builder.clippingContext === null ) return;
- const candidateCount = builder.clippingContext.unionPlanes.length;
- // 8 planes supported by WebGL ANGLE_clip_cull_distance and WebGPU clip-distances
- if ( candidateCount > 0 && candidateCount <= 8 && builder.isAvailable( 'clipDistance' ) ) {
- builder.stack.add( hardwareClipping() );
- this.hardwareClipping = true;
- }
- return;
- }
- /**
- * Setups the depth of this material.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setupDepth( builder ) {
- const { renderer, camera } = builder;
- // Depth
- let depthNode = this.depthNode;
- if ( depthNode === null ) {
- const mrt = renderer.getMRT();
- if ( mrt && mrt.has( 'depth' ) ) {
- depthNode = mrt.get( 'depth' );
- } else if ( renderer.logarithmicDepthBuffer === true ) {
- if ( camera.isPerspectiveCamera ) {
- depthNode = viewZToLogarithmicDepth( positionView.z, cameraNear, cameraFar );
- } else {
- depthNode = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar );
- }
- }
- }
- if ( depthNode !== null ) {
- depth.assign( depthNode ).toStack();
- }
- }
- /**
- * Setups the position node in view space. This method exists
- * so derived node materials can modify the implementation e.g. sprite materials.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec3>} The position in view space.
- */
- setupPositionView( /*builder*/ ) {
- return modelViewMatrix.mul( positionLocal ).xyz;
- }
- /**
- * Setups the position in clip space.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec4>} The position in view space.
- */
- setupModelViewProjection( /*builder*/ ) {
- return cameraProjectionMatrix.mul( positionView );
- }
- /**
- * Setups the logic for the vertex stage.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec4>} The position in clip space.
- */
- setupVertex( builder ) {
- builder.addStack();
- this.setupPosition( builder );
- builder.context.vertex = builder.removeStack();
- return modelViewProjection;
- }
- /**
- * Setups the computation of the position in local space.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec3>} The position in local space.
- */
- setupPosition( builder ) {
- const { object, geometry } = builder;
- if ( geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color ) {
- morphReference( object ).toStack();
- }
- if ( object.isSkinnedMesh === true ) {
- skinning( object ).toStack();
- }
- if ( this.displacementMap ) {
- const displacementMap = materialReference( 'displacementMap', 'texture' );
- const displacementScale = materialReference( 'displacementScale', 'float' );
- const displacementBias = materialReference( 'displacementBias', 'float' );
- positionLocal.addAssign( normalLocal.normalize().mul( ( displacementMap.x.mul( displacementScale ).add( displacementBias ) ) ) );
- }
- if ( object.isBatchedMesh ) {
- batch( object ).toStack();
- }
- if ( ( object.isInstancedMesh && object.instanceMatrix && object.instanceMatrix.isInstancedBufferAttribute === true ) ) {
- instancedMesh( object ).toStack();
- }
- if ( this.positionNode !== null ) {
- positionLocal.assign( subBuild( this.positionNode, 'POSITION', 'vec3' ) );
- }
- return positionLocal;
- }
- /**
- * Setups the computation of the material's diffuse color.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {BufferGeometry} geometry - The geometry.
- */
- setupDiffuseColor( { object, geometry } ) {
- // MASK
- if ( this.maskNode !== null ) {
- // Discard if the mask is `false`
- bool( this.maskNode ).not().discard();
- }
- // COLOR
- let colorNode = this.colorNode ? vec4( this.colorNode ) : materialColor;
- // VERTEX COLORS
- if ( this.vertexColors === true && geometry.hasAttribute( 'color' ) ) {
- colorNode = colorNode.mul( vertexColor() );
- }
- // INSTANCED COLORS
- if ( object.instanceColor ) {
- const instanceColor = varyingProperty( 'vec3', 'vInstanceColor' );
- colorNode = instanceColor.mul( colorNode );
- }
- if ( object.isBatchedMesh && object._colorsTexture ) {
- const batchColor = varyingProperty( 'vec3', 'vBatchColor' );
- colorNode = batchColor.mul( colorNode );
- }
- // DIFFUSE COLOR
- diffuseColor.assign( colorNode );
- // OPACITY
- const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;
- diffuseColor.a.assign( diffuseColor.a.mul( opacityNode ) );
- // ALPHA TEST
- let alphaTestNode = null;
- if ( this.alphaTestNode !== null || this.alphaTest > 0 ) {
- alphaTestNode = this.alphaTestNode !== null ? float( this.alphaTestNode ) : materialAlphaTest;
- diffuseColor.a.lessThanEqual( alphaTestNode ).discard();
- }
- // ALPHA HASH
- if ( this.alphaHash === true ) {
- diffuseColor.a.lessThan( getAlphaHashThreshold( positionLocal ) ).discard();
- }
- // OPAQUE
- const isOpaque = this.transparent === false && this.blending === NormalBlending && this.alphaToCoverage === false;
- if ( isOpaque ) {
- diffuseColor.a.assign( 1.0 );
- } else if ( alphaTestNode === null ) {
- diffuseColor.a.lessThanEqual( 0 ).discard();
- }
- }
- /**
- * Abstract interface method that can be implemented by derived materials
- * to setup material-specific node variables.
- *
- * @abstract
- * @param {NodeBuilder} builder - The current node builder.
- */
- setupVariants( /*builder*/ ) {
- // Interface function.
- }
- /**
- * Setups the outgoing light node variable
- *
- * @return {Node<vec3>} The outgoing light node.
- */
- setupOutgoingLight() {
- return ( this.lights === true ) ? vec3( 0 ) : diffuseColor.rgb;
- }
- /**
- * Setups the normal node from the material.
- *
- * @return {Node<vec3>} The normal node.
- */
- setupNormal() {
- return this.normalNode ? vec3( this.normalNode ) : materialNormal;
- }
- /**
- * Setups the environment node from the material.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec4>} The environment node.
- */
- setupEnvironment( /*builder*/ ) {
- let node = null;
- if ( this.envNode ) {
- node = this.envNode;
- } else if ( this.envMap ) {
- node = this.envMap.isCubeTexture ? materialReference( 'envMap', 'cubeTexture' ) : materialReference( 'envMap', 'texture' );
- }
- return node;
- }
- /**
- * Setups the light map node from the material.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec3>} The light map node.
- */
- setupLightMap( builder ) {
- let node = null;
- if ( builder.material.lightMap ) {
- node = new IrradianceNode( materialLightMap );
- }
- return node;
- }
- /**
- * Setups the lights node based on the scene, environment and material.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {LightsNode} The lights node.
- */
- setupLights( builder ) {
- const materialLightsNode = [];
- //
- const envNode = this.setupEnvironment( builder );
- if ( envNode && envNode.isLightingNode ) {
- materialLightsNode.push( envNode );
- }
- const lightMapNode = this.setupLightMap( builder );
- if ( lightMapNode && lightMapNode.isLightingNode ) {
- materialLightsNode.push( lightMapNode );
- }
- if ( this.aoNode !== null || builder.material.aoMap ) {
- const aoNode = this.aoNode !== null ? this.aoNode : materialAO;
- materialLightsNode.push( new AONode( aoNode ) );
- }
- let lightsN = this.lightsNode || builder.lightsNode;
- if ( materialLightsNode.length > 0 ) {
- lightsN = builder.renderer.lighting.createNode( [ ...lightsN.getLights(), ...materialLightsNode ] );
- }
- return lightsN;
- }
- /**
- * This method should be implemented by most derived materials
- * since it defines the material's lighting model.
- *
- * @abstract
- * @param {NodeBuilder} builder - The current node builder.
- * @return {LightingModel} The lighting model.
- */
- setupLightingModel( /*builder*/ ) {
- // Interface function.
- }
- /**
- * Setups the outgoing light node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec3>} The outgoing light node.
- */
- setupLighting( builder ) {
- const { material } = builder;
- const { backdropNode, backdropAlphaNode, emissiveNode } = this;
- // OUTGOING LIGHT
- const lights = this.lights === true || this.lightsNode !== null;
- const lightsNode = lights ? this.setupLights( builder ) : null;
- let outgoingLightNode = this.setupOutgoingLight( builder );
- if ( lightsNode && lightsNode.getScope().hasLights ) {
- const lightingModel = this.setupLightingModel( builder ) || null;
- outgoingLightNode = lightingContext( lightsNode, lightingModel, backdropNode, backdropAlphaNode );
- } else if ( backdropNode !== null ) {
- outgoingLightNode = vec3( backdropAlphaNode !== null ? mix( outgoingLightNode, backdropNode, backdropAlphaNode ) : backdropNode );
- }
- // EMISSIVE
- if ( ( emissiveNode && emissiveNode.isNode === true ) || ( material.emissive && material.emissive.isColor === true ) ) {
- emissive.assign( vec3( emissiveNode ? emissiveNode : materialEmissive ) );
- outgoingLightNode = outgoingLightNode.add( emissive );
- }
- return outgoingLightNode;
- }
- /**
- * Setup the fog.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node<vec4>} outputNode - The existing output node.
- * @return {Node<vec4>} The output node.
- */
- setupFog( builder, outputNode ) {
- const fogNode = builder.fogNode;
- if ( fogNode ) {
- output.assign( outputNode );
- outputNode = vec4( fogNode.toVar() );
- }
- return outputNode;
- }
- /**
- * Setups premultiplied alpha.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node<vec4>} outputNode - The existing output node.
- * @return {Node<vec4>} The output node.
- */
- setupPremultipliedAlpha( builder, outputNode ) {
- return premultiplyAlpha( outputNode );
- }
- /**
- * Setups the output node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node<vec4>} outputNode - The existing output node.
- * @return {Node<vec4>} The output node.
- */
- setupOutput( builder, outputNode ) {
- // FOG
- if ( this.fog === true ) {
- outputNode = this.setupFog( builder, outputNode );
- }
- // PREMULTIPLIED ALPHA
- if ( this.premultipliedAlpha === true ) {
- outputNode = this.setupPremultipliedAlpha( builder, outputNode );
- }
- return outputNode;
- }
- /**
- * Most classic material types have a node pendant e.g. for `MeshBasicMaterial`
- * there is `MeshBasicNodeMaterial`. This utility method is intended for
- * defining all material properties of the classic type in the node type.
- *
- * @param {Material} material - The material to copy properties with their values to this node material.
- */
- setDefaultValues( material ) {
- // This approach is to reuse the native refreshUniforms*
- // and turn available the use of features like transmission and environment in core
- for ( const property in material ) {
- const value = material[ property ];
- if ( this[ property ] === undefined ) {
- this[ property ] = value;
- if ( value && value.clone ) this[ property ] = value.clone();
- }
- }
- const descriptors = Object.getOwnPropertyDescriptors( material.constructor.prototype );
- for ( const key in descriptors ) {
- if ( Object.getOwnPropertyDescriptor( this.constructor.prototype, key ) === undefined &&
- descriptors[ key ].get !== undefined ) {
- Object.defineProperty( this.constructor.prototype, key, descriptors[ key ] );
- }
- }
- }
- /**
- * Serializes this material to JSON.
- *
- * @param {?(Object|string)} meta - The meta information for serialization.
- * @return {Object} The serialized node.
- */
- toJSON( meta ) {
- const isRoot = ( meta === undefined || typeof meta === 'string' );
- if ( isRoot ) {
- meta = {
- textures: {},
- images: {},
- nodes: {}
- };
- }
- const data = Material.prototype.toJSON.call( this, meta );
- const nodeChildren = getNodeChildren( this );
- data.inputNodes = {};
- for ( const { property, childNode } of nodeChildren ) {
- data.inputNodes[ property ] = childNode.toJSON( meta ).uuid;
- }
- // TODO: Copied from Object3D.toJSON
- function extractFromCache( cache ) {
- const values = [];
- for ( const key in cache ) {
- const data = cache[ key ];
- delete data.metadata;
- values.push( data );
- }
- return values;
- }
- if ( isRoot ) {
- const textures = extractFromCache( meta.textures );
- const images = extractFromCache( meta.images );
- const nodes = extractFromCache( meta.nodes );
- if ( textures.length > 0 ) data.textures = textures;
- if ( images.length > 0 ) data.images = images;
- if ( nodes.length > 0 ) data.nodes = nodes;
- }
- return data;
- }
- /**
- * Copies the properties of the given node material to this instance.
- *
- * @param {NodeMaterial} source - The material to copy.
- * @return {NodeMaterial} A reference to this node material.
- */
- copy( source ) {
- this.lightsNode = source.lightsNode;
- this.envNode = source.envNode;
- this.colorNode = source.colorNode;
- this.normalNode = source.normalNode;
- this.opacityNode = source.opacityNode;
- this.backdropNode = source.backdropNode;
- this.backdropAlphaNode = source.backdropAlphaNode;
- this.alphaTestNode = source.alphaTestNode;
- this.maskNode = source.maskNode;
- this.positionNode = source.positionNode;
- this.geometryNode = source.geometryNode;
- this.depthNode = source.depthNode;
- this.receivedShadowPositionNode = source.receivedShadowPositionNode;
- this.castShadowPositionNode = source.castShadowPositionNode;
- this.receivedShadowNode = source.receivedShadowNode;
- this.castShadowNode = source.castShadowNode;
- this.outputNode = source.outputNode;
- this.mrtNode = source.mrtNode;
- this.fragmentNode = source.fragmentNode;
- this.vertexNode = source.vertexNode;
- return super.copy( source );
- }
- }
- export default NodeMaterial;
|