瀏覽代碼

Add basic path tracing plugin and examples

master
Palash Bansal 11 月之前
父節點
當前提交
ff7070f84f
沒有連結到貢獻者的電子郵件帳戶。

+ 1
- 0
README.md 查看文件

@@ -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

+ 1
- 0
examples/index.html 查看文件

@@ -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>

+ 37
- 0
examples/three-gpu-pathtracer/index.html 查看文件

@@ -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>

+ 67
- 0
examples/three-gpu-pathtracer/script.ts 查看文件

@@ -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)

+ 63
- 0
plugins/path-tracing/package-lock.json 查看文件

@@ -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
}
}
}

+ 78
- 0
plugins/path-tracing/package.json 查看文件

@@ -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"
}
}
}
}

+ 417
- 0
plugins/path-tracing/src/ThreeGpuPathTracerPlugin.ts 查看文件

@@ -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
}
}
}


+ 125
- 0
plugins/path-tracing/src/WebGLPathTracer2.ts 查看文件

@@ -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()}

}

+ 40
- 0
plugins/path-tracing/src/global.d.ts 查看文件

@@ -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

+ 2
- 0
plugins/path-tracing/src/index.ts 查看文件

@@ -0,0 +1,2 @@
export {ThreeGpuPathTracerPlugin} from './ThreeGpuPathTracerPlugin'
export {WebGLPathTracer2} from './WebGLPathTracer2'

+ 41
- 0
plugins/path-tracing/tsconfig.json 查看文件

@@ -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"
]
}

+ 10
- 0
plugins/path-tracing/typedoc.json 查看文件

@@ -0,0 +1,10 @@
{
"extends": [
"../../typedoc.json"
],
"entryPoints": [
"src/index.ts"
],
"name": "Threepipe Path Tracing Plugins",
"readme": "none"
}

+ 93
- 0
plugins/path-tracing/vite.config.js 查看文件

@@ -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.
},
}),
],
})

+ 1
- 0
website/.vitepress/config.ts 查看文件

@@ -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'},
]
},
],

+ 1
- 0
website/guide/threepipe-packages.md 查看文件

@@ -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.

+ 4
- 1
website/package/plugin-assimpjs.md 查看文件

@@ -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

+ 68
- 0
website/package/plugin-path-tracing.md 查看文件

@@ -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/) &mdash;
[Source Code](https://github.com/repalash/threepipe/blob/master/plugins/path-tracing/src/index.ts) &mdash;
[API Reference](https://threepipe.org/plugins/path-tracing/docs)

[![NPM Package](https://img.shields.io/npm/v/@threepipe/plugin-path-tracing.svg)](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.

Loading…
取消
儲存