You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

756 lines
16 KiB

import { defaultShaderStages, NodeFrame, MathNode, GLSLNodeParser, NodeBuilder } from 'three/nodes';
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
};
const glslMethods = {
[ MathNode.ATAN2 ]: 'atan'
};
function getIncludeSnippet( name ) {
return `#include <${name}>`;
}
function getShaderStageProperty( shaderStage ) {
return `${shaderStage}Shader`;
}
class WebGLNodeBuilder extends NodeBuilder {
constructor( object, renderer, shader ) {
super( object, renderer, new GLSLNodeParser() );
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 );
}
addFlowCode( code ) {
if ( ! /;\s*$/.test( code ) ) {
code += ';';
}
super.addFlowCode( code + '\n\t' );
}
_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.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;
if ( 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%;'
} ) );
}
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'
} ) );
} else {
this.addCode( 'fragment', getIncludeSnippet( 'alphatest_fragment' ), 'diffuseColor.a = opacity;', this.shader );
}
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 = geometryNormal;',
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.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%;'
} ) );
}
}
getTexture( textureProperty, uvSnippet ) {
return `texture2D( ${textureProperty}, ${uvSnippet} )`;
}
getTextureBias( textureProperty, uvSnippet, biasSnippet ) {
if ( this.material.extensions !== undefined ) this.material.extensions.shaderTextureLOD = true;
return `textureLod( ${textureProperty}, ${uvSnippet}, ${biasSnippet} )`;
}
getCubeTexture( textureProperty, uvSnippet ) {
return `textureCube( ${textureProperty}, ${uvSnippet} )`;
}
getCubeTextureBias( textureProperty, uvSnippet, biasSnippet ) {
if ( this.material.extensions !== undefined ) this.material.extensions.shaderTextureLOD = true;
return `textureLod( ${textureProperty}, ${uvSnippet}, ${biasSnippet} )`;
}
getUniforms( shaderStage ) {
const uniforms = this.uniforms[ shaderStage ];
let snippet = '';
for ( const uniform of uniforms ) {
if ( uniform.type === 'texture' ) {
snippet += `uniform sampler2D ${uniform.name}; `;
} else if ( uniform.type === 'cubeTexture' ) {
snippet += `uniform samplerCube ${uniform.name}; `;
} else {
const vectorType = this.getVectorType( uniform.type );
snippet += `uniform ${vectorType} ${uniform.name}; `;
}
}
return snippet;
}
getAttributes( shaderStage ) {
let snippet = '';
if ( shaderStage === 'vertex' ) {
const attributes = this.attributes;
for ( const attribute of attributes ) {
// ignore common attributes to prevent redefinitions
if ( attribute.name === 'uv' || attribute.name === 'position' || attribute.name === 'normal' )
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 );
}
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()}
// <node_builder>
// uniforms
${uniforms}
// attributes
${attributes}
// varyings
${varyings}
// vars
${vars}
// codes
${codes}
// </node_builder>
${this.shader[ getShaderStageProperty( shaderStage ) ]}
`;
}
this.vertexShader = shaderData.vertex;
this.fragmentShader = shaderData.fragment;
}
build() {
super.build();
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 };