threepipe
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ProgressivePlugin.ts 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import {IUniform, Texture, TextureDataType, UnsignedByteType, WebGLRenderTarget} from 'three'
  2. import {IPassID, IPipelinePass} from '../../postprocessing'
  3. import {ThreeViewer} from '../../viewer'
  4. import {PipelinePassPlugin} from '../base/PipelinePassPlugin'
  5. import {uiFolderContainer, uiImage, uiInput} from 'uiconfig.js'
  6. import {ICamera, IRenderManager, IScene, IWebGLRenderer} from '../../core'
  7. import {AddBlendTexturePass} from '../../postprocessing/AddBlendTexturePass'
  8. import {getOrCall, serialize, ValOrFunc} from 'ts-browser-helpers'
  9. export type ProgressivePluginEventTypes = ''
  10. export type ProgressivePluginTarget = WebGLRenderTarget
  11. /**
  12. * Progressive Plugin
  13. *
  14. * Adds a post-render pass to blend the last frame with the current frame.
  15. * This can be used to create a progressive rendering effect which is useful for progressive shadows, gi, denoising, baking, anti-aliasing, and many other effects.
  16. * @category Plugins
  17. */
  18. @uiFolderContainer('Progressive Plugin')
  19. export class ProgressivePlugin
  20. extends PipelinePassPlugin<ProgressiveBlendPass, 'progressive', ProgressivePluginEventTypes> {
  21. readonly passId = 'progressive'
  22. public static readonly PluginType = 'ProgressivePlugin'
  23. protected _targets = new Map<string, ProgressivePluginTarget>()
  24. @serialize() @uiInput('Frame count') maxFrameCount: number
  25. // todo: deserialize jitter
  26. // @uiImage('Last Texture' /* {readOnly: true}*/) texture?: Texture
  27. get texture(): Texture | undefined {
  28. return this.target?.texture
  29. }
  30. get target(): ProgressivePluginTarget | undefined {
  31. return this._viewer ? this._targets.get(this._viewer.scene.renderCamera.uuid) : undefined
  32. }
  33. getTarget(camera?: ICamera) {
  34. return this._viewer ? this._targets.get((camera ? camera : this._viewer.scene.renderCamera).uuid) : undefined
  35. }
  36. get textures() {
  37. return this._viewer ? Array.from(this._targets.values()).map(t => t.texture) : []
  38. }
  39. @uiImage('Last Texture' /* {readOnly: true}*/)
  40. get mainTexture() {
  41. return this._viewer ? this.getTarget(this._viewer.scene.mainCamera)?.texture : undefined
  42. }
  43. // @onChange2(ProgressivePlugin.prototype._createTarget)
  44. // @uiDropdown('Buffer Type', threeConstMappings.TextureDataType.uiConfig)
  45. readonly bufferType: TextureDataType // cannot be changed after creation (for now)
  46. constructor(
  47. maxFrameCount = 32,
  48. bufferType: TextureDataType = UnsignedByteType,
  49. enabled = true,
  50. ) {
  51. super()
  52. this.maxFrameCount = maxFrameCount
  53. this.enabled = enabled
  54. this.bufferType = bufferType
  55. }
  56. protected _createTarget(camera?: ICamera, recreate = false) {
  57. if (!this._viewer) return
  58. camera = camera ?? this._viewer.scene.renderCamera
  59. if (recreate) this._disposeTarget(camera)
  60. if (this._targets.has(camera.uuid)) return this._targets.get(camera.uuid)
  61. const target = this._viewer.renderManager.composerTarget.clone(true) as WebGLRenderTarget
  62. target.texture.name = 'progressiveLastBuffer_' + camera.uuid
  63. // target.texture.type = this.bufferType
  64. this._targets.set(camera.uuid, target)
  65. // if (this._pass) this._pass.target = this.target
  66. return target
  67. }
  68. protected _disposeTarget(camera?: ICamera) {
  69. if (!this._viewer) return
  70. if (!camera) {
  71. this._targets.forEach((t) => this._viewer!.renderManager.disposeTarget(t))
  72. this._targets.clear()
  73. } else {
  74. const t = this._targets.get(camera.uuid)
  75. if (t) {
  76. this._viewer!.renderManager.disposeTarget(t)
  77. this._targets.delete(camera.uuid)
  78. }
  79. }
  80. }
  81. protected _createPass() {
  82. // this._createTarget(true)
  83. const pass = new ProgressiveBlendPass(this.passId, ()=>this.target ?? this._createTarget()) // todo: disposeTarget somewhere
  84. pass.dirty = () => (this._viewer?.renderManager.frameCount || 0) < this.maxFrameCount // todo use isConverged function
  85. return pass
  86. }
  87. onRemove(viewer: ThreeViewer): void {
  88. this._disposeTarget()
  89. return super.onRemove(viewer)
  90. }
  91. /**
  92. *
  93. * @param postRender - if called after rendering frame.
  94. */
  95. public isConverged(postRender = false): boolean {
  96. return (this._viewer?.renderer.frameCount || 0) >= this.maxFrameCount - 1 + (postRender ? 1 : 0)
  97. }
  98. updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this {
  99. if (material.uniforms.tLastFrame) material.uniforms.tLastFrame.value = this.target?.texture ?? undefined
  100. return this
  101. }
  102. /**
  103. * Get recording delta post render, For use with animations to sync with converge mode in canvas recorder. See PopmotionPlugin for usage.
  104. * @returns {number} - delta time in milliseconds, or 0 when converging, or -1 in case of not recording in converge mode
  105. */
  106. postFrameConvergedRecordingDelta(_ = 'CanvasRecorder'): number {
  107. // const recorder = this._viewer!.getPluginByType<IConvergedCanvasRecorder&IViewerPlugin>(recorderPlugin)
  108. // if (recorder && recorder.isRecording() && recorder.convergeMode)
  109. // return this.isConverged(true) ? 1. / recorder.videoFrameRate : 0
  110. return -1
  111. }
  112. }
  113. class ProgressiveBlendPass extends AddBlendTexturePass implements IPipelinePass {
  114. before = ['screen']
  115. after = ['render']
  116. required = ['render']
  117. dirty: ValOrFunc<boolean> = () => false
  118. constructor(public readonly passId: IPassID, public target?: ValOrFunc<WebGLRenderTarget|undefined>) {
  119. super()
  120. }
  121. render(renderer: IWebGLRenderer, writeBuffer: WebGLRenderTarget, readBuffer: WebGLRenderTarget, deltaTime: number, maskActive: boolean) {
  122. if (!this.enabled) return
  123. const target = getOrCall(this.target)
  124. if (!target) {
  125. console.warn('ProgressiveBlendPass: target not defined')
  126. return
  127. }
  128. if (renderer.renderManager.frameCount < 1) {
  129. this.needsSwap = false
  130. if (readBuffer?.texture)
  131. renderer.renderManager.blit(target, {
  132. source: readBuffer.texture,
  133. respectColorSpace: false,
  134. })
  135. return
  136. }
  137. this.needsSwap = true
  138. super.render(renderer, writeBuffer, readBuffer, deltaTime, maskActive)
  139. renderer.renderManager.blit(target, {
  140. source: writeBuffer.texture,
  141. respectColorSpace: false,
  142. })
  143. }
  144. beforeRender(_: IScene, _1: ICamera, renderManager: IRenderManager) {
  145. if (!this.enabled) return
  146. if (!this.target) {
  147. console.error('ProgressiveBlendPass: render target undefined')
  148. return
  149. }
  150. let f = 1. / (Math.max(renderManager.frameCount, 0) + 1)
  151. this.uniforms.weight.value.set(f, f, f, f)
  152. f = 1. - f
  153. this.uniforms.weight2.value.set(f, f, f, f)
  154. this.uniforms.tDiffuse2.value = getOrCall(this.target)?.texture
  155. this.material.uniformsNeedUpdate = true
  156. }
  157. }