threepipe
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

ThreeGpuPathTracerPlugin.ts 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. import {
  2. AViewerPluginSync,
  3. Euler,
  4. NoColorSpace,
  5. onChange,
  6. ProgressivePlugin,
  7. Scene,
  8. serialize,
  9. ThreeViewer,
  10. uiButton,
  11. uiConfig,
  12. uiFolderContainer,
  13. uiSlider,
  14. uiToggle,
  15. } from 'threepipe'
  16. import {WebGLPathTracer} from 'three-gpu-pathtracer'
  17. import {WebGLPathTracer2} from './WebGLPathTracer2'
  18. // @ts-expect-error polyfill for new threejs
  19. Scene.prototype.backgroundRotation = new Euler(0, 0, 0, 'XYZ')
  20. // @ts-expect-error polyfill
  21. Scene.prototype.environmentRotation = new Euler(0, 0, 0, 'XYZ')
  22. /**
  23. * ThreeGpuPathTracerPlugin
  24. *
  25. * Path Tracing plugin for Threepipe using [three-gpu-pathtracer](https://github.com/gkjohnson/three-gpu-pathtracer).
  26. *
  27. * This plugin allows for GPU-accelerated path tracing in Three.js scenes. (using webgl2)
  28. *
  29. * It provides options to configure the path tracing parameters such as bounces, samples per frame, and more.
  30. * It also integrates with the Three.js scene and camera, updating materials and lights as needed.
  31. * Serialization and deserialization are supported for plugin state management.
  32. * It listens to scene updates, camera changes, and material updates to refresh the path tracing setup.
  33. * It can be enabled or disabled, and it automatically handles rendering to the screen or to a texture.
  34. * It supports progressive rendering, allowing for a smooth transition of rendered frames.
  35. *
  36. */
  37. @uiFolderContainer('Path Tracing', {expanded: false})
  38. export class ThreeGpuPathTracerPlugin extends AViewerPluginSync {
  39. public static readonly PluginType = 'ThreeGpuPathTracerPlugin'
  40. @uiToggle()
  41. @serialize()
  42. @onChange(ThreeGpuPathTracerPlugin.prototype._enableChange)
  43. enabled = true
  44. // public ptMaterial: any
  45. // public ptGenerator: any
  46. // public sceneInfo: any
  47. // @uiSlider('Environment Multiplier', [0, 10], 0.01)
  48. // @serialize()
  49. // @onChange(ThreeGpuPathTracerPlugin.prototype.reset)
  50. // environmentMultiplier = 1
  51. //
  52. // @uiSlider('Max Iterations', [0, 10000], 1)
  53. // @serialize()
  54. // maxIterations = 100
  55. //
  56. // @uiSlider('Bounces', [1, 30], 1)
  57. // @serialize()
  58. // @onChange(ThreeGpuPathTracerPlugin.prototype.reset)
  59. // bounces = 10
  60. //
  61. // @uiSlider('filterGlossyFactor', [0, 1], 0.01)
  62. // @serialize()
  63. // @onChange(ThreeGpuPathTracerPlugin.prototype.reset)
  64. // filterGlossyFactor = 0.25
  65. // @uiSlider('environmentBlur', [0, 1], 0.01)
  66. // @onChange(ThreeGpuPathTracerPlugin.prototype.reset)
  67. // environmentBlur = 0.0
  68. @uiSlider('maxFrameCount', [16, 10000], 1)
  69. get maxFrameCount(): number {
  70. return this._viewer?.getPlugin(ProgressivePlugin)?.maxFrameCount ?? 32
  71. }
  72. set maxFrameCount(value: number) {
  73. const p = this._viewer?.getPlugin(ProgressivePlugin)
  74. if (!p) {
  75. console.warn('ThreeGpuPathTracerPlugin: ProgressivePlugin not found, cannot set maxFrameCount')
  76. return
  77. }
  78. p.maxFrameCount = value
  79. }
  80. @uiSlider('samplesPerFrame', [1, 5], 1)
  81. samplesPerFrame = 1
  82. @uiConfig(undefined, {expanded: true})
  83. @serialize()
  84. tracer: WebGLPathTracer | undefined
  85. // @uiSlider('tiles', [1, 5], 1)
  86. // tiles = 2
  87. // @uiMonitor()
  88. // private _lastEnvMap: any = null
  89. // private _lastBgMap: any = null
  90. dependencies = [ProgressivePlugin]
  91. constructor(enabled = true) {
  92. super()
  93. this.enabled = enabled
  94. this.refreshScene = this.refreshScene.bind(this)
  95. this.reset = this.reset.bind(this)
  96. this._enableChange = this._enableChange.bind(this)
  97. this._refreshScene = this._refreshScene.bind(this)
  98. }
  99. private async _enableChange() {
  100. if (!this.isDisabled()) this.refreshScene()
  101. this.setDirty()
  102. }
  103. setDirty() {
  104. if (!this._viewer) return
  105. this._viewer.renderManager.defaultRenderToScreen = this.isDisabled()
  106. this._viewer.setDirty()
  107. }
  108. // todo: onremove and ondispose (dispose sceneInfo in that and remove event listeners)
  109. onAdded(viewer: ThreeViewer) {
  110. super.onAdded(viewer)
  111. // removed
  112. // if (viewer.renderer.useLegacyLights) {
  113. // viewer.console.warn('Set viewer.renderer.useLegacyLights to false to get consistent lighting')
  114. // }
  115. // todo diamond
  116. // if (viewer.getPluginByType<any>('Diamond'))
  117. // viewer.getPluginByType<any>('Diamond').forceSceneEnvMap = true // we dont have separate env map support in path tracing
  118. this.tracer = new WebGLPathTracer2(viewer.renderManager.webglRenderer)
  119. this.tracer.setScene(viewer.scene, viewer.scene.mainCamera) // todo: handle active camera change
  120. // this.ptMaterial = new PhysicalPathTracingMaterial()
  121. // this.ptRenderer.alpha = !this._viewer?.getBackground() // for transparent background
  122. // this.ptRenderer.camera = viewer.scene.activeCamera.cameraObject
  123. // this.ptRenderer.material = this.ptMaterial
  124. // this.ptRenderer.setSize(viewer.renderer.renderSize.width, viewer.renderer.renderSize.height)
  125. // this.ptRenderer.tiles.set(this.tiles, this.tiles)
  126. viewer.scene.addEventListener('sceneUpdate', () => {
  127. this.refreshScene()
  128. })
  129. viewer.scene.addEventListener('mainCameraChange', () => {
  130. this.refreshScene()
  131. })
  132. viewer.scene.addEventListener('materialUpdate', () => {
  133. if (this.isDisabled() || !this.tracer || this._refreshing) return
  134. this.tracer.updateMaterials() // todo do post frame?
  135. this.reset()
  136. })
  137. viewer.scene.addEventListener('environmentChanged', () => {
  138. if (this.isDisabled() || !this.tracer || this._refreshing) return
  139. this.tracer.updateEnvironment() // todo do post frame?
  140. this.reset()
  141. })
  142. viewer.renderManager.addEventListener('resize', () => {
  143. if (this.isDisabled() || !this.tracer || this._refreshing) return
  144. // this.ptRenderer.setSize(viewer.renderer.renderSize.width, viewer.renderer.renderSize.height)
  145. // if (this._refreshing) return
  146. // console.log('resize')
  147. this.tracer.updateCamera()// todo do post frame?
  148. this.reset()
  149. })
  150. viewer.scene.mainCamera.addEventListener('update', () => {
  151. if (this.isDisabled() || !this.tracer || this._refreshing) return
  152. this.tracer.updateCamera()// todo do post frame?
  153. this.reset()
  154. })
  155. viewer.addEventListener('preFrame', () => {
  156. if (this.isDisabled() || this._refreshing || !this._viewer || !this.tracer) return
  157. // if (viewer.getPlugin(ProgressivePlugin)?.isConverged(true)) {
  158. // todo on no rasterize don't render rasterize pipeline
  159. const rasterize = viewer.renderManager.frameCount < 16 - 1 // min 16 required for path tracing to show up
  160. viewer.renderManager.defaultRenderToScreen = rasterize
  161. if (viewer.renderManager.frameCount > 0 && viewer.renderManager.frameCount < this.maxFrameCount) {
  162. // const outline = viewer.getPluginByType<OutlinePlugin>('Outline')
  163. // if (outline) outline.mouseInOutAnimationEnabled = false
  164. if (this._sceneNeedsRefresh) {
  165. this._refreshScene()
  166. return
  167. }
  168. //
  169. // if (!this.ptMaterial.bvh.bvhBounds.source.data.data) {
  170. // return
  171. // }
  172. this._viewer.renderEnabled = false
  173. // this.ptRenderer.material.environmentMap = (viewer.renderManager.webglRenderer as any).cubeuvmaps
  174. // const env = viewer.scene.getEnvironment()
  175. // let bg = viewer.scene.getBackground() as any
  176. // if (!bg || !bg.isTexture || bg === env) bg = null
  177. // if (this._lastEnvMap !== env) {
  178. // this._lastEnvMap = env
  179. // this.ptRenderer.material.envMapInfo.updateFrom(env)
  180. // }
  181. // if (this._lastBgMap !== bg) {
  182. // this._lastBgMap = bg
  183. // this.ptRenderer.material.backgroundMap = bg
  184. // }
  185. // // viewer.getPlugin(DiamondPlugin)?.envMap || viewer.scene.getEnvironment()
  186. // // this.ptRenderer.material.environmentMap = viewer.scene.getEnvironment()
  187. // this.ptRenderer.material.environmentRotation.makeRotationY(viewer.scene.getEnvironment()?.rotation || 0)
  188. // // console.log(this.ptRenderer.material.environmentMap)
  189. // // console.log(viewer.getPlugin(DiamondPlugin)?.envMap)
  190. //
  191. // this.ptRenderer.material.filterGlossyFactor = this.filterGlossyFactor
  192. // this.ptRenderer.material.environmentIntensity = (viewer.scene.envMapIntensity || 1) * this.environmentMultiplier
  193. // this.ptRenderer.material.bounces = this.bounces
  194. // this.ptRenderer.camera.updateMatrixWorld()
  195. //
  196. // this.ptRenderer.camera.clearViewOffset()
  197. //
  198. // this._updateDiamondMaterials(this._modelRoot())
  199. //
  200. const encoding = viewer.renderManager.webglRenderer.outputColorSpace
  201. viewer.renderManager.webglRenderer.outputColorSpace = NoColorSpace
  202. // const pcl = (viewer.renderManager.webglRenderer as any).useLegacyLights;
  203. // (viewer.renderManager.webglRenderer as any).useLegacyLights = true
  204. for (let i = 0, l = this.samplesPerFrame; i < l; i++)
  205. this.tracer.renderSample()
  206. // ;(viewer.renderManager.webglRenderer as any).useLegacyLights = pcl
  207. viewer.renderManager.webglRenderer.outputColorSpace = encoding
  208. // this.ptRenderer.camera.clearViewOffset() // todo?
  209. // viewer.renderManager.webglRenderer.autoClear = false
  210. // ;(fsQuad.material as any).map = this.ptRenderer.target.texture
  211. // fsQuad.render(viewer.renderManager.webglRenderer)
  212. // viewer.renderManager.webglRenderer.autoClear = true
  213. // todo
  214. // const combinedPost = this._viewer.getPlugin(CombinedPostPlugin)!
  215. if (!rasterize) {
  216. const combinedPass = viewer.renderManager.screenPass
  217. // if (this._iterationCount < 10 || this._iterationCount > 5 && this._iterationCount % 5 === 0) {
  218. combinedPass.render(viewer.renderManager.renderer, null, this.tracer.target, 0, false)
  219. viewer.renderManager.incRenderToScreen()
  220. }
  221. // }
  222. // viewer.renderer.blit(this.ptRenderer.target.texture, undefined)
  223. // console.log(this.ptRenderer.target.texture)
  224. this._viewer.renderEnabled = true
  225. }
  226. })
  227. }
  228. private _refreshing = false
  229. private _sceneNeedsRefresh = false
  230. @uiButton()
  231. refreshScene() {
  232. this._sceneNeedsRefresh = true
  233. }
  234. // private readonly _diaPhysicalMaterial = new MeshPhysicalMaterial({
  235. // color: 0xffffff,
  236. // metalness: 0,
  237. // roughness: 0,
  238. // fog: false,
  239. // transmission: 1.0,
  240. // opacity: 1,
  241. // transparent: true,
  242. // thickness: 0,
  243. // attenuationDistance: 100,
  244. // })
  245. // private readonly _diaMatMap: Record<string, MeshPhysicalMaterial> = {}
  246. // private _modelRoot = ()=>this._viewer?.scene.modelRoot.modelObject
  247. private async _refreshScene() {
  248. if (!this._viewer) return
  249. if (this.isDisabled() || !this.tracer) return
  250. if (!this._sceneNeedsRefresh) return
  251. if (this._refreshing) {
  252. console.warn('ThreeGpuPathTracerPlugin: refreshing already')
  253. return
  254. }
  255. // const root = this._modelRoot()
  256. //
  257. // if (!root || !root.children.length) return
  258. this._sceneNeedsRefresh = false
  259. this._viewer.renderEnabled = false
  260. // // if (this.sceneInfo) {
  261. // // this.sceneInfo.bvh.geometry.dispose()
  262. // // this.sceneInfo.bvh.geometry.disposeBoundsTree?.() // todo: check what this is and if required
  263. // // this.sceneInfo = undefined
  264. // // }
  265. // this.reset()
  266. //
  267. // let geoms = 0
  268. // root.traverse((node:any)=>{
  269. // if (node.geometry) geoms++
  270. // })
  271. // if (geoms < 1) return
  272. //
  273. this._refreshing = true
  274. const viewer = this._viewer
  275. const async = false // todo needs bvh worker?
  276. if (async) {
  277. await this.tracer.setSceneAsync(viewer.scene, viewer.scene.mainCamera, {
  278. onProgress: (progress)=>{
  279. console.log('Path Tracing Scene Generation Progress:', progress)
  280. },
  281. })
  282. } else {
  283. this.tracer.setScene(viewer.scene, viewer.scene.mainCamera, {
  284. onProgress: (progress) => {
  285. console.log('Path Tracing Scene Generation Progress:', progress)
  286. },
  287. })
  288. }
  289. //
  290. // const generator = new PathTracingSceneGenerator()
  291. // this._updateDiamondMaterials(root)
  292. //
  293. // const matMap: any = {}
  294. //
  295. // root.traverse((node: any) => {
  296. // if (node.material?.isDiamondMaterial) {
  297. // matMap[node.uuid] = node.material
  298. // node.material = this._diaMatMap[this._diamondMaterialKey(node.material)]
  299. // }
  300. // })
  301. //
  302. // const result = generator.generate([root, ...this._viewer!.scene.children.filter(c=>(c as any)?.isLight)] as any)
  303. // // const result = generator.generate(root as any)
  304. //
  305. // root.traverse((node: any) => {
  306. // if (matMap[node.uuid]) {
  307. // node.material = matMap[node.uuid]
  308. // }
  309. // })
  310. //
  311. // this.sceneInfo = result
  312. // const geometry = result.bvh.geometry
  313. // // update bvh and geometry attribute textures
  314. // this.ptMaterial.bvh.updateFrom(result.bvh)
  315. // this.ptMaterial.attributesArray.updateFrom(
  316. // geometry.attributes.normal,
  317. // geometry.attributes.tangent,
  318. // geometry.attributes.uv,
  319. // geometry.attributes.color,
  320. // )
  321. // // this.ptMaterial.normalAttribute.updateFrom(geometry.attributes.normal)
  322. // // this.ptMaterial.tangentAttribute.updateFrom(geometry.attributes.tangent)
  323. // // this.ptMaterial.uvAttribute.updateFrom(geometry.attributes.uv)
  324. // // this.ptMaterial.colorAttribute.updateFrom(geometry.attributes.color)
  325. //
  326. // // console.log(this.ptMaterial, result.materials)
  327. //
  328. // // update lights
  329. // this.ptMaterial.lights.updateFrom(result.lights)
  330. // // this.ptMaterial.lightCount = result.lights.length
  331. //
  332. // // update materials and texture arrays
  333. // this.ptMaterial.materialIndexAttribute.updateFrom(geometry.attributes.materialIndex)
  334. // this.ptMaterial.textures.setTextures(this._viewer.renderManager.webglRenderer, 2048, 2048, result.textures)
  335. //
  336. // // this is done before render also
  337. // this.ptMaterial.materials.updateFrom(result.materials, result.textures)
  338. //
  339. // result.matera
  340. //
  341. // // generator.dispose()
  342. //
  343. this._viewer.renderEnabled = true
  344. this._refreshing = false
  345. }
  346. // private _updateDiamondMaterials(root?: Object3D) {
  347. // root?.traverse((node: any) => {
  348. // if (node.material?.isDiamondMaterial) {
  349. // const matKey = this._diamondMaterialKey(node.material)
  350. // if (!this._diaMatMap[matKey]) {
  351. // this._diaMatMap[matKey] = this._diaPhysicalMaterial.clone()
  352. // }
  353. // this._diaMatMap[matKey].color.set(node.material.color)
  354. // this._diaMatMap[matKey].ior = node.material.refractiveIndex
  355. // }
  356. // })
  357. // }
  358. //
  359. // private _diamondMaterialKey = (mat: any)=>mat.color.getHexString() + mat.refractiveIndex
  360. reset() {
  361. if (this.isDisabled() || !this.tracer) return
  362. this.tracer.reset()
  363. this.setDirty()
  364. }
  365. dispose() {
  366. super.dispose()
  367. if (this.tracer) { // todo do on plugin remove
  368. this.tracer.dispose()
  369. // this.tracer = undefined
  370. }
  371. }
  372. }