UNPKG

@polygonjs/polygonjs

Version:

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

254 lines (253 loc) 8.8 kB
"use strict"; import { WebGLRenderer, WebGLRenderTarget, OrthographicCamera, FloatType, HalfFloatType, RGBAFormat, NearestFilter, LinearFilter, ClampToEdgeWrapping, PerspectiveCamera, Vector2 } from "three"; import { TypedCopNode } from "./_Base"; import { NodeParamsConfig, ParamConfig } from "../utils/params/ParamsConfig"; import { CopRendererController } from "./utils/RendererController"; import { isBooleanTrue } from "../../../core/BooleanValue"; import { DataTextureController, DataTextureControllerBufferType } from "./utils/DataTextureController"; import { TextureParamsController, TextureParamConfig } from "./utils/TextureParamsController"; import { CoreUserAgent } from "../../../core/UserAgent"; import { Poly } from "../../Poly"; import { CoreCameraPerspectiveFrameMode } from "../../../core/camera/frameMode/CoreCameraPerspectiveFrameMode"; import { CoreCameraOrthographicFrameMode } from "../../../core/camera/frameMode/CoreCameraOrthographicFrameMode"; import { CopType } from "../../poly/registers/nodes/types/Cop"; const _v2 = new Vector2(); export function RenderCopNodeParamConfig(Base) { return class Mixin extends Base { constructor() { super(...arguments); /** @param path to the main camera object that will be used when the scene loads outside of the editor */ this.cameraPath = ParamConfig.STRING("*perspectiveCamera*", { objectMask: true }); /** @param transparent background */ this.transparentBackground = ParamConfig.BOOLEAN(1); /** @param bg Color */ this.backgroundColor = ParamConfig.COLOR([0, 0, 0], { visibleIf: { transparentBackground: 0 } }); /** @param use same resolution as renderer */ this.useRendererRes = ParamConfig.BOOLEAN(1); /** @param render resolution */ this.resolution = ParamConfig.VECTOR2([1024, 1024], { visibleIf: { useRendererRes: 0 } }); /** @param use a data texture instead of a render target, which can be useful when using that texture as and envMap */ this.useDataTexture = ParamConfig.BOOLEAN(0); /** @param autoRender */ this.autoRender = ParamConfig.BOOLEAN(1); /** @param render button */ this.render = ParamConfig.BUTTON(null, { callback: (node) => { RenderCopNode.PARAM_CALLBACK_render(node); } }); } }; } class RenderCopParamConfig extends TextureParamConfig(RenderCopNodeParamConfig(NodeParamsConfig)) { } const ParamsConfig = new RenderCopParamConfig(); export class RenderCopNode extends TypedCopNode { constructor() { super(...arguments); this.paramsConfig = ParamsConfig; this.textureParamsController = new TextureParamsController(this); this._renderTargetByRenderer = /* @__PURE__ */ new WeakMap(); this._renderOnTargetBound = this.renderOnTarget.bind(this); } static type() { return CopType.RENDER; } async cook() { if (isBooleanTrue(this.pv.autoRender)) { this._addOnBeforeTickCallback(); } else { this._removeOnBeforeTickCallback(); } const camera = await this._getCamera(); if (camera) { await this.renderOnTarget(); } else { this.cookController.endCook(); } } // // // AUTO RENDER // // _addOnBeforeTickCallback() { const callbackName = this._onBeforeRenderCallbackName(); if (this.scene().hasBeforeTickCallback(callbackName)) { return; } this.scene().registerOnBeforeTick(callbackName, this._renderOnTargetBound); } _removeOnBeforeTickCallback() { this.scene().unRegisterOnBeforeTick(this._onBeforeRenderCallbackName()); } _onBeforeRenderCallbackName() { return `cop/render_onBeforeTickCallback-${this.graphNodeId()}`; } dispose() { super.dispose(); this._removeOnBeforeTickCallback(); } // // // RENDER + RENDER TARGET // // _getCameraSync() { return this.scene().objectsController.findObjectByMask(this.pv.cameraPath); } async _getCamera() { const camera = this._getCameraSync(); if (camera) { return camera; } return new Promise((resolve) => { const onCameraUpdated = async () => { const camera2 = this._getCameraSync(); this.scene().camerasController.removeOnCameraObjectsUpdated(onCameraUpdated); if (camera2) { resolve(camera2); } }; this.scene().camerasController.onCameraObjectsUpdated(onCameraUpdated); }); } async renderOnTarget() { const camera = await this._getCamera(); if (!camera) { this.states.error.set(`no camera found`); return; } this._rendererController = this._rendererController || new CopRendererController(this); const renderer = await this._rendererController.waitForRenderer(); if (!(renderer instanceof WebGLRenderer)) { this.states.error.set(`no renderer found`); return; } const renderTarget = await this.createRenderTargetIfRequired(renderer); this._ensureRenderTargetResolutionValid(renderer, renderTarget); const viewer = this.scene().viewersRegister.lastRenderedViewer(); if (!viewer) { this.states.error.set(`no viewer found`); return; } const viewerCamera = viewer.camera(); if (!viewerCamera) { this.states.error.set(`no viewer camera found`); return; } this._requestedResolution(renderer, _v2); const aspect = _v2.x / _v2.y; if (camera instanceof PerspectiveCamera && viewerCamera instanceof PerspectiveCamera) { CoreCameraPerspectiveFrameMode.updateCameraAspect(camera, aspect, { cameraWithAttributes: viewerCamera }); } else { if (camera instanceof OrthographicCamera && viewerCamera instanceof OrthographicCamera) { CoreCameraOrthographicFrameMode.updateCameraAspect(camera, aspect, { cameraWithAttributes: viewerCamera }); } } const scene = this.scene().threejsScene(); const prevTarget = renderer.getRenderTarget(); const prevColorSpace = renderer.outputColorSpace; const prevBackground = scene.background; scene.background = this.pv.transparentBackground ? null : this.pv.backgroundColor; renderer.setRenderTarget(renderTarget); renderer.outputColorSpace = this.pv.colorSpace; renderer.clear(); renderer.render(scene, camera); renderer.setRenderTarget(prevTarget); renderer.outputColorSpace = prevColorSpace; scene.background = prevBackground; if (renderTarget.texture) { if (isBooleanTrue(this.pv.useDataTexture)) { this._dataTextureController = this._dataTextureController || new DataTextureController(DataTextureControllerBufferType.Float32Array); const dataTexture = this._dataTextureController.fromRenderTarget(renderer, renderTarget); await this.textureParamsController.update(dataTexture); this.setTexture(dataTexture); return; } else { this.setTexture(renderTarget.texture); await this.textureParamsController.update(renderTarget.texture); return; } } } async renderTarget(renderer) { return this._renderTargetByRenderer.get(renderer); } async createRenderTargetIfRequired(renderer) { let renderTarget = this._renderTargetByRenderer.get(renderer); if (!renderTarget) { renderTarget = await this._createRenderTarget(renderer); this._renderTargetByRenderer.set(renderer, renderTarget); } return renderTarget; } _requestedResolution(renderer, target) { if (isBooleanTrue(this.pv.useRendererRes)) { renderer.getSize(target); } else { target.copy(this.pv.resolution); } } _ensureRenderTargetResolutionValid(renderer, renderTarget) { this._requestedResolution(renderer, _v2); const image = renderTarget.texture.image; if (image.width != _v2.x || image.height != _v2.y) { renderTarget.setSize(_v2.x, _v2.y); } } async _createRenderTarget(renderer) { this._requestedResolution(renderer, _v2); const wrapS = ClampToEdgeWrapping; const wrapT = ClampToEdgeWrapping; const minFilter = LinearFilter; const magFilter = NearestFilter; const renderTarget = new WebGLRenderTarget(_v2.x, _v2.y, { wrapS, wrapT, minFilter, magFilter, format: RGBAFormat, generateMipmaps: true, type: CoreUserAgent.isiOS() ? HalfFloatType : FloatType, samples: renderer.getPixelRatio(), stencilBuffer: true, depthBuffer: true }); await this.textureParamsController.update(renderTarget.texture); Poly.warn("created render target", this.path(), _v2.x, _v2.y); return renderTarget; } // // // CALLBACK // // static PARAM_CALLBACK_render(node) { node.renderOnTarget(); } }