UNPKG

polygonjs-engine

Version:

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

319 lines (286 loc) 10.8 kB
/** * Allows to create a shader with GLSL nodes to create the texture values. * * */ import {Constructor, valueof, Number2} from '../../../types/GlobalTypes'; import {WebGLRenderTarget} from 'three/src/renderers/WebGLRenderTarget'; import {ShaderMaterial} from 'three/src/materials/ShaderMaterial'; import {Scene} from 'three/src/scenes/Scene'; import { FloatType, HalfFloatType, RGBAFormat, NearestFilter, LinearFilter, ClampToEdgeWrapping, } from 'three/src/constants'; import {PlaneBufferGeometry} from 'three/src/geometries/PlaneBufferGeometry'; import {Mesh} from 'three/src/objects/Mesh'; import {Camera} from 'three/src/cameras/Camera'; import {TypedCopNode} from './_Base'; // import {CoreGraphNode} from '../../../core/graph/CoreGraphNode'; import {GlobalsGeometryHandler} from '../gl/code/globals/Geometry'; import {GlNodeChildrenMap} from '../../poly/registers/nodes/Gl'; import {BaseGlNodeType} from '../gl/_Base'; import {GlNodeFinder} from '../gl/code/utils/NodeFinder'; import {NodeContext} from '../../poly/NodeContext'; import {IUniform} from 'three/src/renderers/shaders/UniformsLib'; export interface IUniforms { [uniform: string]: IUniform; } const VERTEX_SHADER = ` void main() { gl_Position = vec4( position, 1.0 ); } `; const RESOLUTION_DEFAULT: Number2 = [256, 256]; import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig'; import {DataTextureController, DataTextureControllerBufferType} from './utils/DataTextureController'; import {CopRendererController} from './utils/RendererController'; import {AssemblerName} from '../../poly/registers/assemblers/_BaseRegister'; import {Poly} from '../../Poly'; import {TexturePersistedConfig} from '../gl/code/assemblers/textures/PersistedConfig'; import {IUniformsWithTime} from '../../scene/utils/UniformsController'; import {ParamsInitData} from '../utils/io/IOController'; class BuilderCopParamsConfig extends NodeParamsConfig { /** @param texture resolution */ resolution = ParamConfig.VECTOR2(RESOLUTION_DEFAULT); /** @param defines if the shader is rendered via the same camera used to render the scene */ useCameraRenderer = ParamConfig.BOOLEAN(0); } const ParamsConfig = new BuilderCopParamsConfig(); export class BuilderCopNode extends TypedCopNode<BuilderCopParamsConfig> { params_config = ParamsConfig; static type() { return 'builder'; } readonly persisted_config: TexturePersistedConfig = new TexturePersistedConfig(this); protected _assembler_controller = this._create_assembler_controller(); public usedAssembler(): Readonly<AssemblerName.GL_TEXTURE> { return AssemblerName.GL_TEXTURE; } protected _create_assembler_controller() { const assembler_controller = Poly.assemblersRegister.assembler(this, this.usedAssembler()); if (assembler_controller) { const globals_handler = new GlobalsGeometryHandler(); assembler_controller.set_assembler_globals_handler(globals_handler); return assembler_controller; } } get assemblerController() { return this._assembler_controller; } private _texture_mesh: Mesh = new Mesh(new PlaneBufferGeometry(2, 2)); private _fragment_shader: string | undefined; private _uniforms: IUniforms | undefined; public readonly texture_material: ShaderMaterial = new ShaderMaterial({ uniforms: {}, vertexShader: VERTEX_SHADER, fragmentShader: '', }); private _texture_scene: Scene = new Scene(); private _texture_camera: Camera = new Camera(); private _render_target: WebGLRenderTarget | undefined; private _data_texture_controller: DataTextureController | undefined; private _renderer_controller: CopRendererController | undefined; protected _children_controller_context = NodeContext.GL; initializeNode() { this._texture_mesh.material = this.texture_material; this._texture_mesh.scale.multiplyScalar(0.25); this._texture_scene.add(this._texture_mesh); this._texture_camera.position.z = 1; // 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.dirtyController.addPostDirtyHook( // '_reset_if_resolution_changed', // this._reset_if_resolution_changed.bind(this) // ); // this.params.onParamsCreated('reset', () => { // this._reset(); // }); } createNode<S extends keyof GlNodeChildrenMap>( node_class: S, params_init_value_overrides?: ParamsInitData ): GlNodeChildrenMap[S]; createNode<K extends valueof<GlNodeChildrenMap>>( node_class: Constructor<K>, params_init_value_overrides?: ParamsInitData ): K; createNode<K extends valueof<GlNodeChildrenMap>>( node_class: Constructor<K>, params_init_value_overrides?: ParamsInitData ): K { return super.createNode(node_class, params_init_value_overrides) as K; } children() { return super.children() as BaseGlNodeType[]; } nodesByType<K extends keyof GlNodeChildrenMap>(type: K): GlNodeChildrenMap[K][] { return super.nodesByType(type) as GlNodeChildrenMap[K][]; } childrenAllowed() { if (this.assemblerController) { return super.childrenAllowed(); } this.scene().markAsReadOnly(this); return false; } 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.cook_main_without_inputs(); } // private _reset_if_resolution_changed(trigger?: CoreGraphNode) { // if (trigger && trigger.graphNodeId() == this.p.resolution.graphNodeId()) { // this._reset(); // } // } async cook() { this.compile_if_required(); this.render_on_target(); } shaders_by_name() { return { fragment: this._fragment_shader, }; } compile_if_required() { if (this.assemblerController?.compile_required()) { this.compile(); } } private compile() { const assemblerController = this.assemblerController; if (!assemblerController) { return; } const output_nodes: BaseGlNodeType[] = GlNodeFinder.find_output_nodes(this); if (output_nodes.length > 1) { this.states.error.set('only one output node allowed'); return; } const output_node = output_nodes[0]; if (output_node) { //const param_nodes = GlNodeFinder.find_param_generating_nodes(this); const root_nodes = output_nodes; //.concat(param_nodes); assemblerController.assembler.set_root_nodes(root_nodes); // main compilation assemblerController.assembler.update_fragment_shader(); // receives fragment and uniforms const fragment_shader = assemblerController.assembler.fragment_shader(); const uniforms = assemblerController.assembler.uniforms(); if (fragment_shader && uniforms) { this._fragment_shader = fragment_shader; this._uniforms = uniforms; } BuilderCopNode.handle_dependencies(this, assemblerController.assembler.uniforms_time_dependent()); } if (this._fragment_shader && this._uniforms) { this.texture_material.fragmentShader = this._fragment_shader; this.texture_material.uniforms = this._uniforms; this.texture_material.needsUpdate = true; this.texture_material.uniforms.resolution = { value: this.pv.resolution, }; } assemblerController.post_compile(); } static handle_dependencies(node: BuilderCopNode, time_dependent: boolean, uniforms?: IUniformsWithTime) { // That's actually useless, since this doesn't make the texture recook const scene = node.scene(); const id = node.graphNodeId(); const id_s = `${id}`; if (time_dependent) { // TODO: remove this once the scene knows how to re-render // the render target if it is .uniforms_time_dependent() node.states.time_dependent.force_time_dependent(); if (uniforms) { scene.uniforms_controller.add_time_dependent_uniform_owner(id_s, uniforms); } } else { node.states.time_dependent.unforce_time_dependent(); scene.uniforms_controller.remove_time_dependent_uniform_owner(id_s); } } async render_on_target() { this.create_render_target_if_required(); if (!this._render_target) { return; } this._renderer_controller = this._renderer_controller || new CopRendererController(this); const renderer = await this._renderer_controller.renderer(); const prev_target = renderer.getRenderTarget(); renderer.setRenderTarget(this._render_target); renderer.clear(); renderer.render(this._texture_scene, this._texture_camera); renderer.setRenderTarget(prev_target); if (this._render_target.texture) { if (this.pv.useCameraRenderer) { this.set_texture(this._render_target.texture); } else { // const w = this.pv.resolution.x; // const h = this.pv.resolution.y; // this._data_texture = this._data_texture || this._create_data_texture(w, h); // renderer.readRenderTargetPixels(this._render_target, 0, 0, w, h, this._data_texture.image.data); // this._data_texture.needsUpdate = true; this._data_texture_controller = this._data_texture_controller || new DataTextureController(DataTextureControllerBufferType.Float32Array); const data_texture = this._data_texture_controller.from_render_target(renderer, this._render_target); this.set_texture(data_texture); } } else { this.cookController.end_cook(); } } render_target() { return (this._render_target = this._render_target || this._create_render_target(this.pv.resolution.x, this.pv.resolution.y)); } private create_render_target_if_required() { if (!this._render_target || !this._render_target_resolution_valid()) { this._render_target = this._create_render_target(this.pv.resolution.x, this.pv.resolution.y); this._data_texture_controller?.reset(); } } private _render_target_resolution_valid() { if (this._render_target) { const image = this._render_target.texture.image; if (image.width != this.pv.resolution.x || image.height != this.pv.resolution.y) { return false; } else { return true; } } else { return false; } } private _create_render_target(width: number, height: number) { if (this._render_target) { const image = this._render_target.texture.image; if (image.width == width && image.height == height) { return this._render_target; } } const wrapS = ClampToEdgeWrapping; const wrapT = ClampToEdgeWrapping; const minFilter = LinearFilter; const magFilter = NearestFilter; var renderTarget = new WebGLRenderTarget(width, height, { wrapS: wrapS, wrapT: wrapT, minFilter: minFilter, magFilter: magFilter, format: RGBAFormat, type: /(iPad|iPhone|iPod)/g.test(navigator.userAgent) ? HalfFloatType : FloatType, stencilBuffer: false, depthBuffer: false, }); Poly.warn('created render target', this.fullPath(), width, height); return renderTarget; } }