@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
332 lines (331 loc) • 10.9 kB
JavaScript
"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();
}