UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

228 lines (208 loc) 7.47 kB
/** * Allows to build a post pass with [gl nodes](/docs/nodes/gl) * * */ import {TypedPostNode, TypedPostNodeContext, PostParamOptions} from './_Base'; import {EffectPass} from 'postprocessing'; import {BuilderEffect} from './utils/BuilderEffect'; import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig'; import {GlobalsGeometryHandler} from '../gl/code/globals/Geometry'; import {GlNodeChildrenMap} from '../../poly/registers/nodes/Gl'; import {NodeCreateOptions} from '../utils/hierarchy/ChildrenController'; import {BaseGlNodeType} from '../gl/_Base'; import {GlNodeFinder} from '../gl/code/utils/NodeFinder'; import {IUniformsWithTime} from '../../scene/utils/UniformsController'; import {IUniforms} from '../../../core/geometry/Material'; import {valueof, Constructor} from './../../../types/GlobalTypes'; import {Poly} from '../../Poly'; import {AssemblerName} from './../../poly/registers/assemblers/_BaseRegister'; import {PostPersistedConfig} from './../gl/code/assemblers/post/PostPersistedConfig'; import {PostType} from './../../poly/registers/nodes/types/Post'; import defaultFragmentShader from './gl/builder.glsl'; import {NodeContext} from '../../poly/NodeContext'; import {GlAssemblerController} from '../gl/code/Controller'; import {ShaderAssemblerPost} from '../gl/code/assemblers/post/Post'; class BuilderPostParamsConfig extends NodeParamsConfig { /** @param effect amount */ useInput1OuputBuffer = ParamConfig.BOOLEAN(1, { ...PostParamOptions, }); } const ParamsConfig = new BuilderPostParamsConfig(); export class BuilderPostNode extends TypedPostNode<EffectPass, BuilderPostParamsConfig> { override paramsConfig = ParamsConfig; static override type() { return PostType.BUILDER; } override readonly persisted_config: PostPersistedConfig = new PostPersistedConfig(this); protected _assemblerController = this._createAssemblerController(); public override usedAssembler(): Readonly<AssemblerName.GL_POST> { return AssemblerName.GL_POST; } protected _createAssemblerController(): GlAssemblerController<ShaderAssemblerPost> | undefined { const assemblerController: GlAssemblerController<ShaderAssemblerPost> | undefined = Poly.assemblersRegister.assembler(this, this.usedAssembler()); if (assemblerController) { const globalsHandler = new GlobalsGeometryHandler(); assemblerController.setAssemblerGlobalsHandler(globalsHandler); return assemblerController; } } assemblerController() { return this._assemblerController; } protected override _childrenControllerContext = NodeContext.GL; override initializeNode() { super.initializeNode(); this.io.inputs.setCount(0, 2); // this ensures the builder recooks when its children are changed // and not just when a material that use it requests it this.addPostDirtyHook('_cook_main_without_inputs_when_dirty', () => { // setTimeout(this._cook_main_without_inputs_when_dirty_bound, 0); this.compileIfRequired(); }); } override createNode<S extends keyof GlNodeChildrenMap>( node_class: S, options?: NodeCreateOptions ): GlNodeChildrenMap[S]; override createNode<K extends valueof<GlNodeChildrenMap>>( node_class: Constructor<K>, options?: NodeCreateOptions ): K; override createNode<K extends valueof<GlNodeChildrenMap>>( node_class: Constructor<K>, options?: NodeCreateOptions ): K { return super.createNode(node_class, options) as K; } override children() { return super.children() as BaseGlNodeType[]; } override nodesByType<K extends keyof GlNodeChildrenMap>(type: K): GlNodeChildrenMap[K][] { return super.nodesByType(type) as GlNodeChildrenMap[K][]; } override childrenAllowed() { if (this.assemblerController()) { return super.childrenAllowed(); } return false; } override sceneReadonly() { return this.assemblerController() == null; } // private _cook_main_without_inputs_when_dirty_bound = this._cook_main_without_inputs_when_dirty.bind(this); // private async _cook_main_without_inputs_when_dirty() { // await this.cookController.cookMainWithoutInputs(); // } // override cook() { // this.compileIfRequired(); // this.cookController.endCook(); // } /** * * FRAGMENT SHADER UPDATE * */ private _fragmentShader: string = defaultFragmentShader; private _uniforms: IUniforms = {}; fragmentShader() { return this._fragmentShader; } setFragmentShader(fragmentShader: string) { this._fragmentShader = fragmentShader; this._updatePasses(); } uniforms() { return this._uniforms; } setUniforms(uniforms: IUniforms) { this._uniforms = uniforms; this._updatePasses(); } shaders_by_name() { return { fragment: this._fragmentShader, }; } compileIfRequired() { if (this.assemblerController()?.compileRequired()) { try { this.compile(); } catch (err) { const message = (err as any).message || 'failed to compile'; this.states.error.set(message); } } } private compile() { const assemblerController = this.assemblerController(); if (!assemblerController) { return; } const outputNodes: BaseGlNodeType[] = GlNodeFinder.findOutputNodes(this); if (outputNodes.length == 0) { this.states.error.set('one output node is required'); return; } if (outputNodes.length > 1) { this.states.error.set('only one output node allowed'); return; } const outputNode = outputNodes[0]; if (outputNode) { //const param_nodes = GlNodeFinder.find_param_generating_nodes(this); const rootNodes = outputNodes; //.concat(param_nodes); assemblerController.assembler.set_root_nodes(rootNodes); // main compilation assemblerController.assembler.updateFragmentShader(); // receives fragment and uniforms const fragmentShader = assemblerController.assembler.fragment_shader(); const uniforms = assemblerController.assembler.uniforms(); if (fragmentShader && uniforms) { this._fragmentShader = fragmentShader; this._uniforms = uniforms; } BuilderPostNode.handleDependencies(this, assemblerController.assembler.uniformsTimeDependent()); } if (this._fragmentShader && this._uniforms) { this._updatePasses(); } assemblerController.post_compile(); } static handleDependencies(node: BuilderPostNode, timeDependent: boolean, uniforms?: IUniformsWithTime) {} /** * * PASS CREATE / UPDATE * */ override createPass(context: TypedPostNodeContext) { this.compileIfRequired(); const composerInput1 = this._createEffectComposer(context); composerInput1.autoRenderToScreen = false; const clonedContextInput1 = {...context}; clonedContextInput1.composer = composerInput1; this._addPassFromInput(1, clonedContextInput1); const effect = new BuilderEffect(composerInput1, { fragmentShader: this._fragmentShader, useOutputBuffer: this.pv.useInput1OuputBuffer, }); const pass = new EffectPass(context.camera, effect); // pass.needsSwap = false; // pass.renderToScreen = false; // console.log(pass.needsSwap, pass.renderToScreen); this.updatePass(pass); return pass; } override updatePass(pass: EffectPass) { const effect = (pass as any).effects[0] as BuilderEffect; effect.updateUniforms(this._uniforms); effect.updateFragmentShader(this._fragmentShader); pass.fullscreenMaterial.needsUpdate = true; } protected _createEffectComposer(context: TypedPostNodeContext) { const parentNode = this._postProcessNetworkNode(); return parentNode.effectsComposerController.createEffectsComposer(context); } }