diff --git a/examples/jsm/renderers/nodes/accessors/MaterialNode.js b/examples/jsm/renderers/nodes/accessors/MaterialNode.js index 42a878174cf9e0..fb6cd157a17594 100644 --- a/examples/jsm/renderers/nodes/accessors/MaterialNode.js +++ b/examples/jsm/renderers/nodes/accessors/MaterialNode.js @@ -9,6 +9,8 @@ class MaterialNode extends Node { static OPACITY = 'opacity'; static SPECULAR = 'specular'; static SHININESS = 'shininess'; + static ROUGHNESS = 'roughness'; + static METALNESS = 'metalness'; constructor( scope = MaterialNode.COLOR ) { @@ -21,7 +23,7 @@ class MaterialNode extends Node { getType( builder ) { const scope = this.scope; - const material = builder.getContextParameter( 'material' ); + const material = builder.getContextValue( 'material' ); if ( scope === MaterialNode.COLOR ) { @@ -35,7 +37,7 @@ class MaterialNode extends Node { return 'vec3'; - } else if ( scope === MaterialNode.SHININESS ) { + } else if ( scope === MaterialNode.SHININESS || scope === MaterialNode.ROUGHNESS || scope === MaterialNode.METALNESS ) { return 'float'; @@ -45,7 +47,7 @@ class MaterialNode extends Node { generate( builder, output ) { - const material = builder.getContextParameter( 'material' ); + const material = builder.getContextValue( 'material' ); const scope = this.scope; let node = null; @@ -96,9 +98,11 @@ class MaterialNode extends Node { } - } else if ( scope === MaterialNode.SHININESS ) { + } else { - node = new MaterialReferenceNode( 'shininess', 'float' ); + const type = this.getType( builder ); + + node = new MaterialReferenceNode( scope, type ); } diff --git a/examples/jsm/renderers/nodes/accessors/Object3DNode.js b/examples/jsm/renderers/nodes/accessors/Object3DNode.js index d471a0500033ce..b028ef8142884e 100644 --- a/examples/jsm/renderers/nodes/accessors/Object3DNode.js +++ b/examples/jsm/renderers/nodes/accessors/Object3DNode.js @@ -92,7 +92,7 @@ class Object3DNode extends Node { if ( inputNode === null || inputNode.isMatrix4Node !== true ) { - inputNode = new Matrix4Node( null ); + inputNode = new Matrix4Node( /*null*/ ); } @@ -100,7 +100,7 @@ class Object3DNode extends Node { if ( inputNode === null || inputNode.isMatrix3Node !== true ) { - inputNode = new Matrix3Node( null ); + inputNode = new Matrix3Node( /*null*/ ); } diff --git a/examples/jsm/renderers/nodes/consts/MathConsts.js b/examples/jsm/renderers/nodes/consts/MathConsts.js index b202eb083b39a9..22dc390d92f2ed 100644 --- a/examples/jsm/renderers/nodes/consts/MathConsts.js +++ b/examples/jsm/renderers/nodes/consts/MathConsts.js @@ -2,3 +2,6 @@ import ConstNode from '../core/ConstNode.js'; export const PI = new ConstNode( '3.141592653589793', 'float', 'PI' ); export const RECIPROCAL_PI = new ConstNode( '0.3183098861837907', 'float', 'RECIPROCAL_PI' ); +export const EPSILON = new ConstNode( '1e-6', 'float', 'EPSILON' ); + +export const DEFAULT_SPECULAR_COEFFICIENT = new ConstNode( '0.04', 'float', 'DEFAULT_SPECULAR_COEFFICIENT' ); diff --git a/examples/jsm/renderers/nodes/core/AttributeNode.js b/examples/jsm/renderers/nodes/core/AttributeNode.js index bbf0a2ce0e4474..96c1eae907ab45 100644 --- a/examples/jsm/renderers/nodes/core/AttributeNode.js +++ b/examples/jsm/renderers/nodes/core/AttributeNode.js @@ -1,4 +1,5 @@ import Node from './Node.js'; +import VaryNode from './VaryNode.js'; class AttributeNode extends Node { @@ -43,16 +44,13 @@ class AttributeNode extends Node { if ( nodeVary === undefined ) { - nodeVary = builder.getVaryFromNode( this, attribute.type ); - nodeVary.snippet = attributeName; + nodeVary = new VaryNode( this ); nodeData.nodeVary = nodeVary; } - const varyName = builder.getPropertyName( nodeVary ); - - return builder.format( varyName, attribute.type, output ); + return nodeVary.build( builder, output ); } diff --git a/examples/jsm/renderers/nodes/core/CodeNode.js b/examples/jsm/renderers/nodes/core/CodeNode.js index 3782ec03620ee6..40ddfb1fa961b7 100644 --- a/examples/jsm/renderers/nodes/core/CodeNode.js +++ b/examples/jsm/renderers/nodes/core/CodeNode.js @@ -34,7 +34,7 @@ class CodeNode extends Node { if ( this.useKeywords === true ) { - const contextKeywords = builder.getContextParameter( 'keywords' ); + const contextKeywords = builder.getContextValue( 'keywords' ); if ( contextKeywords !== undefined ) { diff --git a/examples/jsm/renderers/nodes/core/ContextNode.js b/examples/jsm/renderers/nodes/core/ContextNode.js index 5d15180567f291..c9c7e8f62c255b 100644 --- a/examples/jsm/renderers/nodes/core/ContextNode.js +++ b/examples/jsm/renderers/nodes/core/ContextNode.js @@ -2,29 +2,29 @@ import Node from './Node.js'; class ContextNode extends Node { - constructor( node, parameters = {} ) { + constructor( node, type, context = {} ) { - super(); + super( type ); this.node = node; - this.parameters = parameters; + this.context = context; Object.defineProperty( this, 'isContextNode', { value: true } ); } - setParameter( name, value ) { + setContextValue( name, value ) { - this.parameters[ name ] = value; + this.context[ name ] = value; return this; } - getParameter( name ) { + getContextValue( name ) { - return this.parameters[ name ]; + return this.context[ name ]; } @@ -36,9 +36,11 @@ class ContextNode extends Node { generate( builder, output ) { + const type = this.getType( builder ); + const previousContext = builder.getContext(); - builder.setContext( Object.assign( {}, builder.context, this.parameters ) ); + builder.setContext( Object.assign( {}, builder.context, this.context ) ); const snippet = this.node.build( builder, output ); diff --git a/examples/jsm/renderers/nodes/core/ExpressionNode.js b/examples/jsm/renderers/nodes/core/ExpressionNode.js new file mode 100644 index 00000000000000..b0f1f5fcaba3dc --- /dev/null +++ b/examples/jsm/renderers/nodes/core/ExpressionNode.js @@ -0,0 +1,24 @@ +import Node from './Node.js'; + +class ExpressionNode extends Node { + + constructor( snipped = '', type = null ) { + + super( type ); + + this.snipped = snipped; + + } + + generate( builder, output ) { + + const type = this.getType( builder ); + const snipped = this.snipped; + + return builder.format( `( ${ snipped } )`, type, output ); + + } + +} + +export default ExpressionNode; diff --git a/examples/jsm/renderers/nodes/core/FunctionCallNode.js b/examples/jsm/renderers/nodes/core/FunctionCallNode.js index b35ead023ee435..838a562777acc4 100644 --- a/examples/jsm/renderers/nodes/core/FunctionCallNode.js +++ b/examples/jsm/renderers/nodes/core/FunctionCallNode.js @@ -1,6 +1,6 @@ -import Node from './Node.js'; +import TempNode from './TempNode.js'; -class FunctionCallNode extends Node { +class FunctionCallNode extends TempNode { constructor( functionNode = null, parameters = {} ) { diff --git a/examples/jsm/renderers/nodes/core/Node.js b/examples/jsm/renderers/nodes/core/Node.js index bef3f9d0b1f4de..c997b06a5a448f 100644 --- a/examples/jsm/renderers/nodes/core/Node.js +++ b/examples/jsm/renderers/nodes/core/Node.js @@ -22,29 +22,21 @@ class Node { } - update( /*frame*/ ) { + getTypeLength( builder ) { - console.warn( 'Abstract function.' ); + return builder.getTypeLength( this.getType( builder ) ); } - generate( /*builder, output*/ ) { + update( /*frame*/ ) { console.warn( 'Abstract function.' ); } - buildStage( builder, shaderStage, output = null ) { - - const oldShaderStage = builder.shaderStage; - - builder.shaderStage = shaderStage; - - const snippet = this.build( builder, output ); - - builder.shaderStage = oldShaderStage; + generate( /*builder, output*/ ) { - return snippet; + console.warn( 'Abstract function.' ); } diff --git a/examples/jsm/renderers/nodes/core/NodeBuilder.js b/examples/jsm/renderers/nodes/core/NodeBuilder.js index c67afb0b0fad48..52090779fbb183 100644 --- a/examples/jsm/renderers/nodes/core/NodeBuilder.js +++ b/examples/jsm/renderers/nodes/core/NodeBuilder.js @@ -21,11 +21,11 @@ class NodeBuilder { this.slots = { vertex: [], fragment: [] }; this.defines = { vertex: {}, fragment: {} }; - this.uniforms = { vertex: [], fragment: [] }; - this.nodeCodes = { vertex: [], fragment: [] }; - this.vars = { vertex: [], fragment: [] }; + this.uniforms = { vertex: [], fragment: [], index: 0 }; + this.codes = { vertex: [], fragment: [] }; this.attributes = []; this.varys = []; + this.vars = { vertex: [], fragment: [] }; this.flow = { code: '' }; this.context = { @@ -36,6 +36,7 @@ class NodeBuilder { this.nodesData = new WeakMap(); this.shaderStage = null; + this.slot = null; } @@ -81,13 +82,25 @@ class NodeBuilder { } - getContextParameter( name ) { + getContextValue( name ) { return this.context[ name ]; } - getTexture( /* textureProperty, uvSnippet */ ) { + getTexture( /* textureProperty, uvSnippet, biasSnippet = null */ ) { + + console.warn( 'Abstract function.' ); + + } + + getCubeTexture( /* textureProperty, uvSnippet, biasSnippet = null */ ) { + + console.warn( 'Abstract function.' ); + + } + + getPMREM( texture ) { console.warn( 'Abstract function.' ); @@ -155,6 +168,28 @@ class NodeBuilder { } + getTextureEncodingFromMap( map ) { + + let encoding; + + if ( map && map.isTexture ) { + + encoding = map.encoding; + + } else if ( map && map.isWebGLRenderTarget ) { + + encoding = map.texture.encoding; + + } else { + + encoding = LinearEncoding; + + } + + return encoding; + + } + getVectorType( type ) { if ( type === 'color' ) return 'vec3'; @@ -177,18 +212,24 @@ class NodeBuilder { getTypeLength( type ) { - type = this.getVectorType( type ); + const vecType = this.getVectorType( type ); - if ( type === 'float' ) return 1; - if ( type === 'vec2' ) return 2; - if ( type === 'vec3' ) return 3; - if ( type === 'vec4' ) return 4; + if ( vecType === 'float' ) return 1; + if ( vecType === 'vec2' ) return 2; + if ( vecType === 'vec3' ) return 3; + if ( vecType === 'vec4' ) return 4; return 0; } - getDataFromNode( node, shaderStage = null ) { + getVectorFromMatrix( type ) { + + return 'vec' + type.substr( 3 ); + + } + + getDataFromNode( node, shaderStage = this.shaderStage ) { let nodeData = this.nodesData.get( node ); @@ -200,7 +241,7 @@ class NodeBuilder { } - return shaderStage ? nodeData[ shaderStage ] : nodeData; + return shaderStage !== null ? nodeData[ shaderStage ] : nodeData; } @@ -212,12 +253,11 @@ class NodeBuilder { if ( nodeUniform === undefined ) { - const uniforms = this.uniforms[ shaderStage ]; - const index = uniforms.length; + const index = this.uniforms.index ++; nodeUniform = new NodeUniform( 'nodeUniform' + index, type, node ); - uniforms.push( nodeUniform ); + this.uniforms[ shaderStage ].push( nodeUniform ); nodeData.uniform = nodeUniform; @@ -252,7 +292,7 @@ class NodeBuilder { getVaryFromNode( node, type ) { - const nodeData = this.getDataFromNode( node ); + const nodeData = this.getDataFromNode( node, null ); let nodeVary = nodeData.vary; @@ -281,12 +321,12 @@ class NodeBuilder { if ( nodeCode === undefined ) { - const nodeCodes = this.nodeCodes[ shaderStage ]; - const index = nodeCodes.length; + const codes = this.codes[ shaderStage ]; + const index = codes.length; nodeCode = new NodeCode( 'nodeCode' + index, type ); - nodeCodes.push( nodeCode ); + codes.push( nodeCode ); nodeData.code = nodeCode; @@ -296,31 +336,37 @@ class NodeBuilder { } - /* - analyzeNode( node ) { + addFlowCode( code ) { + + if ( ! /;\s*$/.test( code ) ) { + + code += ';'; + + } + this.flow.code += code + ' '; } - */ - addFlowCode( code ) { + flowSlot( slot, shaderStage = this.shaderStage ) { - if ( ! /;\s*$/.test( code ) ) { + this.slot = slot; - code += '; '; + const flowData = this.flowNode( slot.node, slot.output ); - } + this.define( shaderStage, `NODE_CODE_${slot.name}`, flowData.code ); + this.define( shaderStage, `NODE_${slot.name}`, flowData.result ); - this.flow.code += code; + this.slot = null; } - flowNode( node, output ) { + flowNode( node, output = null ) { const previousFlow = this.flow; const flow = { - code: previousFlow.code, + code: '', }; this.flow = flow; @@ -333,9 +379,33 @@ class NodeBuilder { } - _buildDefines( shader ) { + flowNodeFromShaderStage( shaderStage, node, output = null, propertyName = null ) { - const defines = this.defines[ shader ]; + const previousShaderStage = this.shaderStage; + + this.setShaderStage( shaderStage ); + + const flowData = this.flowNode( node, output ); + + if ( propertyName !== null ) { + + flowData.code += `${propertyName} = ${flowData.result}; `; + + } + + const shaderStageCode = this.defines[ shaderStage ][ 'NODE_CODE' ] + flowData.code; + + this.define( shaderStage, 'NODE_CODE', shaderStageCode ); + + this.setShaderStage( previousShaderStage ); + + return flowData; + + } + + getDefines( shaderStage ) { + + const defines = this.defines[ shaderStage ]; let code = ''; @@ -349,55 +419,65 @@ class NodeBuilder { } - getAttributesBodySnippet( /*shaderStage*/ ) { + getAttributes( shaderStage ) { - console.warn( 'Abstract function.' ); + let snippet = ''; - } + if ( shaderStage === 'vertex' ) { - getAttributesHeaderSnippet( /*shaderStage*/ ) { + const attributes = this.attributes; - console.warn( 'Abstract function.' ); + for ( let index = 0; index < attributes.length; index ++ ) { - } + const attribute = attributes[ index ]; - getVarysHeaderSnippet( /*shaderStage*/ ) { + snippet += `layout(location = ${index}) in ${attribute.type} ${attribute.name}; `; - console.warn( 'Abstract function.' ); + } + + } + + return snippet; } - getVarysBodySnippet( /*shaderStage*/ ) { + getVarys( /*shaderStage*/ ) { console.warn( 'Abstract function.' ); } - getVarsHeaderSnippet( /*shaderStage*/ ) { + getVars( shaderStage ) { - console.warn( 'Abstract function.' ); + let snippet = ''; - } + const vars = this.vars[ shaderStage ]; - getVarsBodySnippet( /*shaderStage*/ ) { + for ( let index = 0; index < vars.length; index ++ ) { - console.warn( 'Abstract function.' ); + const variable = vars[ index ]; + + snippet += `${variable.type} ${variable.name}; `; + + } + + return snippet; } - getUniformsHeaderSnippet( /*shaderStage*/ ) { + getUniforms( /*shaderStage*/ ) { console.warn( 'Abstract function.' ); } - getUniformsHeaderCodes( shaderStage ) { + getCodes( shaderStage ) { - const nodeCodes = this.nodeCodes[ shaderStage ]; + const codes = this.codes[ shaderStage ]; let code = ''; - for ( const nodeCode of nodeCodes ) { + for ( const nodeCode of codes ) { code += nodeCode.code + '\n'; @@ -413,6 +493,18 @@ class NodeBuilder { } + getShaderStage() { + + return this.shaderStage; + + } + + setShaderStage( shaderStage ) { + + this.shaderStage = shaderStage; + + } + build() { const shaderStages = [ 'vertex', 'fragment' ]; @@ -420,38 +512,56 @@ class NodeBuilder { for ( const shaderStage of shaderStages ) { - this.shaderStage = shaderStage; + this.setShaderStage( shaderStage ); + + this.define( shaderStage, 'NODE_CODE', '' ); const slots = this.slots[ shaderStage ]; for ( const slot of slots ) { - const flowData = this.flowNode( slot.node, slot.output ); - - this.define( shaderStage, `NODE_CODE_${slot.name}`, flowData.code ); - this.define( shaderStage, `NODE_${slot.name}`, flowData.result ); + this.flowSlot( slot, shaderStage ); } } - this.shaderStage = null; + this.setShaderStage( null ); for ( const shaderStage of shaderStages ) { - this.define( shaderStage, 'NODE_HEADER_UNIFORMS', this.getUniformsHeaderSnippet( shaderStage ) ); - this.define( shaderStage, 'NODE_HEADER_ATTRIBUTES', this.getAttributesHeaderSnippet( shaderStage ) ); - this.define( shaderStage, 'NODE_HEADER_VARYS', this.getVarysHeaderSnippet( shaderStage ) ); + const defines = this.getDefines( shaderStage ); + const uniforms = this.getUniforms( shaderStage ); + const attributes = this.getAttributes( shaderStage ); + const varys = this.getVarys( shaderStage ); + const vars = this.getVars( shaderStage ); + const codes = this.getCodes( shaderStage ); + + shaderData[ shaderStage ] = ` + // + + #define NODE_MATERIAL + + // defines + ${defines} + + // uniforms + ${uniforms} + + // attributes + ${attributes} - this.define( shaderStage, 'NODE_BODY_VARYS', this.getVarysBodySnippet( shaderStage ) ); - this.define( shaderStage, 'NODE_BODY_VARS', this.getVarsBodySnippet( shaderStage ) ); + // varys + ${varys} - let headerCode = ''; + // vars + ${vars} - headerCode += this.getVarsHeaderSnippet( shaderStage ) + '\n'; - headerCode += this.getUniformsHeaderCodes( shaderStage ); + // codes + ${codes} - shaderData[ shaderStage ] = this._buildDefines( shaderStage ) + '\n' + headerCode; + // + `; } diff --git a/examples/jsm/renderers/nodes/core/NodeKeywords.js b/examples/jsm/renderers/nodes/core/NodeKeywords.js index d83d2e123aee45..6eca2a3f7ba4dc 100644 --- a/examples/jsm/renderers/nodes/core/NodeKeywords.js +++ b/examples/jsm/renderers/nodes/core/NodeKeywords.js @@ -3,13 +3,14 @@ import PropertyNode from './PropertyNode.js'; import PositionNode from '../accessors/PositionNode.js'; import NormalNode from '../accessors/NormalNode.js'; -import { PI, RECIPROCAL_PI } from '../consts/MathConsts.js'; +import { PI, RECIPROCAL_PI, EPSILON } from '../consts/MathConsts.js'; import { saturateMacro, whiteComplementMacro } from '../functions/MathFunctions.js'; class NodeKeywords { static PI = 'PI'; static RECIPROCAL_PI = 'RECIPROCAL_PI'; + static EPSILON = 'EPSILON'; static Saturate = 'saturate'; static WhiteComplement = 'whiteComplement'; @@ -31,6 +32,11 @@ class NodeKeywords { static MaterialDiffuseColor = 'MaterialDiffuseColor'; + // STANDARD + static MaterialRoughness = 'MaterialRoughness'; + static MaterialMetalness = 'MaterialMetalness'; + + // PHONG static MaterialSpecularShininess = 'MaterialSpecularShininess'; static MaterialSpecularColor = 'MaterialSpecularColor'; @@ -40,6 +46,7 @@ class NodeKeywords { // consts NodeKeywords.PI, NodeKeywords.RECIPROCAL_PI, + NodeKeywords.EPSILON, // variadic macros NodeKeywords.Saturate, NodeKeywords.WhiteComplement, @@ -53,6 +60,8 @@ class NodeKeywords { NodeKeywords.NormalView, // vars -> float NodeKeywords.MaterialSpecularShininess, + NodeKeywords.MaterialRoughness, + NodeKeywords.MaterialMetalness, // vars -> vec3 NodeKeywords.Irradiance, NodeKeywords.ReflectedLightIndirectDiffuse, @@ -88,6 +97,12 @@ class NodeKeywords { break; + case NodeKeywords.EPSILON: + + node = EPSILON; + + break; + case NodeKeywords.Saturate: node = saturateMacro; @@ -144,6 +159,8 @@ class NodeKeywords { // floats properties case NodeKeywords.MaterialSpecularShininess: + case NodeKeywords.MaterialRoughness: + case NodeKeywords.MaterialMetalness: node = new PropertyNode( name, 'float' ); diff --git a/examples/jsm/renderers/nodes/core/NodeVar.js b/examples/jsm/renderers/nodes/core/NodeVar.js index ed791a79e920f5..d8a02531898942 100644 --- a/examples/jsm/renderers/nodes/core/NodeVar.js +++ b/examples/jsm/renderers/nodes/core/NodeVar.js @@ -1,10 +1,9 @@ class NodeVar { - constructor( name, type, snippet = '' ) { + constructor( name, type ) { this.name = name; this.type = type; - this.snippet = snippet; Object.defineProperty( this, 'isNodeVar', { value: true } ); diff --git a/examples/jsm/renderers/nodes/core/NodeVary.js b/examples/jsm/renderers/nodes/core/NodeVary.js index 7aa0446c3b26da..23e5e42a89ea25 100644 --- a/examples/jsm/renderers/nodes/core/NodeVary.js +++ b/examples/jsm/renderers/nodes/core/NodeVary.js @@ -1,10 +1,9 @@ class NodeVary { - constructor( name, type, snippet = '' ) { + constructor( name, type ) { this.name = name; this.type = type; - this.snippet = snippet; } diff --git a/examples/jsm/renderers/nodes/core/StructNode.js b/examples/jsm/renderers/nodes/core/StructNode.js new file mode 100644 index 00000000000000..fafad774cdb71c --- /dev/null +++ b/examples/jsm/renderers/nodes/core/StructNode.js @@ -0,0 +1,82 @@ +import CodeNode from './CodeNode.js'; +import StructVarNode from './StructVarNode.js'; + +class StructNode extends CodeNode { + + constructor( inputs = {}, name = '' ) { + + super(); + + this.inputs = inputs; + this.name = name; + + } + + getType( builder ) { + + if ( this.name !== '' ) { + + return this.name; + + } else { + + const codeNode = builder.getCodeFromNode( this, 'code' ); + + return codeNode.name; + + } + + } + + create( inputs = {} ) { + + return new StructVarNode( this, inputs ); + + } + + generate( builder, output ) { + + const type = this.getType( builder ); + const inputs = this.inputs; + + const shaderStage = builder.getShaderStage(); + + let code = `struct ${type} {\n`; + + for ( const inputName in inputs ) { + + const inputType = inputs[ inputName ]; + + code += `\t${inputType} ${inputName};\n`; + + } + + code += `};`; + + this.code = code; + + super.generate( builder, output ); + + if ( output === 'var' ) { + + const nodeData = builder.getDataFromNode( this ); + + if ( nodeData.index === undefined ) { + + nodeData.index = 0; + + } + + return `structVar${nodeData.index ++}`; + + } else { + + return code; + + } + + } + +} + +export default StructNode; diff --git a/examples/jsm/renderers/nodes/core/StructVarNode.js b/examples/jsm/renderers/nodes/core/StructVarNode.js new file mode 100644 index 00000000000000..eef70271a18df8 --- /dev/null +++ b/examples/jsm/renderers/nodes/core/StructVarNode.js @@ -0,0 +1,75 @@ +import Node from './Node.js'; +import FloatNode from '../inputs/FloatNode.js'; + +const zeroValue = new FloatNode( 0 ).setConst( true ); + +class StructVarNode extends Node { + + constructor( struct, inputs = {} ) { + + super(); + + this.struct = struct; + this.inputs = inputs; + + } + + getType( builder ) { + + return this.struct.getType( builder ); + + } + + generate( builder, output ) { + + const type = this.getType( builder ); + + const struct = this.struct; + + const inputs = this.inputs; + const structInputs = this.struct.inputs; + + const nodeData = builder.getDataFromNode( this ); + + let property = nodeData.property; + + if ( property === undefined ) { + + property = struct.build( builder, 'var' ); + + const inputsSnippets = []; + + for ( const inputName in structInputs ) { + + const inputType = structInputs[ inputName ]; + const input = inputs[ inputName ]; + + let inputSnippet = null; + + if ( input !== undefined ) { + + inputSnippet = input.build( builder, inputType ); + + } else { + + inputSnippet = zeroValue.build( builder, inputType ); + + } + + inputsSnippets.push( inputSnippet ); + + } + + builder.addFlowCode( `${type} ${property} = ${type}( ${inputsSnippets.join( ', ' )} )` ); + + nodeData.property = property; + + } + + return builder.format( property, type, output ); + + } + +} + +export default StructVarNode; diff --git a/examples/jsm/renderers/nodes/core/TempNode.js b/examples/jsm/renderers/nodes/core/TempNode.js new file mode 100644 index 00000000000000..5cad6eca98fb96 --- /dev/null +++ b/examples/jsm/renderers/nodes/core/TempNode.js @@ -0,0 +1,36 @@ +import Node from './Node.js'; + +class TempNode extends Node { + + constructor( type ) { + + super( type ); + + } + + build( builder, output ) { + + const type = builder.getVectorType( this.getType( builder ) ); + + if ( type !== 'void' ) { + + const nodeVar = builder.getVarFromNode( this, type ); + const propertyName = builder.getPropertyName( nodeVar ); + + const snippet = super.build( builder, type ); + + builder.addFlowCode( `${propertyName} = ${snippet}` ); + + return builder.format( propertyName, type, output ); + + } else { + + return super.build( builder, type ); + + } + + } + +} + +export default TempNode; diff --git a/examples/jsm/renderers/nodes/core/VarNode.js b/examples/jsm/renderers/nodes/core/VarNode.js index 6ad515d6cc1e5d..9da1d069d8422d 100644 --- a/examples/jsm/renderers/nodes/core/VarNode.js +++ b/examples/jsm/renderers/nodes/core/VarNode.js @@ -2,9 +2,9 @@ import Node from './Node.js'; class VarNode extends Node { - constructor( value, name = '' ) { + constructor( value, name = '', type = null ) { - super(); + super( type ); this.value = value; this.name = name; @@ -13,26 +13,29 @@ class VarNode extends Node { getType( builder ) { - return this.value.getType( builder ); + return this.type || this.value.getType( builder ); } generate( builder, output ) { - const snippet = this.value.build( builder ); + const type = builder.getVectorType( this.type || this.getType( builder ) ); + const name = this.name; + const value = this.value; - const type = this.getType( builder ); + const nodeVar = builder.getVarFromNode( this, type ); - const nodeVary = builder.getVarFromNode( this, type ); - nodeVary.snippet = snippet; + const snippet = value.build( builder, type ); - if ( this.name !== '' ) { + if ( name !== '' ) { - nodeVary.name = this.name; + nodeVar.name = name; } - const propertyName = builder.getPropertyName( nodeVary ); + const propertyName = builder.getPropertyName( nodeVar ); + + builder.addFlowCode( `${propertyName} = ${snippet}` ); return builder.format( propertyName, type, output ); diff --git a/examples/jsm/renderers/nodes/core/VaryNode.js b/examples/jsm/renderers/nodes/core/VaryNode.js index 266fd07e7e333b..4afbb30f19cdc5 100644 --- a/examples/jsm/renderers/nodes/core/VaryNode.js +++ b/examples/jsm/renderers/nodes/core/VaryNode.js @@ -23,14 +23,12 @@ class VaryNode extends Node { const type = this.getType( builder ); - // force nodeVary.snippet work in vertex stage - const snippet = this.value.buildStage( builder, NodeShaderStage.Vertex, type ); - const nodeVary = builder.getVaryFromNode( this, type ); - nodeVary.snippet = snippet; - const propertyName = builder.getPropertyName( nodeVary ); + // force nodeVary.snippet work in vertex stage + const flowData = builder.flowNodeFromShaderStage( NodeShaderStage.Vertex, this.value, type, propertyName ); + return builder.format( propertyName, type, output ); } diff --git a/examples/jsm/renderers/nodes/display/ColorSpaceNode.js b/examples/jsm/renderers/nodes/display/ColorSpaceNode.js new file mode 100644 index 00000000000000..ef6689f19093ba --- /dev/null +++ b/examples/jsm/renderers/nodes/display/ColorSpaceNode.js @@ -0,0 +1,124 @@ +import TempNode from '../core/Node.js'; +import CodeNode from '../core/CodeNode.js'; +import * as EncodingFunctions from '../functions/EncodingFunctions.js'; + +import { LinearEncoding, sRGBEncoding, RGBEEncoding, RGBM7Encoding, RGBM16Encoding, + RGBDEncoding, GammaEncoding, LogLuvEncoding } from 'three'; + +function getEncodingComponents ( encoding ) { + + switch ( encoding ) { + + case LinearEncoding: + return [ 'Linear' ]; + case sRGBEncoding: + return [ 'sRGB' ]; + case RGBEEncoding: + return [ 'RGBE' ]; + case RGBM7Encoding: + return [ 'RGBM', new FloatNode( 7.0 ).setConst( true ) ]; + case RGBM16Encoding: + return [ 'RGBM', new FloatNode( 16.0 ).setConst( true ) ]; + case RGBDEncoding: + return [ 'RGBD', new FloatNode( 256.0 ).setConst( true ) ]; + case GammaEncoding: + return [ 'Gamma', new CodeNode( 'float( GAMMA_FACTOR )' ) ]; + case LogLuvEncoding: + return [ 'LogLuv' ]; + + } + +} + +class ColorSpaceNode extends TempNode { + + static LINEAR_TO_LINEAR = 'LinearToLinear'; + + static GAMMA_TO_LINEAR = 'GammaToLinear'; + static LINEAR_TO_GAMMA = 'LinearToGamma'; + + static SRGB_TO_LINEAR = 'sRGBToLinear'; + static LINEAR_TO_SRGB = 'LinearTosRGB'; + + static RGBE_TO_LINEAR = 'RGBEToLinear'; + static LINEAR_TO_RGBE = 'LinearToRGBE'; + + static RGBM_TO_LINEAR = 'RGBMToLinear'; + static LINEAR_TO_RGBM = 'LinearToRGBM'; + + static RGBD_TO_LINEAR = 'RGBDToLinear'; + static LINEAR_TO_RGBD = 'LinearToRGBD'; + + static LINEAR_TO_LOG_LUV = 'LinearToLogLuv'; + static LOG_LUV_TO_LINEAR = 'LogLuvToLinear'; + + constructor( method, input ) { + + super( 'vec4' ); + + this.method = method; + + this.input = input; + this.factor = null; + + } + + fromEncoding( encoding ) { + + const components = getEncodingComponents( encoding ); + + this.method = 'LinearTo' + components[ 0 ]; + this.factor = components[ 1 ]; + + return this; + + } + + fromDecoding( encoding ) { + + const components = getEncodingComponents( encoding ); + + this.method = components[ 0 ] + 'ToLinear'; + this.factor = components[ 1 ]; + + return this; + + } + + generate( builder, output ) { + + const method = this.method; + const input = this.input; + + if ( method !== ColorSpaceNode.LINEAR_TO_LINEAR ) { + + const nodeData = builder.getDataFromNode( this ); + + let encodingFunctionCallNode = nodeData.encodingFunctionCallNode; + + if (encodingFunctionCallNode === undefined) { + + const encodingFunctionNode = EncodingFunctions[ method ]; + + encodingFunctionCallNode = encodingFunctionNode.call( { + value: input, + factor: this.factor + } ); + + nodeData.encodingFunctionCallNode = encodingFunctionCallNode; + + } + + return encodingFunctionCallNode.build( builder, output ); + + } else { + + return input.build( builder, output ); + + } + + } + +} + +export default ColorSpaceNode; diff --git a/examples/jsm/renderers/nodes/display/NormalMapNode.js b/examples/jsm/renderers/nodes/display/NormalMapNode.js new file mode 100644 index 00000000000000..a87c4f9c088643 --- /dev/null +++ b/examples/jsm/renderers/nodes/display/NormalMapNode.js @@ -0,0 +1,99 @@ +import PositionNode from '../accessors/PositionNode.js'; +import NormalNode from '../accessors/NormalNode.js'; +import UVNode from '../accessors/UVNode.js'; +import MathNode from '../math/MathNode.js'; +import OperatorNode from '../math/OperatorNode.js'; +import FloatNode from '../inputs/FloatNode.js'; +import TempNode from '../core/TempNode.js'; +import FunctionNode from '../core/FunctionNode.js'; +import ModelNode from '../accessors/ModelNode.js'; + +import { TangentSpaceNormalMap, ObjectSpaceNormalMap } from 'three'; + +// Normal Mapping Without Precomputed Tangents +// http://www.thetenthplanet.de/archives/1180 + +export const perturbNormal2Arb = new FunctionNode( ` +vec3 ( vec3 eye_pos, vec3 surf_norm, vec3 mapN, float faceDirection, const in vec2 uv ) { + + // Workaround for Adreno 3XX dFd*( vec3 ) bug. See #9988 + + vec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) ); + vec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) ); + vec2 st0 = dFdx( uv.st ); + vec2 st1 = dFdy( uv.st ); + + vec3 N = surf_norm; // normalized + + vec3 q1perp = cross( q1, N ); + vec3 q0perp = cross( N, q0 ); + + vec3 T = q1perp * st0.x + q0perp * st1.x; + vec3 B = q1perp * st0.y + q0perp * st1.y; + + float det = max( dot( T, T ), dot( B, B ) ); + float scale = ( det == 0.0 ) ? 0.0 : faceDirection * inversesqrt( det ); + + return normalize( T * ( mapN.x * scale ) + B * ( mapN.y * scale ) + N * mapN.z ); + +}` ); + +class NormalMapNode extends TempNode { + + constructor( value ) { + + super( 'vec3' ); + + this.value = value; + + this.normalMapType = TangentSpaceNormalMap; + + } + + generate( builder, output ) { + + const type = this.getType( builder ); + const normalMapType = this.normalMapType; + + const nodeData = builder.getDataFromNode( this ); + + const normalOP = new OperatorNode( '*', this.value, new FloatNode( 2.0 ).setConst( true ) ); + const normalMap = new OperatorNode( '-', normalOP, new FloatNode( 1.0 ).setConst( true ) ); + + if ( normalMapType === ObjectSpaceNormalMap ) { + + const vertexNormalNode = new OperatorNode( '*', new ModelNode( ModelNode.NORMAL_MATRIX ), normalMap ); + + const normal = new MathNode( MathNode.NORMALIZE, vertexNormalNode ); + + return normal.build( builder, output ); + + } else if ( normalMapType === TangentSpaceNormalMap ) { + + let perturbNormal2ArbCall = nodeData.perturbNormal2ArbCall; + + if (perturbNormal2ArbCall === undefined) { + + perturbNormal2ArbCall = perturbNormal2Arb.call( { + eye_pos: new PositionNode( PositionNode.VIEW ), + surf_norm: new NormalNode( NormalNode.VIEW ), + mapN: normalMap, + faceDirection: new FloatNode( 1.0 ).setConst( true ), + uv: new UVNode() + } ); + + nodeData.perturbNormal2ArbCall = perturbNormal2ArbCall; + + } + + const snippet = perturbNormal2ArbCall.build( builder, output ); + + return builder.format( snippet, type, output ); + + } + + } + +} + +export default NormalMapNode; diff --git a/examples/jsm/renderers/nodes/functions/BSDFs.js b/examples/jsm/renderers/nodes/functions/BSDFs.js index d98f01565ecc2b..b0c8dc748aa013 100644 --- a/examples/jsm/renderers/nodes/functions/BSDFs.js +++ b/examples/jsm/renderers/nodes/functions/BSDFs.js @@ -1,7 +1,8 @@ import FunctionNode from '../core/FunctionNode.js'; +import { pow2 } from './MathFunctions.js'; export const F_Schlick = new FunctionNode( ` -vec3 F_Schlick( const in vec3 f0, const in vec3 f90, const in float dotVH ) { +vec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) { // Original approximation by Christophe Schlick '94 // float fresnel = pow( 1.0 - dotVH, 5.0 ); @@ -24,13 +25,6 @@ float G_BlinnPhong_Implicit() { }` ); // validated -export const D_BlinnPhong = new FunctionNode( ` -float D_BlinnPhong( const in float shininess, const in float dotNH ) { - - return RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess ); - -}` ); // validated - export const BRDF_Lambert = new FunctionNode( ` vec3 BRDF_Lambert( const in vec3 diffuseColor ) { @@ -38,24 +32,6 @@ vec3 BRDF_Lambert( const in vec3 diffuseColor ) { }` ); // validated -export const BRDF_BlinnPhong = new FunctionNode( ` -vec3 BRDF_BlinnPhong( vec3 lightDirection, vec3 specularColor, float shininess ) { - - vec3 halfDir = normalize( lightDirection + PositionViewDirection ); - - float dotNH = saturate( dot( NormalView, halfDir ) ); - float dotVH = saturate( dot( PositionViewDirection, halfDir ) ); - - vec3 F = F_Schlick( specularColor, vec3( 1.0 ), dotVH ); - - float G = G_BlinnPhong_Implicit( /* dotNL, dotNV */ ); - - float D = D_BlinnPhong( shininess, dotNH ); - - return F * ( G * D ); - -}` ).setIncludes( [ F_Schlick, G_BlinnPhong_Implicit, D_BlinnPhong ] ); // validated - export const punctualLightIntensityToIrradianceFactor = new FunctionNode( ` float punctualLightIntensityToIrradianceFactor( float lightDistance, float cutoffDistance, float decayExponent ) { @@ -88,23 +64,152 @@ float punctualLightIntensityToIrradianceFactor( float lightDistance, float cutof #endif -}` ); +}` ).setIncludes( [ pow2 ] ); + +// +// BLINN PHONG +// + +export const D_BlinnPhong = new FunctionNode( ` +float D_BlinnPhong( const in float shininess, const in float dotNH ) { + + return RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess ); + +}` ); // validated + +export const BRDF_BlinnPhong = new FunctionNode( ` +vec3 BRDF_BlinnPhong( vec3 lightDirection, vec3 specularColor, float shininess ) { + + vec3 halfDir = normalize( lightDirection + PositionViewDirection ); + + float dotNH = saturate( dot( NormalView, halfDir ) ); + float dotVH = saturate( dot( PositionViewDirection, halfDir ) ); + + vec3 F = F_Schlick( specularColor, 1.0, dotVH ); + + float G = G_BlinnPhong_Implicit( /* dotNL, dotNV */ ); + + float D = D_BlinnPhong( shininess, dotNH ); + + return F * ( G * D ); + +}` ).setIncludes( [ F_Schlick, G_BlinnPhong_Implicit, D_BlinnPhong ] ); // validated export const RE_Direct_BlinnPhong = new FunctionNode( ` -void RE_Direct_BlinnPhong( vec3 lightDirection, vec3 lightColor ) { +void RE_Direct_BlinnPhong( inout ReflectedLight reflectedLight, vec3 lightDirection, vec3 lightColor ) { float dotNL = saturate( dot( NormalView, lightDirection ) ); vec3 irradiance = dotNL * lightColor; - ReflectedLightDirectDiffuse += irradiance * BRDF_Lambert( MaterialDiffuseColor.rgb ); +#ifndef PHYSICALLY_CORRECT_LIGHTS + + irradiance *= PI; // punctual light + +#endif + + reflectedLight.directDiffuse += irradiance * BRDF_Lambert( MaterialDiffuseColor.rgb ); - ReflectedLightDirectSpecular += irradiance * BRDF_BlinnPhong( lightDirection, MaterialSpecularColor, MaterialSpecularShininess ); + reflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( lightDirection, MaterialSpecularColor, MaterialSpecularShininess ); }` ).setIncludes( [ BRDF_Lambert, BRDF_BlinnPhong ] ); -export const RE_IndirectDiffuse_BlinnPhong = new FunctionNode( ` -void RE_IndirectDiffuse_BlinnPhong( ) { +export const BlinnPhongLightingModel = new FunctionNode( ` +void ( inout ReflectedLight reflectedLight, vec3 lightDirection, vec3 lightColor ) { + + RE_Direct_BlinnPhong( reflectedLight, lightDirection, lightColor ); + +}` ).setIncludes( [ RE_Direct_BlinnPhong ] ); + +// +// STANDARD +// + +// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2 +// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf +export const V_GGX_SmithCorrelated = new FunctionNode( ` +float V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) { + + float a2 = pow2( alpha ); + + float gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) ); + float gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) ); - ReflectedLightIndirectDiffuse += Irradiance * BRDF_Lambert( MaterialDiffuseColor.rgb ); + return 0.5 / max( gv + gl, EPSILON ); + +}` ).setIncludes( [ pow2 ] ); + +// Microfacet Models for Refraction through Rough Surfaces - equation (33) +// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html +// alpha is "roughness squared" in Disney’s reparameterization +export const D_GGX = new FunctionNode( ` +float D_GGX( const in float alpha, const in float dotNH ) { + + float a2 = pow2( alpha ); + + float denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0; // avoid alpha = 0 with dotNH = 1 + + return RECIPROCAL_PI * a2 / pow2( denom ); + +}` ).setIncludes( [ pow2 ] ); + +// GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility +export const BRDF_Specular_GGX = new FunctionNode( ` +vec3 BRDF_Specular_GGX( vec3 lightDirection, const in vec3 f0, const in float f90, const in float roughness ) { + + float alpha = pow2( roughness ); // UE4's roughness + + vec3 halfDir = normalize( lightDirection + PositionViewDirection ); + + float dotNL = saturate( dot( TransformedNormalView, lightDirection ) ); + float dotNV = saturate( dot( TransformedNormalView, PositionViewDirection ) ); + float dotNH = saturate( dot( TransformedNormalView, halfDir ) ); + float dotVH = saturate( dot( PositionViewDirection, halfDir ) ); -}` ).setIncludes( [ BRDF_Lambert ] ); + vec3 F = F_Schlick( f0, f90, dotVH ); + + float V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV ); + + float D = D_GGX( alpha, dotNH ); + + return F * ( V * D ); + +}` ).setIncludes( [ pow2, F_Schlick, V_GGX_SmithCorrelated, D_GGX ] ); // validated + +export const RE_Direct_Physical = new FunctionNode( ` +void RE_Direct_Physical( inout ReflectedLight reflectedLight, vec3 lightDirection, vec3 lightColor ) { + + float dotNL = saturate( dot( TransformedNormalView, lightDirection ) ); + vec3 irradiance = dotNL * lightColor; + +#ifndef PHYSICALLY_CORRECT_LIGHTS + + irradiance *= PI; // punctual light + +#endif + + reflectedLight.directDiffuse += irradiance * BRDF_Lambert( MaterialDiffuseColor.rgb ); + + reflectedLight.directSpecular += irradiance * BRDF_Specular_GGX( lightDirection, MaterialSpecularColor, 1.0, MaterialRoughness ); + +}` ).setIncludes( [ BRDF_Lambert, BRDF_Specular_GGX ] ); + +export const PhysicalLightingModel = new FunctionNode( ` +void ( inout ReflectedLight reflectedLight, vec3 lightDirection, vec3 lightColor ) { + + RE_Direct_Physical( reflectedLight, lightDirection, lightColor ); + +}` ).setIncludes( [ RE_Direct_Physical ] ); + +// utils + +// Trowbridge-Reitz distribution to Mip level, following the logic of http://casual-effects.blogspot.ca/2011/08/plausible-environment-lighting-in-two.html +export const getSpecularMIPLevel = new FunctionNode( ` +float ( const in float roughness, const in float maxMIPLevelScalar ) { + + float sigma = PI * roughness * roughness / ( 1.0 + roughness ); + float desiredMIPLevel = maxMIPLevelScalar + log2( sigma ); + + // clamp to allowable LOD ranges. + return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar ); + +}` ); diff --git a/examples/jsm/renderers/nodes/functions/EncodingFunctions.js b/examples/jsm/renderers/nodes/functions/EncodingFunctions.js new file mode 100644 index 00000000000000..827e035d7bdc51 --- /dev/null +++ b/examples/jsm/renderers/nodes/functions/EncodingFunctions.js @@ -0,0 +1,99 @@ +import CodeNode from '../core/CodeNode.js'; +import FunctionNode from '../core/FunctionNode.js'; + +export const LinearToLinear = new FunctionNode( ` +vec4 ( in vec4 value ) { + return value; +}` ); + +export const GammaToLinear = new FunctionNode( ` +vec4 ( in vec4 value, in float gammaFactor ) { + return vec4( pow( value.rgb, vec3( gammaFactor ) ), value.a ); +}` ); + +export const LinearToGamma = new FunctionNode( ` +vec4 ( in vec4 value, in float gammaFactor ) { + return vec4( pow( value.rgb, vec3( 1.0 / gammaFactor ) ), value.a ); +}` ); + +export const sRGBToLinear = new FunctionNode( ` +vec4 ( in vec4 value ) { + return vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a ); +}` ); + +export const LinearTosRGB = new FunctionNode( ` +vec4 ( in vec4 value ) { + return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a ); +}` ); + +export const RGBEToLinear = new FunctionNode( ` +vec4 ( in vec4 value ) { + return vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 ); +}` ); + +export const LinearToRGBE = new FunctionNode( ` +vec4 ( in vec4 value ) { + float maxComponent = max( max( value.r, value.g ), value.b ); + float fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 ); + return vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 ); + // return vec4( value.brg, ( 3.0 + 128.0 ) / 256.0 ); +}` ); + +// reference: http://iwasbeingirony.blogspot.ca/2010/06/difference-between-rgbm-and-rgbd.html +export const RGBMToLinear = new FunctionNode( ` +vec4 ( in vec4 value, in float maxRange ) { + return vec4( value.rgb * value.a * maxRange, 1.0 ); +}` ); + +export const LinearToRGBM = new FunctionNode( ` +vec4 ( in vec4 value, in float maxRange ) { + float maxRGB = max( value.r, max( value.g, value.b ) ); + float M = clamp( maxRGB / maxRange, 0.0, 1.0 ); + M = ceil( M * 255.0 ) / 255.0; + return vec4( value.rgb / ( M * maxRange ), M ); +}` ); + +// reference: http://iwasbeingirony.blogspot.ca/2010/06/difference-between-rgbm-and-rgbd.html +export const RGBDToLinear = new FunctionNode( ` +vec4 ( in vec4 value, in float maxRange ) { + return vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 ); +}` ); + +export const LinearToRGBD = new FunctionNode( ` +vec4 ( in vec4 value, in float maxRange ) { + float maxRGB = max( value.r, max( value.g, value.b ) ); + float D = max( maxRange / maxRGB, 1.0 ); + // NOTE: The implementation with min causes the shader to not compile on + // a common Alcatel A502DL in Chrome 78/Android 8.1. Some research suggests + // that the chipset is Mediatek MT6739 w/ IMG PowerVR GE8100 GPU. + // D = min( floor( D ) / 255.0, 1.0 ); + D = clamp( floor( D ) / 255.0, 0.0, 1.0 ); + return vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D ); +}` ); + +// LogLuv reference: http://graphicrants.blogspot.ca/2009/04/rgbm-color-encoding.html +export const cLogLuvM = new CodeNode( 'const mat3 cLogLuvMNode = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );' ); +export const LinearToLogLuv = new FunctionNode( ` +vec4 ( in vec4 value ) { + vec3 Xp_Y_XYZp = cLogLuvMNode * value.rgb; + Xp_Y_XYZp = max( Xp_Y_XYZp, vec3( 1e-6, 1e-6, 1e-6 ) ); + vec4 vResult; + vResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z; + float Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0; + vResult.w = fract( Le ); + vResult.z = ( Le - ( floor( vResult.w * 255.0 ) ) / 255.0 ) / 255.0; + return vResult; +}` ).setIncludes( [ cLogLuvM ] ); + +// Inverse M matrix, for decoding +export const cLogLuvInverseM = new CodeNode( 'const mat3 cLogLuvInverseMNode = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );' ); +export const LogLuvToLinear = new FunctionNode( ` +vec4 ( in vec4 value ) { + float Le = value.z * 255.0 + value.w; + vec3 Xp_Y_XYZp; + Xp_Y_XYZp.y = exp2( ( Le - 127.0 ) / 2.0 ); + Xp_Y_XYZp.z = Xp_Y_XYZp.y / value.y; + Xp_Y_XYZp.x = value.x * Xp_Y_XYZp.z; + vec3 vRGB = cLogLuvInverseMNode * Xp_Y_XYZp.rgb; + return vec4( max( vRGB, 0.0 ), 1.0 ); +}` ).setIncludes( [ cLogLuvInverseM ] ); diff --git a/examples/jsm/renderers/nodes/functions/MathFunctions.js b/examples/jsm/renderers/nodes/functions/MathFunctions.js index c9faaa03765061..f3d92750212db8 100644 --- a/examples/jsm/renderers/nodes/functions/MathFunctions.js +++ b/examples/jsm/renderers/nodes/functions/MathFunctions.js @@ -7,14 +7,14 @@ export const saturateMacro = new CodeNode( '#define saturate(a) clamp( a, 0.0, 1 export const whiteComplementMacro = new CodeNode( '#define whiteComplement(a) ( 1.0 - saturate( a ) )' ); export const transformDirection = new FunctionNode( ` -vec3 transformDirection( in vec3 dir, in mat4 matrix ) { +vec3 ( in vec3 dir, in mat4 matrix ) { return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz ); }` ); export const inverseTransformDirection = new FunctionNode( ` -vec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) { +vec3 ( in vec3 dir, in mat4 matrix ) { // dir can be either a direction vector or a normal vector // upper-left 3x3 of matrix is assumed to be orthogonal diff --git a/examples/jsm/renderers/nodes/inputs/TextureNode.js b/examples/jsm/renderers/nodes/inputs/TextureNode.js index 3c4dc6e7c70dfc..01f7ce32a37ad7 100644 --- a/examples/jsm/renderers/nodes/inputs/TextureNode.js +++ b/examples/jsm/renderers/nodes/inputs/TextureNode.js @@ -1,27 +1,62 @@ import InputNode from '../core/InputNode.js'; +import ExpressionNode from '../core/ExpressionNode.js'; import UVNode from '../accessors/UVNode.js'; +import ColorSpaceNode from '../display/ColorSpaceNode.js'; class TextureNode extends InputNode { - constructor( value = null, uv = new UVNode() ) { + constructor( value = null, uv = new UVNode(), bias = null ) { super( 'texture' ); this.value = value; this.uv = uv; + this.bias = bias; } generate( builder, output ) { - const type = this.getType( builder ); + if ( output === 'sampler2D' ) { - const textureProperty = super.generate( builder, type ); - const uvSnippet = this.uv.build( builder, 'vec2' ); + return super.generate( builder, output ); - const textureCallSnippet = builder.getTexture( textureProperty, uvSnippet ); + } else { - return builder.format( textureCallSnippet, type, output ); + const nodeData = builder.getDataFromNode( this ); + + let colorSpace = nodeData.colorSpace; + + if ( colorSpace === undefined ) { + + const type = this.getType( builder ); + + const textureProperty = super.generate( builder, type ); + + const uvSnippet = this.uv.build( builder, 'vec2' ); + const bias = this.bias; + + let biasSnippet = null; + + if ( bias !== null ) { + + biasSnippet = bias.build( builder, 'float' ); + + } + + const textureCallSnippet = builder.getTexture( textureProperty, uvSnippet, biasSnippet ); + + colorSpace = new ColorSpaceNode(); + colorSpace.input = new ExpressionNode( textureCallSnippet, 'vec4' ); + colorSpace.fromDecoding( builder.getTextureEncodingFromMap( this.value ) ); + + nodeData.colorSpace = colorSpace; + + } + + return colorSpace.build( builder, output ); + + } } diff --git a/examples/jsm/renderers/nodes/lights/LightContextNode.js b/examples/jsm/renderers/nodes/lights/LightContextNode.js index 275c816f3b6ea9..ff33932866ccfe 100644 --- a/examples/jsm/renderers/nodes/lights/LightContextNode.js +++ b/examples/jsm/renderers/nodes/lights/LightContextNode.js @@ -1,17 +1,17 @@ import ContextNode from '../core/ContextNode.js'; -import { RE_Direct_BlinnPhong, RE_IndirectDiffuse_BlinnPhong } from '../functions/BSDFs.js'; +import StructNode from '../core/StructNode.js'; +import { PhysicalLightingModel, BlinnPhongLightingModel } from '../functions/BSDFs.js'; + +const reflectedLightStruct = new StructNode( { + directDiffuse: 'vec3', + directSpecular: 'vec3' +}, 'ReflectedLight' ); class LightContextNode extends ContextNode { constructor( node ) { - super( node ); - - } - - getType( /*builder*/ ) { - - return 'vec3'; + super( node, 'vec3' ); } @@ -21,37 +21,36 @@ class LightContextNode extends ContextNode { const material = builder.material; - let RE_Direct = null; - let RE_IndirectDiffuse = null; + let lightingModel = null; - if ( material.isMeshPhongMaterial === true ) { + if ( material.isMeshStandardMaterial === true ) { - RE_Direct = RE_Direct_BlinnPhong; - RE_IndirectDiffuse = RE_IndirectDiffuse_BlinnPhong; + lightingModel = PhysicalLightingModel; + + } else if ( material.isMeshPhongMaterial === true ) { + + lightingModel = BlinnPhongLightingModel; } - if ( RE_Direct !== null ) { + const reflectedLightNode = reflectedLightStruct.create(); + const reflectedLight = reflectedLightNode.build( builder, 'var' ); - this.setParameter( 'RE_Direct', RE_Direct ); - this.setParameter( 'RE_IndirectDiffuse', RE_IndirectDiffuse ); + this.setContextValue( 'reflectedLight', reflectedLightNode ); - } + if ( lightingModel !== null ) { - const resetTotalLight = 'Irradiance = vec3( 0.0 ); ReflectedLightDirectDiffuse = vec3( 0.0 ); ReflectedLightDirectSpecular = vec3( 0.0 );'; - const resultTotalLight = 'ReflectedLightDirectDiffuse + ReflectedLightDirectSpecular'; + this.setContextValue( 'lightingModel', lightingModel ); - // include keywords + } - builder.getContextParameter( 'keywords' ).include( builder, resetTotalLight ); + const totalLightSnippet = `( ${reflectedLight}.directDiffuse + ${reflectedLight}.directSpecular )`; // add code - builder.addFlowCode( resetTotalLight ); - super.generate( builder, output ); - return builder.format( resultTotalLight, type, output ); + return builder.format( totalLightSnippet, type, output ); } diff --git a/examples/jsm/renderers/nodes/lights/LightNode.js b/examples/jsm/renderers/nodes/lights/LightNode.js index ea447f7dfe24c6..2d403b22309c6c 100644 --- a/examples/jsm/renderers/nodes/lights/LightNode.js +++ b/examples/jsm/renderers/nodes/lights/LightNode.js @@ -56,22 +56,21 @@ class LightNode extends Node { this.lightPositionView.object3d = this.light; - const directFunctionNode = builder.getContextParameter( 'RE_Direct' ); - const indirectDiffuseFunctionNode = builder.getContextParameter( 'RE_IndirectDiffuse' ); + const lightingModelFunctionNode = builder.getContextValue( 'lightingModel' ); - const directFunctionCallNode = directFunctionNode.call( { - lightDirection: this.lightDirection, - lightColor: this.lightColor - } ); + if ( lightingModelFunctionNode !== undefined ) { - const indirectDiffuseFunctionCallNode = indirectDiffuseFunctionNode.call( { - lightDirection: this.lightDirection, - lightColor: this.lightColor - } ); + const reflectedLightStructNode = builder.getContextValue( 'reflectedLight' ); + + const lightingModelCallNode = lightingModelFunctionNode.call( { + lightDirection: this.lightDirection, + lightColor: this.lightColor, + reflectedLight: reflectedLightStructNode + } ); - builder.addFlowCode( directFunctionCallNode.build( builder ) ); + builder.addFlowCode( lightingModelCallNode.build( builder ) ); - builder.addFlowCode( indirectDiffuseFunctionCallNode.build( builder ) ); + } return this.color.build( builder, output ); diff --git a/examples/jsm/renderers/nodes/lights/PhysicalMaterialContextNode.js b/examples/jsm/renderers/nodes/lights/PhysicalMaterialContextNode.js new file mode 100644 index 00000000000000..b3ad44032fc6ab --- /dev/null +++ b/examples/jsm/renderers/nodes/lights/PhysicalMaterialContextNode.js @@ -0,0 +1,45 @@ +import ContextNode from '../core/ContextNode.js'; +import NormalNode from '../accessors/NormalNode.js'; +import ExpressionNode from '../core/ExpressionNode.js'; +import FloatNode from '../inputs/FloatNode.js'; + +class PhysicalMaterialContextNode extends ContextNode { + + static RADIANCE = 'radiance'; + static IRRADIANCE = 'irradiance'; + + constructor( scope, node ) { + + super( node, 'vec3' ); + + this.scope = scope; + + } + + generate( builder, output ) { + + const scope = this.scope; + + let roughness = null; + + if ( scope === PhysicalMaterialContextNode.RADIANCE ) { + + roughness = new ExpressionNode( 'roughnessFactor', 'float' ); + + } else if ( scope === PhysicalMaterialContextNode.IRRADIANCE ) { + + roughness = new FloatNode( 1.0 ).setConst( true ); + + this.setContextValue( 'uv', new NormalNode( NormalNode.WORLD ) ); + + } + + this.setContextValue( 'roughness', roughness ); + + return super.generate( builder, output ); + + } + +} + +export default PhysicalMaterialContextNode; diff --git a/examples/jsm/renderers/nodes/math/MathNode.js b/examples/jsm/renderers/nodes/math/MathNode.js index 4b021b8ee8d00d..da9ea1bfb3aa2c 100644 --- a/examples/jsm/renderers/nodes/math/MathNode.js +++ b/examples/jsm/renderers/nodes/math/MathNode.js @@ -1,12 +1,55 @@ -import Node from '../core/Node.js'; - -class MathNode extends Node { - +import TempNode from '../core/Node.js'; + +class MathNode extends TempNode { + + // 1 input + + static RAD = 'radians'; + static DEG = 'degrees'; + static EXP = 'exp'; + static EXP2 = 'exp2'; + static LOG = 'log'; + static LOG2 = 'log2'; + static SQRT = 'sqrt'; + static INV_SQRT = 'inversesqrt'; + static FLOOR = 'floor'; + static CEIL = 'ceil'; static NORMALIZE = 'normalize'; - static NEGATE = 'negate'; + static FRACT = 'fract'; + static SATURATE = 'saturate'; + static SIN = 'sin'; + static COS = 'cos'; + static TAN = 'tan'; + static ASIN = 'asin'; + static ACOS = 'acos'; + static ATAN = 'atan'; + static ABS = 'abs'; + static SIGN = 'sign'; static LENGTH = 'length'; + static NEGATE = 'negate'; + static INVERT = 'invert'; + + // 2 inputs + + static MIN = 'min'; + static MAX = 'max'; + static MOD = 'mod'; + static STEP = 'step'; + static REFLECT = 'reflect'; + static DISTANCE = 'distance'; + static DOT = 'dot'; + static CROSS = 'cross'; + static POW = 'pow'; + + // 3 inputs - constructor( method, a, b = null ) { + static MIX = 'mix'; + static CLAMP = 'clamp'; + static REFRACT = 'refract'; + static SMOOTHSTEP = 'smoothstep'; + static FACEFORWARD = 'faceforward'; + + constructor( method, a, b = null, c = null ) { super(); @@ -14,28 +57,31 @@ class MathNode extends Node { this.a = a; this.b = b; + this.c = c; } getInputType( builder ) { - const typeA = this.a.getType( builder ); + const aLen = this.a.getTypeLength( builder ); + const bLen = this.b ? this.b.getTypeLength( builder ) : 0; + const cLen = this.c ? this.c.getTypeLength( builder ) : 0; - if ( this.b !== null ) { + if ( aLen > bLen && aLen > cLen ) { - const typeB = this.b.getType( builder ); + return this.a.getType( builder ); - if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) { + } else if ( bLen > cLen ) { - // anytype x anytype: use the greater length vector + return this.b.getType( builder ); - return typeB; + } else if ( cLen > aLen ) { - } + this.c.getType( builder ) } - return typeA; + return this.a.getType( builder ); } @@ -43,14 +89,11 @@ class MathNode extends Node { const method = this.method; - if ( method === MathNode.LENGTH ) { + if ( method === MathNode.LENGTH || method === MathNode.DISTANCE || method === MathNode.DOT ) { return 'float'; - } else if ( - method === MathNode.TRANSFORM_DIRETION || - method === MathNode.INVERSE_TRANSFORM_DIRETION - ) { + } else if (method === MathNode.CROSS) { return 'vec3'; @@ -65,33 +108,77 @@ class MathNode extends Node { generate( builder, output ) { const method = this.method; - const type = this.getInputType( builder ); - - const a = this.a.build( builder, type ); - let b = null; - if ( this.b !== null ) { + const type = this.getType( builder ); + const inputType = this.getInputType( builder ); - b = this.b.build( builder, type ); + if ( method === MathNode.NEGATE ) { - } + return builder.format( '( -' + this.a.build( builder, inputType ) + ' )', type, output ); - if ( b !== null ) { + } else if ( method === MathNode.INVERT ) { - return builder.format( `${method}( ${a}, ${b} )`, type, output ); + return builder.format( '( 1.0 - ' + this.a.build( builder, inputType ) + ' )', type, output ); } else { - if ( method === MathNode.NEGATE ) { + const params = []; + + if ( method === MathNode.CROSS ) { + + params.push( + this.a.build( builder, type ), + this.b.build( builder, type ) + ); - return builder.format( `( -${a} )`, type, output ); + } else if ( method === MathNode.STEP ) { + + params.push( + this.b.build( builder, this.a.getTypeLength( builder ) === 1 ? 'float' : inputType ), + this.b.build( builder, inputType ) + ); + + } else if ( method === MathNode.MIN || method === MathNode.MAX || method === MathNode.MOD ) { + + params.push( + this.a.build( builder, inputType ), + this.b.build( builder, this.b.getTypeLength( builder ) === 1 ? 'float' : inputType ) + ); + + } else if ( method === MathNode.REFRACT ) { + + params.push( + this.a.build( builder, inputType ), + this.b.build( builder, inputType ), + this.c.build( builder, 'float' ) + ); + + } else if ( method === MathNode.MIX ) { + + params.push( + this.a.build( builder, inputType ), + this.b.build( builder, inputType ), + this.c.build( builder, this.c.getTypeLength( builder ) === 1 ? 'float' : inputType ) + ); } else { - return builder.format( `${method}( ${a} )`, type, output ); + params.push( this.a.build( builder, inputType ) ); + + if ( this.c !== null ) { + + params.push( this.b.build( builder, inputType ), this.c.build( builder, inputType ) ); + + } else if ( this.b !== null ) { + + params.push( this.b.build( builder, inputType ) ); + + } } + return builder.format( `${method}( ${params.join(', ')} )`, type, output ); + } } diff --git a/examples/jsm/renderers/nodes/math/OperatorNode.js b/examples/jsm/renderers/nodes/math/OperatorNode.js index 902af0ad95cdb2..c613daf1e559f3 100644 --- a/examples/jsm/renderers/nodes/math/OperatorNode.js +++ b/examples/jsm/renderers/nodes/math/OperatorNode.js @@ -1,6 +1,6 @@ -import Node from '../core/Node.js'; +import TempNode from '../core/TempNode.js'; -class OperatorNode extends Node { +class OperatorNode extends TempNode { constructor( op, a, b ) { @@ -42,12 +42,6 @@ class OperatorNode extends Node { } - getVectorFromMatrix( type ) { - - return 'vec' + type.substr( 3 ); - - } - generate( builder, output ) { let typeA = this.a.getType( builder ); @@ -59,13 +53,13 @@ class OperatorNode extends Node { // matrix x vector - type = typeB = this.getVectorFromMatrix( typeA ); + type = typeB = builder.getVectorFromMatrix( typeA ); } else if ( builder.isVector( typeA ) && builder.isMatrix( typeB ) ) { // vector x matrix - type = typeB = this.getVectorFromMatrix( typeB ); + type = typeB = builder.getVectorFromMatrix( typeB ); } else { diff --git a/examples/jsm/renderers/webgpu/nodes/ShaderLib.js b/examples/jsm/renderers/webgpu/nodes/ShaderLib.js index 47bfe2a3e4e479..64fdfce037095a 100644 --- a/examples/jsm/renderers/webgpu/nodes/ShaderLib.js +++ b/examples/jsm/renderers/webgpu/nodes/ShaderLib.js @@ -5,15 +5,11 @@ const ShaderLib = { vertexShader: `#version 450 - NODE_HEADER_ATTRIBUTES - NODE_HEADER_UNIFORMS - NODE_HEADER_VARYS - void main(){ - NODE_BODY_VARYS - NODE_BODY_VARS + NODE_CODE + NODE_CODE_MVP gl_Position = NODE_MVP; }`, @@ -21,15 +17,11 @@ const ShaderLib = { fragmentShader: `#version 450 - NODE_HEADER_ATTRIBUTES - NODE_HEADER_UNIFORMS - NODE_HEADER_VARYS - layout(location = 0) out vec4 outColor; void main() { - NODE_BODY_VARS + NODE_CODE MaterialDiffuseColor = vec4( 1.0 ); @@ -51,6 +43,7 @@ const ShaderLib = { #ifdef NODE_ALPHA_TEST + NODE_CODE_ALPHA_TEST if ( MaterialDiffuseColor.a < NODE_ALPHA_TEST ) discard; #endif @@ -77,15 +70,11 @@ const ShaderLib = { vertexShader: `#version 450 - NODE_HEADER_ATTRIBUTES - NODE_HEADER_UNIFORMS - NODE_HEADER_VARYS - void main(){ - NODE_BODY_VARYS - NODE_BODY_VARS + NODE_CODE + NODE_CODE_MVP gl_Position = NODE_MVP; }`, @@ -93,15 +82,11 @@ const ShaderLib = { fragmentShader: `#version 450 - NODE_HEADER_ATTRIBUTES - NODE_HEADER_UNIFORMS - NODE_HEADER_VARYS - layout(location = 0) out vec4 outColor; void main() { - NODE_BODY_VARS + NODE_CODE MaterialDiffuseColor = vec4( 1.0 ); MaterialSpecularColor = vec3( 1.0 ); @@ -115,6 +100,7 @@ const ShaderLib = { #ifdef NODE_ALPHA_TEST + NODE_CODE_ALPHA_TEST if ( MaterialDiffuseColor.a < NODE_ALPHA_TEST ) discard; #endif @@ -140,7 +126,89 @@ const ShaderLib = { }` - } + }, + + standard: { + + vertexShader: + `#version 450 + + void main(){ + + NODE_CODE + + NODE_CODE_MVP + gl_Position = NODE_MVP; + + }`, + + fragmentShader: + `#version 450 + + layout(location = 0) out vec4 outColor; + + void main() { + + NODE_CODE + + MaterialDiffuseColor = vec4( 1.0 ); + MaterialMetalness = 1.0; + MaterialRoughness = 1.0; + + #ifdef NODE_COLOR + + NODE_CODE_COLOR + + MaterialDiffuseColor = NODE_COLOR; + + #endif + + #ifdef NODE_OPACITY + + NODE_CODE_OPACITY + + MaterialDiffuseColor.a *= NODE_OPACITY; + + #endif + + #ifdef NODE_ALPHA_TEST + + NODE_CODE_ALPHA_TEST + if ( MaterialDiffuseColor.a < NODE_ALPHA_TEST ) discard; + + #endif + + NODE_CODE_METALNESS + MaterialMetalness = NODE_METALNESS; + + NODE_CODE_ROUGHNESS + MaterialRoughness = NODE_ROUGHNESS; + + #ifdef NODE_NORMAL + + NODE_CODE_NORMAL + TransformedNormalView = NODE_NORMAL; + + #endif + + MaterialDiffuseColor.rgb = MaterialDiffuseColor.rgb * ( 1.0 - MaterialMetalness ); + + #ifdef NODE_LIGHT + + NODE_CODE_LIGHT + + outColor.rgb = NODE_LIGHT; + outColor.a = MaterialDiffuseColor.a; + + #else + + outColor = MaterialDiffuseColor; + + #endif + + }` + + }, }; diff --git a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js index 9081fed7f5dcc7..dc066eea4f4512 100644 --- a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js @@ -7,18 +7,22 @@ import WebGPUNodeSampler from './WebGPUNodeSampler.js'; import { WebGPUNodeSampledTexture } from './WebGPUNodeSampledTexture.js'; import NodeSlot from '../../nodes/core/NodeSlot.js'; +import VarNode from '../../nodes/core/VarNode.js'; import NodeBuilder from '../../nodes/core/NodeBuilder.js'; import MaterialNode from '../../nodes/accessors/MaterialNode.js'; +import NormalNode from '../../nodes/accessors/NormalNode.js'; import ModelViewProjectionNode from '../../nodes/accessors/ModelViewProjectionNode.js'; import LightContextNode from '../../nodes/lights/LightContextNode.js'; import ShaderLib from './ShaderLib.js'; class WebGPUNodeBuilder extends NodeBuilder { - constructor( material, renderer ) { + constructor( material, renderer, lightNode = null ) { super( material, renderer ); + this.lightNode = lightNode; + this.bindings = { vertex: [], fragment: [] }; this.bindingsOffset = { vertex: 0, fragment: 0 }; @@ -38,7 +42,11 @@ class WebGPUNodeBuilder extends NodeBuilder { let shader = null; - if ( material.isMeshPhongMaterial ) { + if ( material.isMeshStandardMaterial ) { + + shader = ShaderLib.standard; + + } else if ( material.isMeshPhongMaterial ) { shader = ShaderLib.phong; @@ -52,10 +60,17 @@ class WebGPUNodeBuilder extends NodeBuilder { // parse inputs - if ( material.isMeshPhongMaterial || material.isMeshBasicMaterial || material.isPointsMaterial || material.isLineBasicMaterial ) { + if ( material.isMeshStandardMaterial || material.isMeshPhongMaterial || material.isMeshBasicMaterial || material.isPointsMaterial || material.isLineBasicMaterial ) { const mvpNode = new ModelViewProjectionNode(); - const lightNode = material.lightNode; + + let lightNode = material.lightNode; + + if ( lightNode === undefined && this.lightNode && this.lightNode.hasLights === true ) { + + lightNode = this.lightNode; + + } if ( material.positionNode !== undefined ) { @@ -95,7 +110,43 @@ class WebGPUNodeBuilder extends NodeBuilder { } - if ( material.isMeshPhongMaterial ) { + if ( material.isMeshStandardMaterial ) { + + if ( material.metalnessNode !== undefined ) { + + this.addSlot( 'fragment', new NodeSlot( material.metalnessNode, 'METALNESS', 'float' ) ); + + } else { + + this.addSlot( 'fragment', new NodeSlot( new MaterialNode( MaterialNode.METALNESS ), 'METALNESS', 'float' ) ); + + } + + if ( material.roughnessNode !== undefined ) { + + this.addSlot( 'fragment', new NodeSlot( material.roughnessNode, 'ROUGHNESS', 'float' ) ); + + } else { + + this.addSlot( 'fragment', new NodeSlot( new MaterialNode( MaterialNode.ROUGHNESS ), 'ROUGHNESS', 'float' ) ); + + } + + let normalNode = null; + + if ( material.normalNode !== undefined ) { + + normalNode = material.normalNode; + + } else { + + normalNode = new NormalNode( NormalNode.VIEW ); + + } + + this.addSlot( 'fragment', new NodeSlot( new VarNode( normalNode, 'TransformedNormalView', 'vec3' ), 'NORMAL', 'vec3' ) ); + + } else if ( material.isMeshPhongMaterial ) { if ( material.specularNode !== undefined ) { @@ -119,7 +170,7 @@ class WebGPUNodeBuilder extends NodeBuilder { } - if ( lightNode !== undefined ) { + if ( lightNode && lightNode.isNode ) { const lightContextNode = new LightContextNode( lightNode ); @@ -131,9 +182,17 @@ class WebGPUNodeBuilder extends NodeBuilder { } - getTexture( textureProperty, uvSnippet ) { + getTexture( textureProperty, uvSnippet, biasSnippet = null ) { + + if ( biasSnippet !== null ) { + + return `texture( sampler2D( ${textureProperty}, ${textureProperty}_sampler ), ${uvSnippet}, ${biasSnippet} )`; + + } else { + + return `texture( sampler2D( ${textureProperty}, ${textureProperty}_sampler ), ${uvSnippet} )`; - return `texture( sampler2D( ${textureProperty}, ${textureProperty}_sampler ), ${uvSnippet} )`; + } } @@ -258,7 +317,7 @@ class WebGPUNodeBuilder extends NodeBuilder { } - getAttributesHeaderSnippet( shaderStage ) { + getAttributes( shaderStage ) { let snippet = ''; @@ -280,7 +339,7 @@ class WebGPUNodeBuilder extends NodeBuilder { } - getVarysHeaderSnippet( shaderStage ) { + getVarys( shaderStage ) { let snippet = ''; @@ -300,63 +359,7 @@ class WebGPUNodeBuilder extends NodeBuilder { } - getVarysBodySnippet( shaderStage ) { - - let snippet = ''; - - if ( shaderStage === 'vertex' ) { - - for ( const vary of this.varys ) { - - snippet += `${vary.name} = ${vary.snippet}; `; - - } - - } - - return snippet; - - } - - getVarsHeaderSnippet( shaderStage ) { - - let snippet = ''; - - const vars = this.vars[ shaderStage ]; - - for ( let index = 0; index < vars.length; index ++ ) { - - const variable = vars[ index ]; - - snippet += `${variable.type} ${variable.name}; `; - - } - - return snippet; - - } - - getVarsBodySnippet( shaderStage ) { - - let snippet = ''; - - const vars = this.vars[ shaderStage ]; - - for ( const variable of vars ) { - - if ( variable.snippet !== '' ) { - - snippet += `${variable.name} = ${variable.snippet}; `; - - } - - } - - return snippet; - - } - - getUniformsHeaderSnippet( shaderStage ) { + getUniforms( shaderStage ) { const uniforms = this.uniforms[ shaderStage ]; @@ -409,7 +412,7 @@ class WebGPUNodeBuilder extends NodeBuilder { build() { - const keywords = this.getContextParameter( 'keywords' ); + const keywords = this.getContextValue( 'keywords' ); for ( const shaderStage of [ 'vertex', 'fragment' ] ) { diff --git a/examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js b/examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js index ac049a816b6ab7..f0eb164545d39b 100644 --- a/examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js +++ b/examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js @@ -13,13 +13,13 @@ class WebGPUNodes { } - get( object ) { + get( object, lightNode ) { let nodeBuilder = this.builders.get( object ); if ( nodeBuilder === undefined ) { - nodeBuilder = new WebGPUNodeBuilder( object.material, this.renderer ).build(); + nodeBuilder = new WebGPUNodeBuilder( object.material, this.renderer, lightNode ).build(); this.builders.set( object, nodeBuilder ); @@ -41,11 +41,11 @@ class WebGPUNodes { } - update( object, camera ) { + update( object, camera, lightNode ) { const material = object.material; - const nodeBuilder = this.get( object ); + const nodeBuilder = this.get( object, lightNode ); const nodeFrame = this.nodeFrame; nodeFrame.material = material; diff --git a/examples/screenshots/webgpu_lights_custom.jpg b/examples/screenshots/webgpu_lights_custom.jpg index 001fe203e0b94e..35b06935974bb9 100644 Binary files a/examples/screenshots/webgpu_lights_custom.jpg and b/examples/screenshots/webgpu_lights_custom.jpg differ diff --git a/examples/screenshots/webgpu_lights_selective.jpg b/examples/screenshots/webgpu_lights_selective.jpg index a6da7a64623125..35b06935974bb9 100644 Binary files a/examples/screenshots/webgpu_lights_selective.jpg and b/examples/screenshots/webgpu_lights_selective.jpg differ diff --git a/examples/webgpu_lights_custom.html b/examples/webgpu_lights_custom.html index 5c46d069affc72..eeb58c5c00dcfe 100644 --- a/examples/webgpu_lights_custom.html +++ b/examples/webgpu_lights_custom.html @@ -89,26 +89,17 @@ // custom lighting model - const RE_Direct = new FunctionNode( ` - void ( vec3 lightColor ) { + const customLightingModel = new FunctionNode( ` + void ( inout ReflectedLight reflectedLight, vec3 lightColor ) { // lightColor returns the light color with the intensity calculated - ReflectedLightDirectDiffuse += lightColor; - - }` ); - - - const RE_IndirectDiffuse = new FunctionNode( ` - void ( ) { - - //ReflectedLightIndirectDiffuse += vec3( 0.0 ); + reflectedLight.directDiffuse += lightColor; }` ); const lightingModelContext = new ContextNode( allLightsNode ); - lightingModelContext.setParameter( 'RE_Direct', RE_Direct ); - lightingModelContext.setParameter( 'RE_IndirectDiffuse', RE_IndirectDiffuse ); + lightingModelContext.setContextValue( 'lightingModel', customLightingModel ); materialPoints.lightNode = lightingModelContext; diff --git a/examples/webgpu_lights_selective.html b/examples/webgpu_lights_selective.html index fac0d39af82410..62a0b228354d21 100644 --- a/examples/webgpu_lights_selective.html +++ b/examples/webgpu_lights_selective.html @@ -27,6 +27,8 @@ import Stats from './jsm/libs/stats.module.js'; + import { GUI } from './jsm/libs/dat.gui.module.js'; + import { OrbitControls } from './jsm/controls/OrbitControls.js'; import { TeapotGeometry } from './jsm/geometries/TeapotGeometry.js'; @@ -34,6 +36,8 @@ import WebGPU from './jsm/renderers/webgpu/WebGPU.js'; import LightsNode from './jsm/renderers/nodes/lights/LightsNode.js'; + import TextureNode from './jsm/renderers/nodes/inputs/TextureNode.js'; + import NormalMapNode from './jsm/renderers/nodes/display/NormalMapNode.js'; let camera, scene, renderer, light1, light2, light3, light4, @@ -58,6 +62,18 @@ const sphere = new THREE.SphereGeometry( 0.5, 16, 8 ); + //textures + + const textureLoader = new THREE.TextureLoader(); + + const normalMapTexture = textureLoader.load( './textures/water/Water_1_M_Normal.jpg' ); + normalMapTexture.wrapS = THREE.RepeatWrapping; + normalMapTexture.wrapT = THREE.RepeatWrapping; + + const alphaTexture = textureLoader.load( './textures/roughness_map.jpg' ); + alphaTexture.wrapS = THREE.RepeatWrapping; + alphaTexture.wrapT = THREE.RepeatWrapping; + //lights light1 = new THREE.PointLight( 0xff0040, 2, 100 ); @@ -86,22 +102,25 @@ const geometryTeapot = new TeapotGeometry( 8, 18 ); - const leftObject = new THREE.Mesh( geometryTeapot, new THREE.MeshPhongMaterial( { color: 0x555555 } ) ); + const leftObject = new THREE.Mesh( geometryTeapot, new THREE.MeshStandardMaterial( { color: 0x555555 } ) ); leftObject.material.lightNode = redLightNode; + leftObject.material.roughnessNode = new TextureNode( alphaTexture ); + leftObject.material.metalness = 0; leftObject.position.x = - 30; scene.add( leftObject ); - const centerObject = new THREE.Mesh( geometryTeapot, new THREE.MeshPhongMaterial( { color: 0x555555 } ) ); + const centerObject = new THREE.Mesh( geometryTeapot, new THREE.MeshStandardMaterial( { color: 0x555555 } ) ); centerObject.material.lightNode = allLightsNode; + centerObject.material.normalNode = new NormalMapNode( new TextureNode( normalMapTexture ) ); + centerObject.material.roughness = .5; scene.add( centerObject ); - const rightObject = new THREE.Mesh( geometryTeapot, new THREE.MeshPhongMaterial( { color: 0x555555 } ) ); + const rightObject = new THREE.Mesh( geometryTeapot, new THREE.MeshStandardMaterial( { color: 0x555555 } ) ); rightObject.material.lightNode = blueLightNode; + rightObject.material.metalnessNode = new TextureNode( alphaTexture ); rightObject.position.x = 30; scene.add( rightObject ); - leftObject.material.shininess = centerObject.material.shininess = rightObject.material.shininess = 70; - leftObject.rotation.y = centerObject.rotation.y = rightObject.rotation.y = Math.PI * - 0.5; leftObject.position.y = centerObject.position.y = rightObject.position.y = - 10; @@ -125,6 +144,13 @@ window.addEventListener( 'resize', onWindowResize ); + //gui + + const gui = new GUI(); + + gui.add( centerObject.material, 'roughness', 0, 1, 0.01 ); + gui.add( centerObject.material, 'metalness', 0, 1, 0.01 ); + return renderer.init(); }