From b0a28771c1a6faf1f81ebc277ad440f4ebe9b19e Mon Sep 17 00:00:00 2001 From: hybridherbst Date: Wed, 24 Jul 2024 18:03:33 +0200 Subject: [PATCH] Partially revert "NodeMaterial: Remove `WebGLNodeBuilder` (#28167)" --- .../webgl-legacy/nodes/GLSL1NodeBuilder.js | 318 +++++++ .../renderers/webgl-legacy/nodes/SlotNode.js | 26 + .../webgl-legacy/nodes/WebGLNodeBuilder.js | 792 ++++++++++++++++++ .../webgl-legacy/nodes/WebGLNodes.js | 51 ++ 4 files changed, 1187 insertions(+) create mode 100644 examples/jsm/renderers/webgl-legacy/nodes/GLSL1NodeBuilder.js create mode 100644 examples/jsm/renderers/webgl-legacy/nodes/SlotNode.js create mode 100644 examples/jsm/renderers/webgl-legacy/nodes/WebGLNodeBuilder.js create mode 100644 examples/jsm/renderers/webgl-legacy/nodes/WebGLNodes.js diff --git a/examples/jsm/renderers/webgl-legacy/nodes/GLSL1NodeBuilder.js b/examples/jsm/renderers/webgl-legacy/nodes/GLSL1NodeBuilder.js new file mode 100644 index 00000000000000..73873ebd11da92 --- /dev/null +++ b/examples/jsm/renderers/webgl-legacy/nodes/GLSL1NodeBuilder.js @@ -0,0 +1,318 @@ +import { MathNode, GLSLNodeParser, NodeBuilder } from '../../../nodes/Nodes.js'; + +const glslMethods = { + [ MathNode.ATAN2 ]: 'atan' +}; + +const precisionLib = { + low: 'lowp', + medium: 'mediump', + high: 'highp' +}; + +class GLSL1NodeBuilder extends NodeBuilder { + + constructor( object, renderer, scene = null ) { + + super( object, renderer, new GLSLNodeParser(), scene ); + + } + + getMethod( method ) { + + return glslMethods[ method ] || method; + + } + + getTexture( texture, textureProperty, uvSnippet ) { + + if ( texture.isTextureCube ) { + + return `textureCube( ${textureProperty}, ${uvSnippet} )`; + + } else { + + return `texture2D( ${textureProperty}, ${uvSnippet} )`; + + } + + } + + getTextureBias( texture, textureProperty, uvSnippet, biasSnippet ) { + + return `textureLod( ${textureProperty}, ${uvSnippet}, ${biasSnippet} )`; + + } + + getVars( shaderStage ) { + + const snippets = []; + + const vars = this.vars[ shaderStage ]; + + for ( const variable of vars ) { + + snippets.push( `${ this.getVar( variable.type, variable.name ) };` ); + + } + + return snippets.join( '\n\t' ); + + } + + getUniforms( shaderStage ) { + + const uniforms = this.uniforms[ shaderStage ]; + + let output = ''; + + for ( const uniform of uniforms ) { + + let snippet = null; + + if ( uniform.type === 'texture' ) { + + snippet = `sampler2D ${uniform.name};\n`; + + } else if ( uniform.type === 'cubeTexture' ) { + + snippet = `samplerCube ${uniform.name};\n`; + + } else { + + const vectorType = this.getVectorType( uniform.type ); + + snippet = `${vectorType} ${uniform.name};\n`; + + } + + const precision = uniform.node.precision; + + if ( precision !== null ) { + + snippet = 'uniform ' + precisionLib[ precision ] + ' ' + snippet; + + } else { + + snippet = 'uniform ' + snippet; + + } + + output += snippet; + + } + + return output; + + } + + getAttributes( shaderStage ) { + + let snippet = ''; + + if ( shaderStage === 'vertex' ) { + + const attributes = this.attributes; + + for ( const attribute of attributes ) { + + snippet += `attribute ${attribute.type} ${attribute.name};\n`; + + } + + } + + return snippet; + + } + + getVaryings( shaderStage ) { + + let snippet = ''; + + const varyings = this.varyings; + + if ( shaderStage === 'vertex' ) { + + for ( const varying of varyings ) { + + snippet += `${varying.needsInterpolation ? 'varying' : '/*varying*/'} ${varying.type} ${varying.name};\n`; + + } + + } else if ( shaderStage === 'fragment' ) { + + for ( const varying of varyings ) { + + if ( varying.needsInterpolation ) { + + snippet += `varying ${varying.type} ${varying.name};\n`; + + } + + } + + } + + return snippet; + + } + + getVertexIndex() { + + return 'gl_VertexID'; + + } + + getFrontFacing() { + + return 'gl_FrontFacing'; + + } + + getFragCoord() { + + return 'gl_FragCoord'; + + } + + isFlipY() { + + return true; + + } + + _getGLSLVertexCode( shaderData ) { + + return `${ this.getSignature() } + +// uniforms +${shaderData.uniforms} + +// varyings +${shaderData.varyings} + +// attributes +${shaderData.attributes} + +// codes +${shaderData.codes} + +void main() { + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + +} +`; + + } + + _getGLSLFragmentCode( shaderData ) { + + return `${ this.getSignature() } + +// precision +precision highp float; +precision highp int; + +// uniforms +${shaderData.uniforms} + +// varyings +${shaderData.varyings} + +// codes +${shaderData.codes} + +void main() { + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + +} +`; + + } + + buildCode() { + + const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} }; + + for ( const shaderStage in shadersData ) { + + let flow = '// code\n\n'; + flow += this.flowCode[ shaderStage ]; + + const flowNodes = this.flowNodes[ shaderStage ]; + const mainNode = flowNodes[ flowNodes.length - 1 ]; + + for ( const node of flowNodes ) { + + const flowSlotData = this.getFlowData( node/*, shaderStage*/ ); + const slotName = node.name; + + if ( slotName ) { + + if ( flow.length > 0 ) flow += '\n'; + + flow += `\t// flow -> ${ slotName }\n\t`; + + } + + flow += `${ flowSlotData.code }\n\t`; + + if ( node === mainNode && shaderStage !== 'compute' ) { + + flow += '// result\n\t'; + + if ( shaderStage === 'vertex' ) { + + flow += 'gl_Position = '; + + } else if ( shaderStage === 'fragment' ) { + + flow += 'gl_FragColor = '; + + } + + flow += `${ flowSlotData.result };`; + + } + + } + + const stageData = shadersData[ shaderStage ]; + + stageData.uniforms = this.getUniforms( shaderStage ); + stageData.attributes = this.getAttributes( shaderStage ); + stageData.varyings = this.getVaryings( shaderStage ); + stageData.vars = this.getVars( shaderStage ); + stageData.codes = this.getCodes( shaderStage ); + stageData.flow = flow; + + } + + if ( this.material !== null ) { + + this.vertexShader = this._getGLSLVertexCode( shadersData.vertex ); + this.fragmentShader = this._getGLSLFragmentCode( shadersData.fragment ); + + } else { + + console.warn( 'GLSLNodeBuilder: compute shaders are not supported.' ); + //this.computeShader = this._getGLSLComputeCode( shadersData.compute ); + + } + + } + +} + +export default GLSL1NodeBuilder; diff --git a/examples/jsm/renderers/webgl-legacy/nodes/SlotNode.js b/examples/jsm/renderers/webgl-legacy/nodes/SlotNode.js new file mode 100644 index 00000000000000..e6488edf3a35bc --- /dev/null +++ b/examples/jsm/renderers/webgl-legacy/nodes/SlotNode.js @@ -0,0 +1,26 @@ +import { Node } from '../../../nodes/Nodes.js'; + +class SlotNode extends Node { + + constructor( params ) { + + super( params.nodeType ); + + this.node = null; + this.source = null; + this.target = null; + this.inclusionType = 'replace'; + + Object.assign( this, params ); + + } + + generate( builder ) { + + return this.node.build( builder, this.getNodeType( builder ) ); + + } + +} + +export default SlotNode; diff --git a/examples/jsm/renderers/webgl-legacy/nodes/WebGLNodeBuilder.js b/examples/jsm/renderers/webgl-legacy/nodes/WebGLNodeBuilder.js new file mode 100644 index 00000000000000..e903e873959116 --- /dev/null +++ b/examples/jsm/renderers/webgl-legacy/nodes/WebGLNodeBuilder.js @@ -0,0 +1,792 @@ +import { defaultShaderStages, NodeFrame, MathNode, GLSLNodeParser, NodeBuilder, normalView } from '../../../nodes/Nodes.js'; +import SlotNode from './SlotNode.js'; +import { PerspectiveCamera, ShaderChunk, ShaderLib, UniformsUtils, UniformsLib } from 'three'; + +const nodeFrame = new NodeFrame(); +nodeFrame.camera = new PerspectiveCamera(); + +const nodeShaderLib = { + LineBasicNodeMaterial: ShaderLib.basic, + MeshBasicNodeMaterial: ShaderLib.basic, + PointsNodeMaterial: ShaderLib.points, + MeshStandardNodeMaterial: ShaderLib.standard, + MeshPhysicalNodeMaterial: ShaderLib.physical, + MeshPhongNodeMaterial: ShaderLib.phong +}; + +const glslMethods = { + [ MathNode.ATAN2 ]: 'atan' +}; + +const precisionLib = { + low: 'lowp', + medium: 'mediump', + high: 'highp' +}; + +function getIncludeSnippet( name ) { + + return `#include <${name}>`; + +} + +function getShaderStageProperty( shaderStage ) { + + return `${shaderStage}Shader`; + +} + +class WebGLNodeBuilder extends NodeBuilder { + + constructor( object, renderer, shader, material = null ) { + + super( object, renderer, new GLSLNodeParser(), null, material ); + + this.shader = shader; + this.slots = { vertex: [], fragment: [] }; + + this._parseShaderLib(); + this._parseInclude( 'fragment', 'lights_physical_fragment', 'clearcoat_normal_fragment_begin', 'transmission_fragment' ); + this._parseObject(); + + this._sortSlotsToFlow(); + + } + + getMethod( method ) { + + return glslMethods[ method ] || method; + + } + + addSlot( shaderStage, slotNode ) { + + this.slots[ shaderStage ].push( slotNode ); + + } + + _parseShaderLib() { + + const material = this.material; + + let type = material.type; + + // see https://github.com/mrdoob/three.js/issues/23707 + + if ( material.isMeshPhysicalNodeMaterial ) type = 'MeshPhysicalNodeMaterial'; + else if ( material.isMeshStandardNodeMaterial ) type = 'MeshStandardNodeMaterial'; + else if ( material.isMeshPhongNodeMaterial ) type = 'MeshPhongNodeMaterial'; + else if ( material.isMeshBasicNodeMaterial ) type = 'MeshBasicNodeMaterial'; + else if ( material.isPointsNodeMaterial ) type = 'PointsNodeMaterial'; + else if ( material.isLineBasicNodeMaterial ) type = 'LineBasicNodeMaterial'; + + // shader lib + + if ( nodeShaderLib[ type ] !== undefined ) { + + const shaderLib = nodeShaderLib[ type ]; + const shader = this.shader; + + shader.vertexShader = shaderLib.vertexShader; + shader.fragmentShader = shaderLib.fragmentShader; + shader.uniforms = UniformsUtils.merge( [ shaderLib.uniforms, UniformsLib.lights ] ); + + } + + } + + _parseObject() { + + const { material, renderer } = this; + + this.addSlot( 'fragment', new SlotNode( { + node: normalView, + nodeType: 'vec3', + source: 'void main() {', + target: 'vec3 TransformedNormalView = %RESULT%;', + inclusionType: 'append' + } ) ); + + if ( renderer.toneMappingNode && renderer.toneMappingNode.isNode === true ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.colorNode, + nodeType: 'vec4', + source: getIncludeSnippet( 'tonemapping_fragment' ), + target: '' + } ) ); + + } + + // parse inputs + + if ( material.colorNode && material.colorNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.colorNode, + nodeType: 'vec4', + source: 'vec4 diffuseColor = vec4( diffuse, opacity );', + target: 'vec4 diffuseColor = %RESULT%; diffuseColor.a *= opacity;', + } ) ); + + } + + if ( material.opacityNode && material.opacityNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.opacityNode, + nodeType: 'float', + source: getIncludeSnippet( 'alphatest_fragment' ), + target: 'diffuseColor.a = %RESULT%;', + inclusionType: 'append' + } ) ); + + } + + if ( material.normalNode && material.normalNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.normalNode, + nodeType: 'vec3', + source: getIncludeSnippet( 'normal_fragment_begin' ), + target: 'normal = %RESULT%;', + inclusionType: 'append' + } ) ); + + } + + if ( material.emissiveNode && material.emissiveNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.emissiveNode, + nodeType: 'vec3', + source: getIncludeSnippet( 'emissivemap_fragment' ), + target: 'totalEmissiveRadiance = %RESULT%;', + inclusionType: 'append' + } ) ); + + } + + if ( material.isMeshStandardNodeMaterial ) { + + if ( material.metalnessNode && material.metalnessNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.metalnessNode, + nodeType: 'float', + source: getIncludeSnippet( 'metalnessmap_fragment' ), + target: 'metalnessFactor = %RESULT%;', + inclusionType: 'append' + } ) ); + + } + + if ( material.roughnessNode && material.roughnessNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.roughnessNode, + nodeType: 'float', + source: getIncludeSnippet( 'roughnessmap_fragment' ), + target: 'roughnessFactor = %RESULT%;', + inclusionType: 'append' + } ) ); + + } + + if ( material.isMeshPhysicalNodeMaterial ) { + + if ( material.clearcoatNode && material.clearcoatNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.clearcoatNode, + nodeType: 'float', + source: 'material.clearcoat = clearcoat;', + target: 'material.clearcoat = %RESULT%;' + } ) ); + + if ( material.clearcoatRoughnessNode && material.clearcoatRoughnessNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.clearcoatRoughnessNode, + nodeType: 'float', + source: 'material.clearcoatRoughness = clearcoatRoughness;', + target: 'material.clearcoatRoughness = %RESULT%;' + } ) ); + + } + + if ( material.clearcoatNormalNode && material.clearcoatNormalNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.clearcoatNormalNode, + nodeType: 'vec3', + source: 'vec3 clearcoatNormal = nonPerturbedNormal;', + target: 'vec3 clearcoatNormal = %RESULT%;' + } ) ); + + } + + material.defines.USE_CLEARCOAT = ''; + + } else { + + delete material.defines.USE_CLEARCOAT; + + } + + if ( material.sheenNode && material.sheenNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.sheenNode, + nodeType: 'vec3', + source: 'material.sheenColor = sheenColor;', + target: 'material.sheenColor = %RESULT%;' + } ) ); + + if ( material.sheenRoughnessNode && material.sheenRoughnessNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.sheenRoughnessNode, + nodeType: 'float', + source: 'material.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );', + target: 'material.sheenRoughness = clamp( %RESULT%, 0.07, 1.0 );' + } ) ); + + } + + material.defines.USE_SHEEN = ''; + + } else { + + delete material.defines.USE_SHEEN; + + } + + if ( material.iridescenceNode && material.iridescenceNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.iridescenceNode, + nodeType: 'float', + source: 'material.iridescence = iridescence;', + target: 'material.iridescence = %RESULT%;' + } ) ); + + if ( material.iridescenceIORNode && material.iridescenceIORNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.iridescenceIORNode, + nodeType: 'float', + source: 'material.iridescenceIOR = iridescenceIOR;', + target: 'material.iridescenceIOR = %RESULT%;' + } ) ); + + } + + if ( material.iridescenceThicknessNode && material.iridescenceThicknessNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.iridescenceThicknessNode, + nodeType: 'float', + source: 'material.iridescenceThickness = iridescenceThicknessMaximum;', + target: 'material.iridescenceThickness = %RESULT%;' + } ) ); + + } + + material.defines.USE_IRIDESCENCE = ''; + + } else { + + delete material.defines.USE_IRIDESCENCE; + + } + + if ( material.iorNode && material.iorNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.iorNode, + nodeType: 'float', + source: 'material.ior = ior;', + target: 'material.ior = %RESULT%;' + } ) ); + + } + + if ( material.specularColorNode && material.specularColorNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.specularColorNode, + nodeType: 'vec3', + source: 'vec3 specularColorFactor = specularColor;', + target: 'vec3 specularColorFactor = %RESULT%;' + } ) ); + + } + + if ( material.specularIntensityNode && material.specularIntensityNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.specularIntensityNode, + nodeType: 'float', + source: 'float specularIntensityFactor = specularIntensity;', + target: 'float specularIntensityFactor = %RESULT%;' + } ) ); + + } + + if ( material.transmissionNode && material.transmissionNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.transmissionNode, + nodeType: 'float', + source: 'material.transmission = transmission;', + target: 'material.transmission = %RESULT%;' + } ) ); + + if ( material.thicknessNode && material.thicknessNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.thicknessNode, + nodeType: 'float', + source: 'material.thickness = thickness;', + target: 'material.thickness = %RESULT%;' + } ) ); + + } + + if ( material.attenuationDistanceNode && material.attenuationDistanceNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.attenuationDistanceNode, + nodeType: 'float', + source: 'material.attenuationDistance = attenuationDistance;', + target: 'material.attenuationDistance = %RESULT%;' + } ) ); + + } + + if ( material.attenuationColorNode && material.attenuationColorNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( { + node: material.attenuationColorNode, + nodeType: 'vec3', + source: 'material.attenuationColor = attenuationColor;', + target: 'material.attenuationColor = %RESULT%;' + } ) ); + + } + + material.transmission = 1; + material.defines.USE_TRANSMISSION = ''; + + } else { + + material.transmission = 0; + delete material.defines.USE_TRANSMISSION; + + } + + } + + } + + // + + if ( material.positionNode && material.positionNode.isNode ) { + + this.addSlot( 'vertex', new SlotNode( { + node: material.positionNode, + nodeType: 'vec3', + source: getIncludeSnippet( 'begin_vertex' ), + target: 'transformed = %RESULT%;', + inclusionType: 'append' + } ) ); + + } + + if ( material.sizeNode && material.sizeNode.isNode ) { + + this.addSlot( 'vertex', new SlotNode( { + node: material.sizeNode, + nodeType: 'float', + source: 'gl_PointSize = size;', + target: 'gl_PointSize = %RESULT%;' + } ) ); + + } + + } + + generateTexture( texture, textureProperty, uvSnippet ) { + + if ( texture.isTextureCube ) { + + return `textureCube( ${textureProperty}, ${uvSnippet} )`; + + } else { + + return `texture2D( ${textureProperty}, ${uvSnippet} )`; + + } + + } + + generateTextureLevel( texture, textureProperty, uvSnippet, biasSnippet ) { + + return `textureLod( ${textureProperty}, ${uvSnippet}, ${biasSnippet} )`; + + } + + buildFunctionCode( shaderNode ) { + + const layout = shaderNode.layout; + const flowData = this.flowShaderNode( shaderNode ); + + const parameters = []; + + for ( const input of layout.inputs ) { + + parameters.push( this.getType( input.type ) + ' ' + input.name ); + + } + + // + + const code = `${ this.getType( layout.type ) } ${ layout.name }( ${ parameters.join( ', ' ) } ) { + + ${ flowData.vars } + +${ flowData.code } + return ${ flowData.result }; + +}`; + + // + + return code; + + } + + getUniforms( shaderStage ) { + + const uniforms = this.uniforms[ shaderStage ]; + + let output = ''; + + for ( const uniform of uniforms ) { + + if ( /^(modelViewMatrix|projectionMatrix)$/.test( uniform.name ) ) + continue; + + let snippet = null; + + if ( uniform.type === 'texture' ) { + + snippet = `sampler2D ${uniform.name}; `; + + } else if ( uniform.type === 'cubeTexture' ) { + + snippet = `samplerCube ${uniform.name}; `; + + } else { + + const vectorType = this.getVectorType( uniform.type ); + + snippet = `${vectorType} ${uniform.name}; `; + + } + + const precision = uniform.node.precision; + + if ( precision !== null ) { + + snippet = 'uniform ' + precisionLib[ precision ] + ' ' + snippet; + + } else { + + snippet = 'uniform ' + snippet; + + } + + output += snippet; + + } + + return output; + + } + + getAttributes( shaderStage ) { + + let snippet = ''; + + if ( shaderStage === 'vertex' ) { + + const attributes = this.attributes; + + for ( const attribute of attributes ) { + + // ignore common attributes to prevent redefinitions + if ( /^(position|normal|uv[1-3]?)$/.test( attribute.name ) ) + continue; + + snippet += `attribute ${attribute.type} ${attribute.name}; `; + + } + + } + + return snippet; + + } + + getVaryings( shaderStage ) { + + let snippet = ''; + + const varyings = this.varyings; + + if ( shaderStage === 'vertex' ) { + + for ( const varying of varyings ) { + + snippet += `${varying.needsInterpolation ? 'varying' : '/*varying*/'} ${varying.type} ${varying.name}; `; + + } + + } else if ( shaderStage === 'fragment' ) { + + for ( const varying of varyings ) { + + if ( varying.needsInterpolation ) { + + snippet += `varying ${varying.type} ${varying.name}; `; + + } + + } + + } + + return snippet; + + } + + addCode( shaderStage, source, code, scope = this ) { + + const shaderProperty = getShaderStageProperty( shaderStage ); + + let snippet = scope[ shaderProperty ]; + + const index = snippet.indexOf( source ); + + if ( index !== - 1 ) { + + const start = snippet.substring( 0, index + source.length ); + const end = snippet.substring( index + source.length ); + + snippet = `${start}\n${code}\n${end}`; + + } + + scope[ shaderProperty ] = snippet; + + } + + replaceCode( shaderStage, source, target, scope = this ) { + + const shaderProperty = getShaderStageProperty( shaderStage ); + + scope[ shaderProperty ] = scope[ shaderProperty ].replaceAll( source, target ); + + } + + getVertexIndex() { + + return 'gl_VertexID'; + + } + + getFrontFacing() { + + return 'gl_FrontFacing'; + + } + + getFragCoord() { + + return 'gl_FragCoord'; + + } + + isFlipY() { + + return true; + + } + + buildCode() { + + const shaderData = {}; + + for ( const shaderStage of defaultShaderStages ) { + + const uniforms = this.getUniforms( shaderStage ); + const attributes = this.getAttributes( shaderStage ); + const varyings = this.getVaryings( shaderStage ); + const vars = this.getVars( shaderStage ); + const codes = this.getCodes( shaderStage ); + + shaderData[ shaderStage ] = `${this.getSignature()} +// + +// uniforms +${uniforms} + +// attributes +${attributes} + +// varyings +${varyings} + +// vars +${vars} + +// codes +${codes} + +// + +${this.shader[ getShaderStageProperty( shaderStage ) ]} +`; + + } + + this.vertexShader = shaderData.vertex; + this.fragmentShader = shaderData.fragment; + + } + + build() { + + super.build( false ); + + this._addSnippets(); + this._addUniforms(); + + this._updateUniforms(); + + this.shader.vertexShader = this.vertexShader; + this.shader.fragmentShader = this.fragmentShader; + + return this; + + } + + _parseInclude( shaderStage, ...includes ) { + + for ( const name of includes ) { + + const includeSnippet = getIncludeSnippet( name ); + const code = ShaderChunk[ name ]; + + const shaderProperty = getShaderStageProperty( shaderStage ); + + this.shader[ shaderProperty ] = this.shader[ shaderProperty ].replaceAll( includeSnippet, code ); + + } + + } + + _sortSlotsToFlow() { + + for ( const shaderStage of defaultShaderStages ) { + + const sourceCode = this.shader[ getShaderStageProperty( shaderStage ) ]; + + const slots = this.slots[ shaderStage ].sort( ( slotA, slotB ) => { + + return sourceCode.indexOf( slotA.source ) > sourceCode.indexOf( slotB.source ) ? 1 : - 1; + + } ); + + for ( const slotNode of slots ) { + + this.addFlow( shaderStage, slotNode ); + + } + + } + + } + + _addSnippets() { + + for ( const shaderStage of defaultShaderStages ) { + + for ( const slotNode of this.slots[ shaderStage ] ) { + + const flowData = this.getFlowData( slotNode/*, shaderStage*/ ); + + const inclusionType = slotNode.inclusionType; + const source = slotNode.source; + const target = flowData.code + '\n\t' + slotNode.target.replace( '%RESULT%', flowData.result ); + + if ( inclusionType === 'append' ) { + + this.addCode( shaderStage, source, target ); + + } else if ( inclusionType === 'replace' ) { + + this.replaceCode( shaderStage, source, target ); + + } else { + + console.warn( `Inclusion type "${ inclusionType }" not compatible.` ); + + } + + } + + this.addCode( + shaderStage, + 'main() {', + '\n\t' + this.flowCode[ shaderStage ] + ); + + } + + } + + _addUniforms() { + + for ( const shaderStage of defaultShaderStages ) { + + // uniforms + + for ( const uniform of this.uniforms[ shaderStage ] ) { + + this.shader.uniforms[ uniform.name ] = uniform; + + } + + } + + } + + _updateUniforms() { + + nodeFrame.object = this.object; + nodeFrame.renderer = this.renderer; + + for ( const node of this.updateNodes ) { + + nodeFrame.updateNode( node ); + + } + + } + +} + +export { WebGLNodeBuilder }; diff --git a/examples/jsm/renderers/webgl-legacy/nodes/WebGLNodes.js b/examples/jsm/renderers/webgl-legacy/nodes/WebGLNodes.js new file mode 100644 index 00000000000000..0f5a00f3c07878 --- /dev/null +++ b/examples/jsm/renderers/webgl-legacy/nodes/WebGLNodes.js @@ -0,0 +1,51 @@ +import { WebGLNodeBuilder } from './WebGLNodeBuilder.js'; +import { NodeFrame } from '../../../nodes/Nodes.js'; + +import { Material } from 'three'; + +const builders = new WeakMap(); +export const nodeFrame = new NodeFrame(); + +Material.prototype.onBuild = function ( object, parameters, renderer ) { + + const material = this; + + if ( material.isNodeMaterial === true ) { + + builders.set( material, new WebGLNodeBuilder( object, renderer, parameters, material ).build() ); + + } + +}; + +Material.prototype.onBeforeRender = function ( renderer, scene, camera, geometry, object ) { + + const nodeBuilder = builders.get( this ); + + if ( nodeBuilder !== undefined ) { + + nodeFrame.material = this; + nodeFrame.camera = camera; + nodeFrame.object = object; + nodeFrame.renderer = renderer; + + const updateNodes = nodeBuilder.updateNodes; + + if ( updateNodes.length > 0 ) { + + // force refresh material uniforms + renderer.state.useProgram( null ); + + //this.uniformsNeedUpdate = true; + + for ( const node of updateNodes ) { + + nodeFrame.updateNode( node ); + + } + + } + + } + +};