threepipe
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

TonemapPlugin.ts 6.9KB

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