|
@@ -1,11 +1,15 @@
|
|
|
import { Material, ShaderMaterial } from 'three';
|
|
|
import { getNodesKeys, getCacheKey } from '../core/NodeUtils.js';
|
|
|
-import ExpressionNode from '../core/ExpressionNode.js';
|
|
|
+import StackNode from '../core/StackNode.js';
|
|
|
+import LightsNode from '../lighting/LightsNode.js';
|
|
|
+import EnvironmentNode from '../lighting/EnvironmentNode.js';
|
|
|
+import AONode from '../lighting/AONode.js';
|
|
|
import {
|
|
|
float, vec3, vec4,
|
|
|
- assign, label, mul, bypass, attribute,
|
|
|
- positionLocal, skinning, instance, modelViewProjection, lightingContext, colorSpace,
|
|
|
- materialAlphaTest, materialColor, materialOpacity, reference, rangeFog, exp2Fog
|
|
|
+ assign, mul, bypass, attribute, context, texture, lessThanEqual, discard,
|
|
|
+ positionLocal, diffuseColor, skinning, instance, modelViewProjection, lightingContext, colorSpace,
|
|
|
+ materialAlphaTest, materialColor, materialOpacity, materialEmissive, materialNormal, transformedNormalView,
|
|
|
+ reference, rangeFog, densityFog
|
|
|
} from '../shadernode/ShaderNodeElements.js';
|
|
|
|
|
|
class NodeMaterial extends ShaderMaterial {
|
|
@@ -19,34 +23,57 @@ class NodeMaterial extends ShaderMaterial {
|
|
|
this.type = this.constructor.name;
|
|
|
|
|
|
this.lights = true;
|
|
|
+ this.normals = true;
|
|
|
+
|
|
|
+ this.lightsNode = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
- build( builder ) {
|
|
|
+ customProgramCacheKey() {
|
|
|
|
|
|
- this.generatePosition( builder );
|
|
|
+ return getCacheKey( this );
|
|
|
|
|
|
- const { lightsNode } = this;
|
|
|
- const { diffuseColorNode } = this.generateDiffuseColor( builder );
|
|
|
+ }
|
|
|
|
|
|
- const outgoingLightNode = this.generateLight( builder, { diffuseColorNode, lightsNode } );
|
|
|
+ build( builder ) {
|
|
|
|
|
|
- this.generateOutput( builder, { diffuseColorNode, outgoingLightNode } );
|
|
|
+ this.construct( builder );
|
|
|
|
|
|
}
|
|
|
|
|
|
- customProgramCacheKey() {
|
|
|
+ construct( builder ) {
|
|
|
|
|
|
- return getCacheKey( this );
|
|
|
+ // < STACKS >
|
|
|
+
|
|
|
+ const vertexStack = new StackNode();
|
|
|
+ const fragmentStack = new StackNode();
|
|
|
+
|
|
|
+ // < VERTEX STAGE >
|
|
|
+
|
|
|
+ vertexStack.outputNode = this.constructPosition( builder, vertexStack );
|
|
|
+
|
|
|
+ // < FRAGMENT STAGE >
|
|
|
+
|
|
|
+ if ( this.normals === true ) this.constructNormal( builder, fragmentStack );
|
|
|
+
|
|
|
+ this.constructDiffuseColor( builder, fragmentStack );
|
|
|
+ this.constructVariants( builder, fragmentStack );
|
|
|
+
|
|
|
+ const outgoingLightNode = this.constructLighting( builder, fragmentStack );
|
|
|
+
|
|
|
+ fragmentStack.outputNode = this.constructOutput( builder, fragmentStack, outgoingLightNode, diffuseColor.a );
|
|
|
+
|
|
|
+ // < FLOW >
|
|
|
+
|
|
|
+ builder.addFlow( 'vertex', vertexStack );
|
|
|
+ builder.addFlow( 'fragment', fragmentStack );
|
|
|
|
|
|
}
|
|
|
|
|
|
- generatePosition( builder ) {
|
|
|
+ constructPosition( builder ) {
|
|
|
|
|
|
const object = builder.object;
|
|
|
|
|
|
- // < VERTEX STAGE >
|
|
|
-
|
|
|
let vertex = positionLocal;
|
|
|
|
|
|
if ( this.positionNode !== null ) {
|
|
@@ -69,16 +96,14 @@ class NodeMaterial extends ShaderMaterial {
|
|
|
|
|
|
builder.context.vertex = vertex;
|
|
|
|
|
|
- builder.addFlow( 'vertex', modelViewProjection() );
|
|
|
+ return modelViewProjection();
|
|
|
|
|
|
}
|
|
|
|
|
|
- generateDiffuseColor( builder ) {
|
|
|
-
|
|
|
- // < FRAGMENT STAGE >
|
|
|
+ constructDiffuseColor( builder, stack ) {
|
|
|
|
|
|
let colorNode = vec4( this.colorNode || materialColor );
|
|
|
- let opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;
|
|
|
+ const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;
|
|
|
|
|
|
// VERTEX COLORS
|
|
|
|
|
@@ -90,13 +115,11 @@ class NodeMaterial extends ShaderMaterial {
|
|
|
|
|
|
// COLOR
|
|
|
|
|
|
- colorNode = builder.addFlow( 'fragment', label( colorNode, 'Color' ) );
|
|
|
- const diffuseColorNode = builder.addFlow( 'fragment', label( colorNode, 'DiffuseColor' ) );
|
|
|
+ stack.assign( diffuseColor, colorNode );
|
|
|
|
|
|
// OPACITY
|
|
|
|
|
|
- opacityNode = builder.addFlow( 'fragment', label( opacityNode, 'OPACITY' ) );
|
|
|
- builder.addFlow( 'fragment', assign( diffuseColorNode.a, mul( diffuseColorNode.a, opacityNode ) ) );
|
|
|
+ stack.assign( diffuseColor.a, diffuseColor.a.mul( opacityNode ) );
|
|
|
|
|
|
// ALPHA TEST
|
|
|
|
|
@@ -104,39 +127,113 @@ class NodeMaterial extends ShaderMaterial {
|
|
|
|
|
|
const alphaTestNode = this.alphaTestNode ? float( this.alphaTestNode ) : materialAlphaTest;
|
|
|
|
|
|
- builder.addFlow( 'fragment', label( alphaTestNode, 'AlphaTest' ) );
|
|
|
+ stack.add( discard( lessThanEqual( diffuseColor.a, alphaTestNode ) ) );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ constructVariants( /*builder*/ ) {
|
|
|
+
|
|
|
+ // Interface function.
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ constructNormal( builder, stack ) {
|
|
|
|
|
|
- // @TODO: remove ExpressionNode here and then possibly remove it completely
|
|
|
- builder.addFlow( 'fragment', new ExpressionNode( 'if ( DiffuseColor.a <= AlphaTest ) { discard; }' ) );
|
|
|
+ // NORMAL VIEW
|
|
|
+
|
|
|
+ const normalNode = this.normalNode ? vec3( this.normalNode ) : materialNormal;
|
|
|
+
|
|
|
+ stack.assign( transformedNormalView, normalNode );
|
|
|
+
|
|
|
+ return normalNode;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ constructLights( builder ) {
|
|
|
+
|
|
|
+ let lightsNode = this.lightsNode || builder.lightsNode;
|
|
|
+
|
|
|
+ const envNode = this.envNode || builder.scene.environmentNode;
|
|
|
+ const materialLightsNode = [];
|
|
|
+
|
|
|
+ if ( envNode ) {
|
|
|
+
|
|
|
+ materialLightsNode.push( new EnvironmentNode( envNode ) );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( builder.material.aoMap ) {
|
|
|
+
|
|
|
+ materialLightsNode.push( new AONode( texture( builder.material.aoMap ) ) );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( materialLightsNode.length > 0 ) {
|
|
|
+
|
|
|
+ lightsNode = new LightsNode( [ ...lightsNode.lightNodes, ...materialLightsNode ] );
|
|
|
|
|
|
}
|
|
|
|
|
|
- return { colorNode, diffuseColorNode };
|
|
|
+ return lightsNode;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ constructLightingModel( /*builder*/ ) {
|
|
|
+
|
|
|
+ // Interface function.
|
|
|
|
|
|
}
|
|
|
|
|
|
- generateLight( builder, { diffuseColorNode, lightingModelNode, lightsNode = builder.lightsNode } ) {
|
|
|
+ constructLighting( builder ) {
|
|
|
|
|
|
- // < ANALYTIC LIGHTS >
|
|
|
+ const { material } = builder;
|
|
|
|
|
|
// OUTGOING LIGHT
|
|
|
|
|
|
- let outgoingLightNode = diffuseColorNode.xyz;
|
|
|
- if ( lightsNode && lightsNode.hasLight !== false ) outgoingLightNode = builder.addFlow( 'fragment', label( lightingContext( lightsNode, lightingModelNode ), 'Light' ) );
|
|
|
+ const lights = ( this.lights === true ) || this.lightsNode !== null;
|
|
|
+
|
|
|
+ const lightsNode = lights ? this.constructLights( builder ) : null;
|
|
|
+ const lightingModelNode = lightsNode ? this.constructLightingModel( builder ) : null;
|
|
|
+
|
|
|
+ let outgoingLightNode = diffuseColor.xyz;
|
|
|
+
|
|
|
+ if ( lightsNode && lightsNode.hasLight !== false ) {
|
|
|
+
|
|
|
+ outgoingLightNode = lightingContext( lightsNode, lightingModelNode );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // EMISSIVE
|
|
|
+
|
|
|
+ if ( ( this.emissiveNode && this.emissiveNode.isNode === true ) || ( material.emissive && material.emissive.isColor === true ) ) {
|
|
|
+
|
|
|
+ outgoingLightNode = outgoingLightNode.add( vec3( this.emissiveNode || materialEmissive ) );
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
return outgoingLightNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
- generateOutput( builder, { diffuseColorNode, outgoingLightNode } ) {
|
|
|
+ constructOutput( builder, stack, outgoingLight, opacity ) {
|
|
|
+
|
|
|
+ const renderer = builder.renderer;
|
|
|
|
|
|
- // OUTPUT
|
|
|
+ // TONE MAPPING
|
|
|
|
|
|
- let outputNode = vec4( outgoingLightNode, diffuseColorNode.a );
|
|
|
+ if ( renderer.toneMappingNode && renderer.toneMappingNode.isNode === true ) {
|
|
|
+
|
|
|
+ outgoingLight = context( renderer.toneMappingNode, { color: outgoingLight } );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ let outputNode = vec4( outgoingLight, opacity );
|
|
|
|
|
|
// ENCODING
|
|
|
|
|
|
- outputNode = colorSpace( outputNode, builder.renderer.outputEncoding );
|
|
|
+ outputNode = colorSpace( outputNode, renderer.outputEncoding );
|
|
|
|
|
|
// FOG
|
|
|
|
|
@@ -148,7 +245,7 @@ class NodeMaterial extends ShaderMaterial {
|
|
|
|
|
|
if ( fog.isFogExp2 ) {
|
|
|
|
|
|
- fogNode = exp2Fog( reference( 'color', 'color', fog ), reference( 'density', 'float', fog ) );
|
|
|
+ fogNode = densityFog( reference( 'color', 'color', fog ), reference( 'density', 'float', fog ) );
|
|
|
|
|
|
} else if ( fog.isFog ) {
|
|
|
|
|
@@ -164,10 +261,6 @@ class NodeMaterial extends ShaderMaterial {
|
|
|
|
|
|
if ( fogNode ) outputNode = vec4( vec3( fogNode.mix( outputNode ) ), outputNode.w );
|
|
|
|
|
|
- // RESULT
|
|
|
-
|
|
|
- builder.addFlow( 'fragment', label( outputNode, 'Output' ) );
|
|
|
-
|
|
|
return outputNode;
|
|
|
|
|
|
}
|