| @@ -328,6 +328,7 @@ Many features will be added but the core API will not change significantly in fu | |||
| - [@threepipe/plugin-network](https://threepipe.org/package/plugin-network.html) - Network/Cloud related plugin implementations for Threepipe | |||
| - [@threepipe/plugin-svg-renderer](https://threepipe.org/package/plugin-svg-renderer.html) - Add support for exporting a 3d scene as SVG using [three-svg-renderer](https://www.npmjs.com/package/three-svg-renderer) | |||
| - [@threepipe/plugin-3d-tiles-renderer](https://threepipe.org/package/plugin-3d-tiles-renderer.html) - Plugins for [3d-tiles-renderer](https://github.com/NASA-AMMOS/3DTilesRendererJS), b3dm, i3dm, cmpt, pnts, dzi, slippy maps importers. | |||
| - [@threepipe/plugin-path-tracing](https://threepipe.org/package/plugin-path-tracing.html) - Plugins for [path-tracing](https://en.wikipedia.org/wiki/Path_tracing). Using [three-gpu-pathtracer](https://github.com/gkjohnson/three-gpu-pathtracer) | |||
| - [@threepipe/plugin-assimpjs](https://threepipe.org/package/plugin-assimpjs.html) - Plugin and helpers to load and use [assimpjs](https://github.com/kovacsv/assimpjs) (with fbx, other exporters) in the browser. | |||
| ## Documentation | |||
| @@ -374,6 +374,7 @@ | |||
| <li><a href="./virtual-camera/">Virtual Camera (Animated) </a></li> | |||
| <li><a href="./basic-svg-renderer-plugin/">Basic SVG Renderer Plugin </a></li> | |||
| <li><a href="./three-svg-renderer-plugin/">Three SVG Renderer Plugin </a></li> | |||
| <li><a href="./three-gpu-pathtracer/">Three GPU Path Tracer </a></li> | |||
| <li><a href="./3d-tiles-renderer/">3D Tiles Renderer </a></li> | |||
| </ul> | |||
| <h2 class="category">Realistic Rendering (<a class="brand-link webgi-link" href="https://webgi.dev/" target="_blank">webgi</a>)</h2> | |||
| @@ -0,0 +1,37 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Three GPU Path Tracer</title> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||
| <!-- Import maps polyfill --> | |||
| <!-- Remove this when import maps will be widely supported --> | |||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "threepipe": "./../../dist/index.mjs", | |||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs", | |||
| "@threepipe/plugin-path-tracing": "./../../plugins/path-tracing/dist/index.mjs" | |||
| } | |||
| } | |||
| </script> | |||
| <style id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| </style> | |||
| <script type="module" src="../examples-utils/global-loading.mjs"></script> | |||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||
| </head> | |||
| <body> | |||
| <div id="canvas-container"> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,67 @@ | |||
| import { | |||
| _testFinish, | |||
| _testStart, | |||
| BaseGroundPlugin, | |||
| CanvasSnapshotPlugin, | |||
| IObject3D, | |||
| LoadingScreenPlugin, | |||
| PickingPlugin, | |||
| ProgressivePlugin, | |||
| ThreeViewer, | |||
| TonemapPlugin, | |||
| } from 'threepipe' | |||
| import {ThreeGpuPathTracerPlugin} from '@threepipe/plugin-path-tracing' | |||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: false, | |||
| debug: true, | |||
| renderScale: 'auto', | |||
| dropzone: { | |||
| allowedExtensions: ['gltf', 'glb', 'hdr', 'bin', 'png', 'jpeg', 'webp', 'jpg', 'exr', 'json'], | |||
| addOptions: { | |||
| disposeSceneObjects: true, | |||
| autoSetEnvironment: true, // when hdr is dropped | |||
| autoSetBackground: true, | |||
| }, | |||
| }, | |||
| plugins: [LoadingScreenPlugin, PickingPlugin, ProgressivePlugin, BaseGroundPlugin, CanvasSnapshotPlugin, ThreeGpuPathTracerPlugin], | |||
| }) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', {setBackground: true}) | |||
| const modelUrl = 'https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf' | |||
| const result = await viewer.load<IObject3D>(modelUrl, { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| }) | |||
| console.log(result) | |||
| const ground = viewer.getPlugin(BaseGroundPlugin)?.material | |||
| if (ground) { | |||
| // make reflective | |||
| ground.roughness = 0.1 | |||
| ground.metalness = 0.9 | |||
| ground.color.set(0xffffff) | |||
| ground.setDirty() | |||
| } | |||
| // optional | |||
| // const controls = viewer.scene.mainCamera.controls as EnvironmentControls2 | |||
| // result && controls.setTilesRenderer(result.tilesRenderer) | |||
| const ui = viewer.addPluginSync(TweakpaneUiPlugin) | |||
| // ui.appendChild(controls.uiConfig) | |||
| ui.setupPluginUi(ThreeGpuPathTracerPlugin) | |||
| ui.setupPluginUi(ProgressivePlugin) | |||
| ui.setupPluginUi(CanvasSnapshotPlugin) | |||
| ui.setupPluginUi(TonemapPlugin) | |||
| ui.setupPluginUi(BaseGroundPlugin) | |||
| ui.setupPluginUi(PickingPlugin) | |||
| } | |||
| _testStart() | |||
| init().finally(_testFinish) | |||
| @@ -0,0 +1,63 @@ | |||
| { | |||
| "name": "@threepipe/plugin-path-tracing", | |||
| "version": "0.1.0", | |||
| "lockfileVersion": 3, | |||
| "requires": true, | |||
| "packages": { | |||
| "": { | |||
| "name": "@threepipe/plugin-path-tracing", | |||
| "version": "0.1.0", | |||
| "license": "Apache-2.0", | |||
| "dependencies": { | |||
| "threepipe": "file:./../../src/" | |||
| }, | |||
| "devDependencies": { | |||
| "three-gpu-pathtracer": "^0.0.23" | |||
| } | |||
| }, | |||
| "../../src": {}, | |||
| "node_modules/three": { | |||
| "version": "0.177.0", | |||
| "resolved": "https://registry.npmjs.org/three/-/three-0.177.0.tgz", | |||
| "integrity": "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg==", | |||
| "dev": true, | |||
| "license": "MIT", | |||
| "peer": true | |||
| }, | |||
| "node_modules/three-gpu-pathtracer": { | |||
| "version": "0.0.23", | |||
| "resolved": "https://registry.npmjs.org/three-gpu-pathtracer/-/three-gpu-pathtracer-0.0.23.tgz", | |||
| "integrity": "sha512-CjMX5YU3ajDklOv3hW1EQ4CCkPLiLR+jB31w6G5WLf6Nasemib62tmCj45vBK5ItKAD0aHG1NWc479gXzk6fOQ==", | |||
| "dev": true, | |||
| "license": "MIT", | |||
| "peerDependencies": { | |||
| "three": ">=0.151.0", | |||
| "three-mesh-bvh": ">=0.7.4", | |||
| "xatlas-web": "^0.1.0" | |||
| } | |||
| }, | |||
| "node_modules/three-mesh-bvh": { | |||
| "version": "0.9.1", | |||
| "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.9.1.tgz", | |||
| "integrity": "sha512-WNT+m9jGQgtp4YdtwEnl4oFylNVifRf7iphlwWdJ4bJu7oNkY0xHIyntep9OzHuR1hpe/pyAP840gB/EsYDJfg==", | |||
| "dev": true, | |||
| "license": "MIT", | |||
| "peer": true, | |||
| "peerDependencies": { | |||
| "three": ">= 0.159.0" | |||
| } | |||
| }, | |||
| "node_modules/threepipe": { | |||
| "resolved": "../../src", | |||
| "link": true | |||
| }, | |||
| "node_modules/xatlas-web": { | |||
| "version": "0.1.0", | |||
| "resolved": "https://registry.npmjs.org/xatlas-web/-/xatlas-web-0.1.0.tgz", | |||
| "integrity": "sha512-PprVfuXbaIskxLTLBUQRaWfgSy9xUQqAMIRooOw0P6NYqwgh6T0voeer6+Z5M7AFt5SGXUybuww/uDGs1yw8vQ==", | |||
| "dev": true, | |||
| "license": "MIT", | |||
| "peer": true | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,78 @@ | |||
| { | |||
| "name": "@threepipe/plugin-path-tracing", | |||
| "description": "Path tracing plugin interfaces for Threepipe", | |||
| "version": "0.1.0", | |||
| "devDependencies": { | |||
| "three-gpu-pathtracer": "^0.0.23" | |||
| }, | |||
| "dependencies": { | |||
| "three": "file:./../../node_modules/three", | |||
| "threepipe": "file:./../../src/" | |||
| }, | |||
| "overrides": { | |||
| "threepipe": "$threepipe", | |||
| "three": "$three", | |||
| "@types/three": "$@types/three" | |||
| }, | |||
| "exports": { | |||
| ".": { | |||
| "types": "./dist/index.d.ts", | |||
| "import": "./dist/index.mjs", | |||
| "require": "./dist/index.js" | |||
| }, | |||
| "./dist/": { | |||
| "import": "./dist/", | |||
| "require": "./dist/" | |||
| } | |||
| }, | |||
| "type": "module", | |||
| "main": "dist/index.js", | |||
| "module": "dist/index.mjs", | |||
| "types": "dist/index.d.ts", | |||
| "files": [ | |||
| "dist", | |||
| "src" | |||
| ], | |||
| "scripts": { | |||
| "new:pack": "npm run prepare && clean-package && npm pack && clean-package restore", | |||
| "new:publish": "git diff --exit-code --name-only HEAD * && npm run prepare && clean-package && npm publish --access public && clean-package restore && git tag $npm_package_name-$npm_package_version", | |||
| "prepare": "npm run build && npm run docs", | |||
| "build": "rimraf dist && vite build", | |||
| "dev": "NODE_ENV=development vite build --watch", | |||
| "docs": "rimraf docs && npx typedoc" | |||
| }, | |||
| "author": "repalash <palash@shaders.app>", | |||
| "license": "Apache-2.0", | |||
| "keywords": [ | |||
| "three", | |||
| "three.js", | |||
| "threepipe", | |||
| "path-tracing", | |||
| "three-gpu-pathtracer", | |||
| "rendering" | |||
| ], | |||
| "bugs": { | |||
| "url": "https://github.com/repalash/threepipe/issues" | |||
| }, | |||
| "homepage": "https://github.com/repalash/threepipe#readme", | |||
| "repository": { | |||
| "type": "git", | |||
| "url": "git://github.com/repalash/threepipe.git", | |||
| "directory": "plugins/path-tracing" | |||
| }, | |||
| "clean-package": { | |||
| "remove": [ | |||
| "clean-package", | |||
| "scripts", | |||
| "devDependencies", | |||
| "//", | |||
| "markdown-to-html" | |||
| ], | |||
| "replace": { | |||
| "dependencies": {}, | |||
| "peerDependencies": { | |||
| "threepipe": "^0.0.47" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,417 @@ | |||
| import { | |||
| AViewerPluginSync, | |||
| Euler, | |||
| NoColorSpace, | |||
| onChange, | |||
| ProgressivePlugin, | |||
| Scene, | |||
| serialize, | |||
| ThreeViewer, | |||
| uiButton, | |||
| uiConfig, | |||
| uiFolderContainer, | |||
| uiSlider, | |||
| uiToggle, | |||
| } from 'threepipe' | |||
| import {WebGLPathTracer} from 'three-gpu-pathtracer' | |||
| import {WebGLPathTracer2} from './WebGLPathTracer2' | |||
| // @ts-expect-error polyfill for new threejs | |||
| Scene.prototype.backgroundRotation = new Euler(0, 0, 0, 'XYZ') | |||
| // @ts-expect-error polyfill | |||
| Scene.prototype.environmentRotation = new Euler(0, 0, 0, 'XYZ') | |||
| /** | |||
| * ThreeGpuPathTracerPlugin | |||
| * | |||
| * Path Tracing plugin for Threepipe using [three-gpu-pathtracer](https://github.com/gkjohnson/three-gpu-pathtracer). | |||
| * | |||
| * This plugin allows for GPU-accelerated path tracing in Three.js scenes. (using webgl2) | |||
| * | |||
| * It provides options to configure the path tracing parameters such as bounces, samples per frame, and more. | |||
| * It also integrates with the Three.js scene and camera, updating materials and lights as needed. | |||
| * Serialization and deserialization are supported for plugin state management. | |||
| * It listens to scene updates, camera changes, and material updates to refresh the path tracing setup. | |||
| * It can be enabled or disabled, and it automatically handles rendering to the screen or to a texture. | |||
| * It supports progressive rendering, allowing for a smooth transition of rendered frames. | |||
| * | |||
| */ | |||
| @uiFolderContainer('Path Tracing', {expanded: false}) | |||
| export class ThreeGpuPathTracerPlugin extends AViewerPluginSync { | |||
| public static readonly PluginType = 'ThreeGpuPathTracerPlugin' | |||
| @uiToggle() | |||
| @serialize() | |||
| @onChange(ThreeGpuPathTracerPlugin.prototype._enableChange) | |||
| enabled = true | |||
| // public ptMaterial: any | |||
| // public ptGenerator: any | |||
| // public sceneInfo: any | |||
| // @uiSlider('Environment Multiplier', [0, 10], 0.01) | |||
| // @serialize() | |||
| // @onChange(ThreeGpuPathTracerPlugin.prototype.reset) | |||
| // environmentMultiplier = 1 | |||
| // | |||
| // @uiSlider('Max Iterations', [0, 10000], 1) | |||
| // @serialize() | |||
| // maxIterations = 100 | |||
| // | |||
| // @uiSlider('Bounces', [1, 30], 1) | |||
| // @serialize() | |||
| // @onChange(ThreeGpuPathTracerPlugin.prototype.reset) | |||
| // bounces = 10 | |||
| // | |||
| // @uiSlider('filterGlossyFactor', [0, 1], 0.01) | |||
| // @serialize() | |||
| // @onChange(ThreeGpuPathTracerPlugin.prototype.reset) | |||
| // filterGlossyFactor = 0.25 | |||
| // @uiSlider('environmentBlur', [0, 1], 0.01) | |||
| // @onChange(ThreeGpuPathTracerPlugin.prototype.reset) | |||
| // environmentBlur = 0.0 | |||
| @uiSlider('maxFrameCount', [16, 10000], 1) | |||
| get maxFrameCount(): number { | |||
| return this._viewer?.getPlugin(ProgressivePlugin)?.maxFrameCount ?? 32 | |||
| } | |||
| set maxFrameCount(value: number) { | |||
| const p = this._viewer?.getPlugin(ProgressivePlugin) | |||
| if (!p) { | |||
| console.warn('ThreeGpuPathTracerPlugin: ProgressivePlugin not found, cannot set maxFrameCount') | |||
| return | |||
| } | |||
| p.maxFrameCount = value | |||
| } | |||
| @uiSlider('samplesPerFrame', [1, 5], 1) | |||
| samplesPerFrame = 1 | |||
| @uiConfig() | |||
| @serialize() | |||
| tracer: WebGLPathTracer | undefined | |||
| // @uiSlider('tiles', [1, 5], 1) | |||
| // tiles = 2 | |||
| // @uiMonitor() | |||
| // private _lastEnvMap: any = null | |||
| // private _lastBgMap: any = null | |||
| dependencies = [ProgressivePlugin] | |||
| constructor(enabled = true) { | |||
| super() | |||
| this.enabled = enabled | |||
| this.refreshScene = this.refreshScene.bind(this) | |||
| this.reset = this.reset.bind(this) | |||
| this._enableChange = this._enableChange.bind(this) | |||
| this._refreshScene = this._refreshScene.bind(this) | |||
| } | |||
| private async _enableChange() { | |||
| if (this.enabled) this.refreshScene() | |||
| if (this._viewer) { | |||
| this._viewer.renderManager.defaultRenderToScreen = !this.enabled | |||
| } | |||
| this._viewer?.setDirty() | |||
| } | |||
| // todo: onremove and ondispose (dispose sceneInfo in that and remove event listeners) | |||
| onAdded(viewer: ThreeViewer) { | |||
| super.onAdded(viewer) | |||
| // removed | |||
| // if (viewer.renderer.useLegacyLights) { | |||
| // viewer.console.warn('Set viewer.renderer.useLegacyLights to false to get consistent lighting') | |||
| // } | |||
| // todo diamond | |||
| // if (viewer.getPluginByType<any>('Diamond')) | |||
| // viewer.getPluginByType<any>('Diamond').forceSceneEnvMap = true // we dont have separate env map support in path tracing | |||
| this.tracer = new WebGLPathTracer2(viewer.renderManager.webglRenderer) | |||
| this.tracer.setScene(viewer.scene, viewer.scene.mainCamera) // todo: handle active camera change | |||
| // this.ptMaterial = new PhysicalPathTracingMaterial() | |||
| // this.ptRenderer.alpha = !this._viewer?.getBackground() // for transparent background | |||
| // this.ptRenderer.camera = viewer.scene.activeCamera.cameraObject | |||
| // this.ptRenderer.material = this.ptMaterial | |||
| // this.ptRenderer.setSize(viewer.renderer.renderSize.width, viewer.renderer.renderSize.height) | |||
| // this.ptRenderer.tiles.set(this.tiles, this.tiles) | |||
| viewer.scene.addEventListener('sceneUpdate', () => { | |||
| this.refreshScene() | |||
| }) | |||
| viewer.scene.addEventListener('mainCameraChange', () => { | |||
| this.refreshScene() | |||
| }) | |||
| viewer.scene.addEventListener('materialUpdate', () => { | |||
| if (!this.enabled || !this.tracer || this._refreshing) return | |||
| this.tracer.updateMaterials() // todo do post frame? | |||
| this.reset() | |||
| }) | |||
| viewer.scene.addEventListener('environmentChanged', () => { | |||
| if (!this.enabled || !this.tracer || this._refreshing) return | |||
| this.tracer.updateEnvironment() // todo do post frame? | |||
| this.reset() | |||
| }) | |||
| viewer.renderManager.addEventListener('resize', () => { | |||
| if (!this.enabled || !this.tracer || this._refreshing) return | |||
| // this.ptRenderer.setSize(viewer.renderer.renderSize.width, viewer.renderer.renderSize.height) | |||
| // if (this._refreshing) return | |||
| // console.log('resize') | |||
| this.tracer.updateCamera()// todo do post frame? | |||
| this.reset() | |||
| }) | |||
| viewer.scene.mainCamera.addEventListener('update', () => { | |||
| if (!this.enabled || !this.tracer || this._refreshing) return | |||
| this.tracer.updateCamera()// todo do post frame? | |||
| this.reset() | |||
| }) | |||
| viewer.addEventListener('preFrame', () => { | |||
| if (!this.enabled || this._refreshing || !this._viewer || !this.tracer) return | |||
| // if (viewer.getPlugin(ProgressivePlugin)?.isConverged(true)) { | |||
| // todo on no rasterize don't render rasterize pipeline | |||
| const rasterize = viewer.renderManager.frameCount < 16 - 1 // min 16 required for path tracing to show up | |||
| viewer.renderManager.defaultRenderToScreen = rasterize | |||
| if (viewer.renderManager.frameCount > 0 && viewer.renderManager.frameCount < this.maxFrameCount) { | |||
| // const outline = viewer.getPluginByType<OutlinePlugin>('Outline') | |||
| // if (outline) outline.mouseInOutAnimationEnabled = false | |||
| if (this._sceneNeedsRefresh) { | |||
| this._refreshScene() | |||
| return | |||
| } | |||
| // | |||
| // if (!this.ptMaterial.bvh.bvhBounds.source.data.data) { | |||
| // return | |||
| // } | |||
| this._viewer.renderEnabled = false | |||
| // this.ptRenderer.material.environmentMap = (viewer.renderManager.webglRenderer as any).cubeuvmaps | |||
| // const env = viewer.scene.getEnvironment() | |||
| // let bg = viewer.scene.getBackground() as any | |||
| // if (!bg || !bg.isTexture || bg === env) bg = null | |||
| // if (this._lastEnvMap !== env) { | |||
| // this._lastEnvMap = env | |||
| // this.ptRenderer.material.envMapInfo.updateFrom(env) | |||
| // } | |||
| // if (this._lastBgMap !== bg) { | |||
| // this._lastBgMap = bg | |||
| // this.ptRenderer.material.backgroundMap = bg | |||
| // } | |||
| // // viewer.getPlugin(DiamondPlugin)?.envMap || viewer.scene.getEnvironment() | |||
| // // this.ptRenderer.material.environmentMap = viewer.scene.getEnvironment() | |||
| // this.ptRenderer.material.environmentRotation.makeRotationY(viewer.scene.getEnvironment()?.rotation || 0) | |||
| // // console.log(this.ptRenderer.material.environmentMap) | |||
| // // console.log(viewer.getPlugin(DiamondPlugin)?.envMap) | |||
| // | |||
| // this.ptRenderer.material.filterGlossyFactor = this.filterGlossyFactor | |||
| // this.ptRenderer.material.environmentIntensity = (viewer.scene.envMapIntensity || 1) * this.environmentMultiplier | |||
| // this.ptRenderer.material.bounces = this.bounces | |||
| // this.ptRenderer.camera.updateMatrixWorld() | |||
| // | |||
| // this.ptRenderer.camera.clearViewOffset() | |||
| // | |||
| // this._updateDiamondMaterials(this._modelRoot()) | |||
| // | |||
| const encoding = viewer.renderManager.webglRenderer.outputColorSpace | |||
| viewer.renderManager.webglRenderer.outputColorSpace = NoColorSpace | |||
| // const pcl = (viewer.renderManager.webglRenderer as any).useLegacyLights; | |||
| // (viewer.renderManager.webglRenderer as any).useLegacyLights = true | |||
| for (let i = 0, l = this.samplesPerFrame; i < l; i++) | |||
| this.tracer.renderSample() | |||
| // ;(viewer.renderManager.webglRenderer as any).useLegacyLights = pcl | |||
| viewer.renderManager.webglRenderer.outputColorSpace = encoding | |||
| // this.ptRenderer.camera.clearViewOffset() // todo? | |||
| // viewer.renderManager.webglRenderer.autoClear = false | |||
| // ;(fsQuad.material as any).map = this.ptRenderer.target.texture | |||
| // fsQuad.render(viewer.renderManager.webglRenderer) | |||
| // viewer.renderManager.webglRenderer.autoClear = true | |||
| // todo | |||
| // const combinedPost = this._viewer.getPlugin(CombinedPostPlugin)! | |||
| if (!rasterize) { | |||
| const combinedPass = viewer.renderManager.screenPass | |||
| // if (this._iterationCount < 10 || this._iterationCount > 5 && this._iterationCount % 5 === 0) { | |||
| combinedPass.render(viewer.renderManager.renderer, null, this.tracer.target, 0, false) | |||
| viewer.renderManager.incRenderToScreen() | |||
| } | |||
| // } | |||
| // viewer.renderer.blit(this.ptRenderer.target.texture, undefined) | |||
| // console.log(this.ptRenderer.target.texture) | |||
| this._viewer.renderEnabled = true | |||
| } | |||
| }) | |||
| } | |||
| private _refreshing = false | |||
| private _sceneNeedsRefresh = false | |||
| @uiButton() | |||
| refreshScene() { | |||
| this._sceneNeedsRefresh = true | |||
| } | |||
| // private readonly _diaPhysicalMaterial = new MeshPhysicalMaterial({ | |||
| // color: 0xffffff, | |||
| // metalness: 0, | |||
| // roughness: 0, | |||
| // fog: false, | |||
| // transmission: 1.0, | |||
| // opacity: 1, | |||
| // transparent: true, | |||
| // thickness: 0, | |||
| // attenuationDistance: 100, | |||
| // }) | |||
| // private readonly _diaMatMap: Record<string, MeshPhysicalMaterial> = {} | |||
| // private _modelRoot = ()=>this._viewer?.scene.modelRoot.modelObject | |||
| private async _refreshScene() { | |||
| if (!this._viewer) return | |||
| if (!this.enabled || !this.tracer) return | |||
| if (!this._sceneNeedsRefresh) return | |||
| if (this._refreshing) { | |||
| console.warn('ThreeGpuPathTracerPlugin: refreshing already') | |||
| return | |||
| } | |||
| // const root = this._modelRoot() | |||
| // | |||
| // if (!root || !root.children.length) return | |||
| this._sceneNeedsRefresh = false | |||
| this._viewer.renderEnabled = false | |||
| // // if (this.sceneInfo) { | |||
| // // this.sceneInfo.bvh.geometry.dispose() | |||
| // // this.sceneInfo.bvh.geometry.disposeBoundsTree?.() // todo: check what this is and if required | |||
| // // this.sceneInfo = undefined | |||
| // // } | |||
| // this.reset() | |||
| // | |||
| // let geoms = 0 | |||
| // root.traverse((node:any)=>{ | |||
| // if (node.geometry) geoms++ | |||
| // }) | |||
| // if (geoms < 1) return | |||
| // | |||
| this._refreshing = true | |||
| const viewer = this._viewer | |||
| const async = false // todo needs bvh worker? | |||
| if (async) { | |||
| await this.tracer.setSceneAsync(viewer.scene, viewer.scene.mainCamera, { | |||
| onProgress: (progress)=>{ | |||
| console.log('Path Tracing Scene Generation Progress:', progress) | |||
| }, | |||
| }) | |||
| } else { | |||
| this.tracer.setScene(viewer.scene, viewer.scene.mainCamera, { | |||
| onProgress: (progress) => { | |||
| console.log('Path Tracing Scene Generation Progress:', progress) | |||
| }, | |||
| }) | |||
| } | |||
| // | |||
| // const generator = new PathTracingSceneGenerator() | |||
| // this._updateDiamondMaterials(root) | |||
| // | |||
| // const matMap: any = {} | |||
| // | |||
| // root.traverse((node: any) => { | |||
| // if (node.material?.isDiamondMaterial) { | |||
| // matMap[node.uuid] = node.material | |||
| // node.material = this._diaMatMap[this._diamondMaterialKey(node.material)] | |||
| // } | |||
| // }) | |||
| // | |||
| // const result = generator.generate([root, ...this._viewer!.scene.children.filter(c=>(c as any)?.isLight)] as any) | |||
| // // const result = generator.generate(root as any) | |||
| // | |||
| // root.traverse((node: any) => { | |||
| // if (matMap[node.uuid]) { | |||
| // node.material = matMap[node.uuid] | |||
| // } | |||
| // }) | |||
| // | |||
| // this.sceneInfo = result | |||
| // const geometry = result.bvh.geometry | |||
| // // update bvh and geometry attribute textures | |||
| // this.ptMaterial.bvh.updateFrom(result.bvh) | |||
| // this.ptMaterial.attributesArray.updateFrom( | |||
| // geometry.attributes.normal, | |||
| // geometry.attributes.tangent, | |||
| // geometry.attributes.uv, | |||
| // geometry.attributes.color, | |||
| // ) | |||
| // // this.ptMaterial.normalAttribute.updateFrom(geometry.attributes.normal) | |||
| // // this.ptMaterial.tangentAttribute.updateFrom(geometry.attributes.tangent) | |||
| // // this.ptMaterial.uvAttribute.updateFrom(geometry.attributes.uv) | |||
| // // this.ptMaterial.colorAttribute.updateFrom(geometry.attributes.color) | |||
| // | |||
| // // console.log(this.ptMaterial, result.materials) | |||
| // | |||
| // // update lights | |||
| // this.ptMaterial.lights.updateFrom(result.lights) | |||
| // // this.ptMaterial.lightCount = result.lights.length | |||
| // | |||
| // // update materials and texture arrays | |||
| // this.ptMaterial.materialIndexAttribute.updateFrom(geometry.attributes.materialIndex) | |||
| // this.ptMaterial.textures.setTextures(this._viewer.renderManager.webglRenderer, 2048, 2048, result.textures) | |||
| // | |||
| // // this is done before render also | |||
| // this.ptMaterial.materials.updateFrom(result.materials, result.textures) | |||
| // | |||
| // result.matera | |||
| // | |||
| // // generator.dispose() | |||
| // | |||
| this._viewer.renderEnabled = true | |||
| this._refreshing = false | |||
| } | |||
| // private _updateDiamondMaterials(root?: Object3D) { | |||
| // root?.traverse((node: any) => { | |||
| // if (node.material?.isDiamondMaterial) { | |||
| // const matKey = this._diamondMaterialKey(node.material) | |||
| // if (!this._diaMatMap[matKey]) { | |||
| // this._diaMatMap[matKey] = this._diaPhysicalMaterial.clone() | |||
| // } | |||
| // this._diaMatMap[matKey].color.set(node.material.color) | |||
| // this._diaMatMap[matKey].ior = node.material.refractiveIndex | |||
| // } | |||
| // }) | |||
| // } | |||
| // | |||
| // private _diamondMaterialKey = (mat: any)=>mat.color.getHexString() + mat.refractiveIndex | |||
| reset() { | |||
| if (!this.enabled || !this.tracer) return | |||
| this.tracer.reset() | |||
| this._viewer?.setDirty() | |||
| } | |||
| dispose() { | |||
| super.dispose() | |||
| if (this.tracer) { // todo do on plugin remove | |||
| this.tracer.dispose() | |||
| // this.tracer = undefined | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,125 @@ | |||
| import { | |||
| serialize, | |||
| uiButton, | |||
| uiFolderContainer, | |||
| uiNumber, | |||
| uiSlider, | |||
| uiToggle, | |||
| uiVector, | |||
| Vector2, | |||
| WebGLRenderer, | |||
| WebGLRenderTarget, | |||
| } from 'threepipe' | |||
| import {WebGLPathTracer} from 'three-gpu-pathtracer' | |||
| @uiFolderContainer('Path Tracer') | |||
| export class WebGLPathTracer2 extends WebGLPathTracer { | |||
| constructor(renderer: WebGLRenderer) { | |||
| super(renderer) | |||
| this.rasterizeScene = false | |||
| this.renderToCanvas = false | |||
| // this.tiles.set(1, 1) | |||
| } | |||
| @uiNumber(undefined, {readOnly: true}) | |||
| readonly declare samples: number | |||
| // @uiImage(undefined, {readOnly: true}) | |||
| readonly declare target: WebGLRenderTarget | |||
| @uiVector(undefined) | |||
| readonly declare tiles: Vector2 | |||
| @uiToggle() | |||
| @serialize() | |||
| declare multipleImportanceSampling: boolean | |||
| @uiSlider(undefined, [1, 100], 1) | |||
| @serialize() | |||
| declare bounces: number | |||
| @uiSlider(undefined, [1, 100], 1) | |||
| @serialize() | |||
| declare transmissiveBounces: number | |||
| @uiNumber() | |||
| @serialize() | |||
| declare filterGlossyFactor: number | |||
| @uiNumber() | |||
| declare renderDelay: number | |||
| @uiNumber() | |||
| declare minSamples: number | |||
| @uiNumber() | |||
| declare fadeDuration: number | |||
| @uiToggle() | |||
| declare enablePathTracing: boolean | |||
| @uiToggle() | |||
| declare pausePathTracing: boolean | |||
| @uiToggle() | |||
| declare dynamicLowRes: boolean | |||
| @uiNumber() | |||
| declare lowResScale: number | |||
| @uiNumber() | |||
| declare renderScale: number | |||
| @uiToggle() | |||
| declare synchronizeRenderSize: boolean | |||
| // @uiToggle() | |||
| // rasterizeScene: boolean | |||
| // @uiToggle() | |||
| // renderToCanvas: boolean | |||
| @uiVector() | |||
| @serialize() | |||
| declare textureSize: Vector2 | |||
| @uiSlider('Emissive Multiplier', [0, 1000], 0.1) | |||
| @serialize() | |||
| emissiveMultiplier = 1 | |||
| @uiButton() | |||
| updateCamera() { | |||
| super.updateCamera() | |||
| } | |||
| protected _materials: any[] = [] | |||
| @uiButton() | |||
| updateMaterials() { | |||
| this._materials.forEach((material: any) => { | |||
| material.userData.__emissiveIntensity = material.emissiveIntensity | |||
| material.emissiveIntensity *= this.emissiveMultiplier | |||
| if (material.attenuationDistance < 0.00001) { | |||
| material.attenuationDistance = 10000 | |||
| material.userData.__attenuationDistance = material.attenuationDistance | |||
| } | |||
| }) | |||
| super.updateMaterials() | |||
| // this.ptMaterial.materials.updateFrom(this.sceneInfo.materials, this.sceneInfo.textures) | |||
| // | |||
| this._materials.forEach((material: any) => { | |||
| if (material.userData.__emissiveIntensity !== undefined) { | |||
| material.emissiveIntensity = material.userData.__emissiveIntensity | |||
| delete material.userData.__emissiveIntensity | |||
| } | |||
| if (material.userData.__attenuationDistance !== undefined) { | |||
| material.attenuationDistance = material.userData.__attenuationDistance | |||
| delete material.userData.__attenuationDistance | |||
| } | |||
| }) | |||
| } | |||
| @uiButton() | |||
| updateLights() { | |||
| super.updateLights() | |||
| } | |||
| @uiButton() | |||
| updateEnvironment() { | |||
| super.updateEnvironment() | |||
| } | |||
| // @uiButton() | |||
| // renderSample() {super.renderSample()} | |||
| @uiButton() | |||
| reset() { | |||
| super.reset() | |||
| } | |||
| // @uiButton() | |||
| // dispose() {super.dispose()} | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| declare module '*.txt' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.glsl' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.vert' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.frag' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.module.scss' { | |||
| const content: any | |||
| export default content | |||
| export const stylesheet: string | |||
| } | |||
| declare module '*.module.css' { | |||
| const content: any | |||
| export default content | |||
| export const stylesheet: string | |||
| } | |||
| declare module '*.css' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.css?inline' { // for vite | |||
| const content: string | |||
| export default content | |||
| } | |||
| // export {} | |||
| // hack for typedoc | |||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||
| // declare type OffscreenCanvas = HTMLCanvasElement | |||
| @@ -0,0 +1,2 @@ | |||
| export {ThreeGpuPathTracerPlugin} from './ThreeGpuPathTracerPlugin' | |||
| export {WebGLPathTracer2} from './WebGLPathTracer2' | |||
| @@ -0,0 +1,41 @@ | |||
| { | |||
| "compilerOptions": { | |||
| "baseUrl": "./src", | |||
| "rootDir": "./src", | |||
| "allowJs": true, | |||
| "checkJs": false, | |||
| "skipLibCheck": true, | |||
| "allowSyntheticDefaultImports": true, | |||
| "experimentalDecorators": true, | |||
| "isolatedModules": true, | |||
| "module": "es2020", | |||
| "noImplicitAny": true, | |||
| "declaration": true, | |||
| "declarationMap": true, | |||
| "declarationDir": "dist", | |||
| "outDir": "dist", | |||
| "noImplicitThis": true, | |||
| "noUnusedLocals": true, | |||
| "noUnusedParameters": true, | |||
| "removeComments": false, | |||
| "preserveConstEnums": true, | |||
| "moduleResolution": "node", | |||
| "emitDecoratorMetadata": false, | |||
| "sourceMap": true, | |||
| "target": "ES2021", | |||
| "strictNullChecks": true, | |||
| "lib": [ | |||
| "es2020", | |||
| "esnext", | |||
| "dom" | |||
| ] | |||
| }, | |||
| "include": [ | |||
| "src/**/*" | |||
| ], | |||
| "exclude": [ | |||
| "node_modules", | |||
| "**/*.spec.ts", | |||
| "dist" | |||
| ] | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| { | |||
| "extends": [ | |||
| "../../typedoc.json" | |||
| ], | |||
| "entryPoints": [ | |||
| "src/index.ts" | |||
| ], | |||
| "name": "Threepipe Path Tracing Plugins", | |||
| "readme": "none" | |||
| } | |||
| @@ -0,0 +1,93 @@ | |||
| import {defineConfig} from 'vite' | |||
| import json from '@rollup/plugin-json'; | |||
| import dts from 'vite-plugin-dts' | |||
| import packageJson from './package.json'; | |||
| import license from 'rollup-plugin-license'; | |||
| import replace from '@rollup/plugin-replace'; | |||
| import glsl from 'rollup-plugin-glsl'; | |||
| import path from 'node:path'; | |||
| const isProd = process.env.NODE_ENV === 'production' | |||
| const { name, version, author } = packageJson | |||
| const {main, module, browser} = packageJson | |||
| const globals = { | |||
| 'three': 'threepipe', // just incase someone uses three | |||
| 'threepipe': 'threepipe', | |||
| // '@threepipe/plugin-tweakpane': '@threepipe/plugin-tweakpane', | |||
| } | |||
| export default defineConfig({ | |||
| optimizeDeps: { | |||
| exclude: ['uiconfig.js', 'ts-browser-helpers'], | |||
| }, | |||
| base: '', | |||
| // define: { | |||
| // 'process.env': process.env | |||
| // }, | |||
| build: { | |||
| sourcemap: true, | |||
| minify: false, | |||
| cssMinify: isProd, | |||
| cssCodeSplit: false, | |||
| watch: !isProd ? { | |||
| buildDelay: 1000, | |||
| } : null, | |||
| lib: { | |||
| entry: 'src/index.ts', | |||
| formats: isProd ? ['es', 'umd'] : ['es'], | |||
| name: name, | |||
| fileName: (format) => (format === 'umd' ? main : module).replace('dist/', ''), | |||
| }, | |||
| outDir: 'dist', | |||
| emptyOutDir: isProd, | |||
| commonjsOptions: { | |||
| exclude: [/uiconfig.js/, /ts-browser-helpers/], | |||
| }, | |||
| rollupOptions: { | |||
| output: { | |||
| // inlineDynamicImports: false, | |||
| globals, | |||
| }, | |||
| external: Object.keys(globals), | |||
| }, | |||
| }, | |||
| plugins: [ | |||
| isProd ? dts({tsconfigPath: './tsconfig.json'}) : null, | |||
| replace({ | |||
| 'from \'three\'': 'from \'threepipe\'', | |||
| // 'from \'three/examples/jsm/.*\'': 'from \'threepipe\'', // todo regex doesnt work... | |||
| 'from \'three/examples/jsm/postprocessing/Pass.js\'': 'from \'threepipe\'', | |||
| delimiters: ['', ''], | |||
| }), | |||
| replace({ | |||
| 'process.env.NODE_ENV': JSON.stringify(isProd ? 'production' : 'development'), | |||
| preventAssignment: true, | |||
| }), | |||
| glsl({ // todo: minify glsl. | |||
| include: 'src/**/*.glsl', | |||
| }), | |||
| json(), | |||
| // postcss({ | |||
| // modules: false, | |||
| // autoModules: true, // todo; issues with typescript import css, because inject is false | |||
| // inject: false, | |||
| // minimize: isProduction, | |||
| // // Or with custom options for `postcss-modules` | |||
| // }), | |||
| license({ | |||
| banner: ` | |||
| @license | |||
| ${name} v${version} | |||
| Copyright 2022<%= moment().format('YYYY') > 2022 ? '-' + moment().format('YYYY') : null %> ${author} | |||
| ${packageJson.license} License | |||
| See ./dependencies.txt for any bundled third-party dependencies and licenses. | |||
| `, | |||
| thirdParty: { | |||
| output: path.join(__dirname, 'dist', 'dependencies.txt'), | |||
| includePrivate: true, // Default is false. | |||
| }, | |||
| }), | |||
| ], | |||
| }) | |||
| @@ -177,6 +177,7 @@ export default defineConfig({ | |||
| {text: 'svg-renderer Plugin', link: 'package/plugin-svg-renderer'}, | |||
| {text: '3D Tiles (OGC) Renderer Plugin', link: 'package/plugin-3d-tiles-renderer'}, | |||
| {text: 'Assimpjs Plugin', link: 'package/plugin-assimpjs'}, | |||
| {text: 'Path Tracing', link: 'package/plugin-path-tracing'}, | |||
| ] | |||
| }, | |||
| ], | |||
| @@ -31,4 +31,5 @@ Checkout the [model-viewer](https://threepipe.org/examples/#model-viewer) or [tw | |||
| - [@threepipe/plugin-gaussian-splatting](../package/plugin-gaussian-splatting) - [3D Gaussian Splatting](https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/) plugin for loading and rendering splat files | |||
| - [@threepipe/plugin-svg-renderer](../package/plugin-svg-renderer) - Add support for exporting 3d scene as SVG (WIP) using [three-svg-renderer](https://www.npmjs.com/package/three-svg-renderer). | |||
| - [@threepipe/plugin-3d-tiles-renderer](https://threepipe.org/package/plugin-3d-tiles-renderer.html) - Plugins for [3d-tiles-renderer](https://github.com/NASA-AMMOS/3DTilesRendererJS), b3dm, i3dm, cmpt, pnts importers. | |||
| - [@threepipe/plugin-path-tracing](https://threepipe.org/package/plugin-path-tracing.html) - Plugins for [path-tracing](https://en.wikipedia.org/wiki/Path_tracing). Using [three-gpu-pathtracer](https://github.com/gkjohnson/three-gpu-pathtracer) | |||
| - [@threepipe/plugin-assimpjs](https://threepipe.org/package/plugin-assimpjs.html) - Plugin and helpers to load and use [assimpjs](https://github.com/kovacsv/assimpjs) (with fbx, other exporters) in the browser. | |||
| @@ -3,7 +3,10 @@ prev: | |||
| text: '@threepipe/plugin-3d-tiles-renderer' | |||
| link: './plugin-3d-tiles-renderer' | |||
| next: false | |||
| next: | |||
| text: '@threepipe/plugin-path-tracing' | |||
| link: './plugin-path-tracing' | |||
| --- | |||
| # @threepipe/plugin-assimpjs | |||
| @@ -0,0 +1,68 @@ | |||
| --- | |||
| prev: | |||
| text: '@threepipe/plugin-assimpjs' | |||
| link: './plugin-assimpjs' | |||
| next: false | |||
| --- | |||
| # @threepipe/plugin-path-tracing | |||
| Provides plugin(s) for [path-tracing](https://en.wikipedia.org/wiki/Path_tracing). | |||
| Exports | |||
| - [ThreeGpuPathTracer](https://threepipe.org/plugins/path-tracing/docs/classes/ThreeGpuPathTracer.html) - adds support for full scene path tracing with GPU acceleration using [three-gpu-pathtracer](https://github.com/gkjohnson/three-gpu-pathtracer) | |||
| It provides options to configure the path tracing parameters such as bounces, samples per frame, and more. | |||
| It also integrates with the Three.js scene and camera, updating materials and lights as needed. | |||
| Serialization and deserialization are supported for plugin state management. | |||
| It listens to scene updates, camera changes, and material updates to refresh the path tracing setup. | |||
| It can be enabled or disabled, and it automatically handles rendering to the screen or to a texture. | |||
| It supports progressive rendering, allowing for a smooth transition of rendered frames. | |||
| [Example](https://threepipe.org/examples/#three-gpu-pathtracer/) — | |||
| [Source Code](https://github.com/repalash/threepipe/blob/master/plugins/path-tracing/src/index.ts) — | |||
| [API Reference](https://threepipe.org/plugins/path-tracing/docs) | |||
| [](https://www.npmjs.com/package/@threepipe/plugin-path-tracing) | |||
| ```bash | |||
| npm install @threepipe/plugin-path-tracing | |||
| ``` | |||
| ::: warning Note | |||
| This is still a WIP. | |||
| ::: | |||
| :::tip Editor | |||
| Path tracing rendering can be done directly in the tweakpane editor. Simply enable the plugin from the UI. | |||
| ::: | |||
| ## Sample Usage | |||
| To use the plugin, simply add it to the viewer. | |||
| The plugin automatically interfaces with the `ProgressivePlugin` to render upto `maxFrameCount`. | |||
| The samples are rendered whenever the plugin is enabled and the camera is not moving. | |||
| The `WebGLPathTracer` instance in the plugin can be accessed via `viewer.getPlugin(ThreeGpuPathTracer).tracer` property or edited in the UI. | |||
| ```typescript | |||
| import {ThreeViewer} from 'threepipe' | |||
| import {ThreeGpuPathTracer} from '@threepipe/plugin-path-tracing' | |||
| const viewer = new ThreeViewer({...}) | |||
| const pathTracer = viewer.addPluginSync(new ThreeGpuPathTracer(false)) // add the plugin disabled | |||
| console.log(pathTracer.tracer) // access the path tracer instance | |||
| // load files and environment | |||
| pathTracer.enabled = true // enable the plugin to start rendering | |||
| ``` | |||
| Check the [three-gpu-pathtracer](https://threepipe.org/examples/#three-gpu-pathtracer/) example for a live demo. | |||