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.

TonemapPlugin.ts 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. // noinspection ES6PreferShortImport
  2. import {AViewerPluginSync} from '../../viewer/AViewerPlugin'
  3. import type {ThreeViewer} from '../../viewer'
  4. import {MaterialExtension} from '../../materials'
  5. import {uiDropdown, uiFolderContainer, uiSlider, uiToggle} from 'uiconfig.js'
  6. import {
  7. ACESFilmicToneMapping,
  8. CineonToneMapping,
  9. CustomToneMapping,
  10. LinearToneMapping,
  11. Object3D,
  12. ReinhardToneMapping,
  13. Shader,
  14. ShaderChunk,
  15. ToneMapping,
  16. Vector4,
  17. WebGLRenderer,
  18. } from 'three'
  19. import {glsl, onChange, serialize} from 'ts-browser-helpers'
  20. import {IMaterial} from '../../core'
  21. import {shaderReplaceString, updateBit} from '../../utils'
  22. import {matDefine, uniform} from '../../three'
  23. import Uncharted2ToneMapping from './shaders/Uncharted2ToneMapping.glsl'
  24. import TonemapShader from './shaders/TonemapPlugin.pars.glsl'
  25. import TonemapShaderPatch from './shaders/TonemapPlugin.patch.glsl'
  26. // todo move
  27. export interface GBufferUpdater {
  28. updateGBufferFlags: (material: IMaterial, data: Vector4) => void
  29. }
  30. // eslint-disable-next-line @typescript-eslint/naming-convention
  31. export const Uncharted2Tonemapping: ToneMapping = CustomToneMapping
  32. /**
  33. * Tonemap Plugin
  34. *
  35. * Adds an extension to {@link ScreenPass} material
  36. * for applying tonemapping on the final buffer before rendering to screen.
  37. *
  38. * Also adds support for Uncharted2 tone-mapping.
  39. * @category Plugins
  40. */
  41. @uiFolderContainer('Tonemapping')
  42. export class TonemapPlugin extends AViewerPluginSync<''> implements MaterialExtension, GBufferUpdater {
  43. static readonly PluginType = 'Tonemap'
  44. @serialize() @uiToggle('Enabled') enabled = true
  45. @uiDropdown('Mode', ([
  46. ['Linear', LinearToneMapping],
  47. ['Reinhard', ReinhardToneMapping],
  48. ['Cineon', CineonToneMapping],
  49. ['ACESFilmic', ACESFilmicToneMapping],
  50. ['Uncharted2', Uncharted2Tonemapping],
  51. ] as [string, ToneMapping][]).map(value => ({
  52. label: value[0],
  53. value: value[1],
  54. })))
  55. @onChange(TonemapPlugin.prototype.setDirty)
  56. @serialize() toneMapping: ToneMapping = ACESFilmicToneMapping
  57. @uiToggle('Tonemap Background', (t: TonemapPlugin)=>({hidden: ()=>!t._viewer?.renderManager.gbufferTarget}))
  58. @matDefine('TONEMAP_BACKGROUND', undefined, true, TonemapPlugin.prototype.setDirty, (v)=>v ? '1' : '0', (v) => v !== '0')
  59. @serialize() tonemapBackground = true
  60. // todo handle legacy deserialize
  61. // @onChange(TonemapPlugin.prototype.setDirty)
  62. // @uiToggle('Clip Background')
  63. // @serialize() clipBackground = false
  64. @onChange(TonemapPlugin.prototype.setDirty)
  65. @uiSlider('Exposure', [0, 2 * Math.PI], 0.01)
  66. @serialize() exposure = 1
  67. @uiSlider('Saturation', [0, 2], 0.01)
  68. @uniform({propKey: 'toneMappingSaturation'})
  69. @serialize() saturation: number
  70. @uiSlider('Contrast', [0, 2], 0.01)
  71. @uniform({propKey: 'toneMappingContrast'})
  72. @serialize() contrast: number
  73. readonly extraUniforms = {
  74. toneMappingContrast: {value: 1},
  75. toneMappingSaturation: {value: 1},
  76. } as const
  77. set uniformsNeedUpdate(v: boolean) { // for @uniform decorator
  78. if (v) this.setDirty()
  79. }
  80. parsFragmentSnippet: any = (_: WebGLRenderer, _1: IMaterial) => {
  81. if (!this.enabled) return ''
  82. return glsl`
  83. uniform float toneMappingContrast;
  84. uniform float toneMappingSaturation;
  85. ${TonemapShader}
  86. `
  87. }
  88. constructor() {
  89. super()
  90. this.setDirty = this.setDirty.bind(this)
  91. }
  92. /**
  93. * The priority of the material extension when applied to the material in ScreenPass
  94. * set to very low priority, so applied at the end
  95. */
  96. readonly priority = -100
  97. shaderExtender(shader: Shader, _: IMaterial, _1: WebGLRenderer): void {
  98. if (!this.enabled) return
  99. shader.fragmentShader = shaderReplaceString(
  100. shader.fragmentShader,
  101. '#glMarker', '\n' + TonemapShaderPatch + '\n',
  102. {prepend: true}
  103. )
  104. }
  105. readonly extraDefines = {
  106. ['TONEMAP_BACKGROUND']: '1',
  107. } as const
  108. private _rendererState: any = {}
  109. onObjectRender(_: Object3D, material: IMaterial, renderer: WebGLRenderer): void {
  110. if (!this.enabled) return
  111. const {toneMapping, toneMappingExposure} = renderer
  112. this._rendererState.toneMapping = toneMapping
  113. this._rendererState.toneMappingExposure = toneMappingExposure
  114. renderer.toneMapping = this.toneMapping
  115. renderer.toneMappingExposure = this.exposure
  116. material.toneMapped = true
  117. material.needsUpdate = true
  118. }
  119. onAfterRender(_: Object3D, _1: IMaterial, renderer: WebGLRenderer): void {
  120. renderer.toneMapping = this._rendererState.toneMapping
  121. renderer.toneMappingExposure = this._rendererState.toneMappingExposure
  122. }
  123. getUiConfig(): any {
  124. return this.uiConfig
  125. }
  126. computeCacheKey = (_: IMaterial) => this.enabled ? '1' : '0'
  127. isCompatible(_: IMaterial): boolean {
  128. return true // (material as MeshStandardMaterial2).isMeshStandardMaterial2
  129. }
  130. setDirty() {
  131. this.__setDirty?.() // this will update version which will set needsUpdate on material
  132. this._viewer?.renderManager.screenPass.setDirty()
  133. }
  134. fromJSON(data: any, meta?: any): this|null|Promise<this|null> {
  135. // really pld legacy
  136. if (data.pass) {
  137. data = {...data}
  138. data.extension = {...data.pass}
  139. delete data.extension.enabled
  140. delete data.pass
  141. }
  142. // legacy
  143. if (data.extension) {
  144. data = {...data, ...data.extension}
  145. delete data.extension
  146. if (data.clipBackground !== undefined) {
  147. if (this._viewer) this._viewer.renderManager.screenPass.clipBackground = data.clipBackground
  148. else console.warn('TonemapPlugin: no viewer attached, clipBackground ignored')
  149. delete data.clipBackground
  150. }
  151. }
  152. return super.fromJSON(data, meta)
  153. }
  154. onAdded(viewer: ThreeViewer) {
  155. super.onAdded(viewer)
  156. // viewer.getPlugin(GBufferPlugin)?.registerGBufferUpdater(this.updateGBufferFlags) // todo
  157. viewer.renderManager.screenPass.material.registerMaterialExtensions([this])
  158. }
  159. onRemove(viewer: ThreeViewer) {
  160. // viewer.getPlugin(GBufferPlugin)?.unregisterGBufferUpdater(this.updateGBufferFlags)
  161. viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this])
  162. super.onRemove(viewer)
  163. }
  164. updateGBufferFlags(material: IMaterial, data: Vector4): void {
  165. const x = material?.userData.postTonemap === false ? 0 : 1
  166. data.w = updateBit(data.w, 1, x) // 2nd Bit
  167. }
  168. static {
  169. // Add support for Uncharted2 tone mapping
  170. ShaderChunk.tonemapping_pars_fragment = ShaderChunk.tonemapping_pars_fragment.replace('vec3 CustomToneMapping( vec3 color ) { return color; }', Uncharted2ToneMapping)
  171. }
  172. // for typescript
  173. // eslint-disable-next-line @typescript-eslint/naming-convention
  174. __setDirty?: () => void
  175. }