UNPKG

@polygonjs/polygonjs

Version:

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

332 lines (331 loc) 10.9 kB
"use strict"; import { Line, LineBasicMaterial, Matrix4, Vector2 } from "three"; import { MeshBasicMaterial } from "three"; import { isArray } from "../../../../../core/Type"; import { CoreSleep } from "../../../../../core/Sleep"; import { PathTracingSceneWorker, PathTracingSceneGenerator, BlurredEnvMapGenerator } from "../../../../../core/render/PBR/three-gpu-pathtracer"; export class PathTracingRendererContainer { constructor(options) { this.displayDebug = true; this.backgroundBlur = 0.1; this.maxSamplesCount = 1; this.samplesPerAnimationFrame = 1; this.frameRange = new Vector2(); this.resolutionScale = 0.5; this._useWorker = false; this._generated = false; this._generating = false; this.isPathTracingRendererContainer = true; this._denoiseActive = false; this._multipleImportanceSampling = true; this._generator = new PathTracingSceneGenerator(); this._generateRequired = false; this._previousCameraProjectionMatrix = new Matrix4(); this._previousCameraWorldMatrix = new Matrix4(); this.node = options.node; this.webGLRenderer = options.webGLRenderer; this.pathTracingRenderer = options.pathTracingRenderer; this.fsQuad = options.fsQuad; this.fsQuadMat = options.fsQuadMat; this.denoiseQuad = options.denoiseQuad; this.denoiseMat = options.denoiseMat; this.domElement = this.webGLRenderer.domElement; } update(options) { const { pathTracingRenderer } = this; let resetRequired = false; let generateRequired = false; if (this.resolutionScale != options.resolutionScale) { this.resolutionScale = options.resolutionScale; resetRequired = true; } if (this.displayDebug != options.displayDebug) { this.displayDebug = options.displayDebug; resetRequired = true; } if (pathTracingRenderer.material.bounces != options.bounces) { pathTracingRenderer.material.bounces = options.bounces; resetRequired = true; } if (pathTracingRenderer.material.transmissiveBounces != options.transmissiveBounces) { pathTracingRenderer.material.transmissiveBounces = options.transmissiveBounces; resetRequired = true; } if (pathTracingRenderer.stableNoise != options.stableNoise) { pathTracingRenderer.stableNoise = options.stableNoise; resetRequired = true; } if (pathTracingRenderer.material.filterGlossyFactor != options.filterGlossyFactor) { pathTracingRenderer.material.filterGlossyFactor = options.filterGlossyFactor; resetRequired = true; } if (this.backgroundBlur != options.backgroundBlur) { this.backgroundBlur = options.backgroundBlur; generateRequired = true; } if (pathTracingRenderer.material.environmentIntensity != options.environmentIntensity) { pathTracingRenderer.material.environmentIntensity = options.environmentIntensity; resetRequired = true; } if (!pathTracingRenderer.tiles.equals(options.tiles)) { pathTracingRenderer.tiles.set(options.tiles.x, options.tiles.y); resetRequired = true; } if (this._multipleImportanceSampling != options.multipleImportanceSampling) { this._multipleImportanceSampling = options.multipleImportanceSampling; pathTracingRenderer.material.setDefine("FEATURE_MIS", Number(options.multipleImportanceSampling)); resetRequired = true; } this._denoiseActive = options.denoise; this.denoiseMat.sigma = options.denoiseSigma; this.denoiseMat.threshold = options.denoiseThreshold; this.denoiseMat.kSigma = options.denoiseKSigma; this._useWorker = options.useWorker; if (this.maxSamplesCount > options.maxSamplesCount) { resetRequired = true; } this.maxSamplesCount = options.maxSamplesCount; this.samplesPerAnimationFrame = options.samplesPerAnimationFrame; this.frameRange.copy(options.f); if (generateRequired) { this.markAsNotGenerated(); } else if (resetRequired) { this.reset(); } } render(scene, camera) { this.renderRealtime(scene, camera); } pbrRenderAllowed() { return this._generating == false && this._generated == true; } markAsNotGenerated() { this._generated = false; } renderRealtime(scene, camera) { if (!this.pbrRenderAllowed()) { this.webGLRenderer.render(scene, camera); return; } const maxSamplesCount = this.maxSamplesCount; if (this.pathTracingRenderer.samples >= maxSamplesCount) { return; } this.pathTracingRenderer.material.physicalCamera.updateFrom(camera); this._preRender(camera); this.pathTracingRenderer.update(); if (this.pathTracingRenderer.samples < 1) { this.webGLRenderer.render(scene, camera); } this._postRender(); } _preRender(camera) { this.pathTracingRenderer.camera = camera; camera.updateMatrixWorld(); this._resetIfCameraUpdated(camera); } _postRender() { this.webGLRenderer.autoClear = false; const quad = this._denoiseActive ? this.denoiseQuad : this.fsQuad; const mat = this._denoiseActive ? this.denoiseMat : this.fsQuadMat; mat.map = this.pathTracingRenderer.target.texture; quad.render(this.webGLRenderer); this.webGLRenderer.autoClear = true; } samplesCount() { return Math.floor(this.pathTracingRenderer.samples); } getPixelRatio() { return this.webGLRenderer.getPixelRatio(); } compile(scene, camera, targetScene) { return this.webGLRenderer.compile(scene, camera, targetScene); } dispose() { } setSize(w, h, setStyle) { this.webGLRenderer.setSize(w, h, setStyle); this.pathTracingRenderer.setSize( w * this.resolutionScale * globalThis.devicePixelRatio, h * this.resolutionScale * globalThis.devicePixelRatio ); this.pathTracingRenderer.reset(); } setRenderTarget(renderTarget, activeCubeFace, activeMipmapLevel) { this.webGLRenderer.setRenderTarget(renderTarget, activeCubeFace, activeMipmapLevel); } readRenderTargetPixels(renderTarget, x, y, width, height, buffer, activeCubeFaceIndex) { return this.webGLRenderer.readRenderTargetPixels( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ); } get capabilities() { return this.webGLRenderer.capabilities; } async generate(scene) { if (this._generating) { this._generateRequired = true; return; } console.warn("GENERATOR START"); this._generated = false; this._generating = true; const timeStart = performance.now(); const _restartIfRequired = () => { if (this._generateRequired) { let generate2 = function() { _this.generate(scene); }; var generate = generate2; this._generateRequired = false; this._generating = false; console.log("GENERATOR RESTART..."); const _this = this; setTimeout(generate2, 20); return true; } }; const bgTexture = scene.environment; if (bgTexture) { const generator = new BlurredEnvMapGenerator(this.webGLRenderer); const blurredTex = generator.generate(bgTexture, this.backgroundBlur); this.pathTracingRenderer.material.envMapInfo.updateFrom(blurredTex); await CoreSleep.sleep(20); } if (_restartIfRequired()) { return; } scene.updateMatrixWorld(true); prepareScene(scene); const generateInThread = () => { this._generator = this._generator || new PathTracingSceneGenerator(); return this._generator.generate(scene); }; const generateInWorker = async () => { this._workerGenerator = this._workerGenerator || new PathTracingSceneWorker(); const result2 = await this._workerGenerator.generate(scene, { onProgress: (progress) => { } }); return result2; }; let result; try { result = this._useWorker ? await generateInWorker() : generateInThread(); } catch (err) { console.error(err); this._generating = false; return; } restoreScene(scene); await CoreSleep.sleep(20); if (_restartIfRequired()) { return; } const { bvh, textures, materials, lights } = result; const geometry = bvh.geometry; const material = this.pathTracingRenderer.material; material.bvh.updateFrom(bvh); material.attributesArray.updateFrom( geometry.attributes.normal, geometry.attributes.tangent, geometry.attributes.uv, geometry.attributes.color ); material.materialIndexAttribute.updateFrom(geometry.attributes.materialIndex); material.textures.setTextures(this.webGLRenderer, 1024, 1024, textures); material.materials.updateFrom(materials, textures); const iesTextures = lights.map((light) => light.iesTexture); material.iesProfiles.updateFrom(this.webGLRenderer, iesTextures); material.lights.updateFrom(lights, iesTextures); await CoreSleep.sleep(20); if (_restartIfRequired()) { return; } this._generating = false; this._generated = true; this.reset(); console.log("GENERATOR DONE", performance.now() - timeStart); } reset() { if (!this._generated) { return; } this.pathTracingRenderer.reset(); } _resetIfCameraUpdated(camera) { if (this._previousCameraProjectionMatrix.equals(camera.projectionMatrix) && this._previousCameraWorldMatrix.equals(camera.matrixWorld)) { return; } this._previousCameraProjectionMatrix.copy(camera.projectionMatrix); this._previousCameraWorldMatrix.copy(camera.matrixWorld); this.reset(); } } const previousParent = /* @__PURE__ */ new Map(); function prepareScene(scene) { function _saveAndSetObjectState(object) { if (!object.parent) { return; } previousParent.set(object, object.parent); object.parent.remove(object); } function _isValidMaterial(material) { if (material instanceof LineBasicMaterial) { return false; } if (material instanceof MeshBasicMaterial) { return false; } return true; } function _isValidObject(object) { if (object instanceof Line) { return false; } const material = object.material; if (isArray(material)) { for (const mat of material) { if (!_isValidMaterial(mat)) { return false; } } } else { if (!_isValidMaterial(material)) { return false; } } return true; } const objectsToRemoveFromHierarchy = []; scene.traverse((object) => { if (!_isValidObject(object)) { objectsToRemoveFromHierarchy.push(object); } }); for (const object of objectsToRemoveFromHierarchy) { _saveAndSetObjectState(object); } } function restoreScene(scene) { previousParent.forEach((parent, object) => { parent.add(object); }); previousParent.clear(); }