threepipe
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

SSAOPlugin.ts 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. import {Matrix4, Texture, TextureDataType, UnsignedByteType, Vector2, Vector3, Vector4, WebGLRenderTarget} from 'three'
  2. import {ExtendedShaderPass, IPassID, IPipelinePass} from '../../postprocessing'
  3. import {ThreeViewer} from '../../viewer'
  4. import {PipelinePassPlugin} from '../base/PipelinePassPlugin'
  5. import {uiConfig, uiFolderContainer, uiImage, uiSlider} from 'uiconfig.js'
  6. import {ICamera, IMaterial, IRenderManager, IScene, IWebGLRenderer, PhysicalMaterial} from '../../core'
  7. import {getOrCall, glsl, onChange2, serialize, updateBit, ValOrFunc} from 'ts-browser-helpers'
  8. import {MaterialExtension} from '../../materials'
  9. import {shaderReplaceString, shaderUtils} from '../../utils'
  10. import {getTexelDecoding, matDefine, matDefineBool} from '../../three'
  11. import ssaoPass from './shaders/SSAOPlugin.pass.glsl'
  12. import ssaoPatch from './shaders/SSAOPlugin.patch.glsl'
  13. import {uiConfigMaterialExtension} from '../../materials/MaterialExtender'
  14. import {GBufferPlugin, GBufferUpdaterContext} from './GBufferPlugin'
  15. export type SSAOPluginEventTypes = ''
  16. export type SSAOPluginTarget = WebGLRenderTarget
  17. /**
  18. * SSAO Plugin
  19. *
  20. * Adds Screen Space Ambient Occlusion (SSAO) to the scene.
  21. * Adds a pass to calculate AO, which is then read by materials in the render pass.
  22. * @category Plugins
  23. */
  24. @uiFolderContainer('SSAO Plugin')
  25. export class SSAOPlugin
  26. extends PipelinePassPlugin<SSAOPluginPass, 'ssao', SSAOPluginEventTypes> {
  27. readonly passId = 'ssao'
  28. public static readonly PluginType = 'SSAOPlugin'
  29. public static readonly OldPluginType = 'SSAO'
  30. dependencies = [GBufferPlugin]
  31. target?: SSAOPluginTarget
  32. @uiImage('SSAO Buffer' /* {readOnly: true}*/) texture?: Texture
  33. @uiConfig() declare protected _pass?: SSAOPluginPass
  34. // @onChange2(SSAOPlugin.prototype._createTarget)
  35. // @uiDropdown('Buffer Type', threeConstMappings.TextureDataType.uiConfig)
  36. readonly bufferType: TextureDataType // cannot be changed after creation (for now)
  37. // @onChange2(SSAOPlugin.prototype._createTarget)
  38. // @uiSlider('Buffer Size Multiplier', [0.25, 2.0], 0.25)
  39. readonly sizeMultiplier: number // cannot be changed after creation (for now)
  40. constructor(
  41. bufferType: TextureDataType = UnsignedByteType,
  42. sizeMultiplier = 1,
  43. enabled = true,
  44. ) {
  45. super()
  46. this.enabled = enabled
  47. this.bufferType = bufferType
  48. this.sizeMultiplier = sizeMultiplier
  49. }
  50. protected _createTarget(recreate = true) {
  51. if (!this._viewer) return
  52. if (recreate) this._disposeTarget()
  53. if (!this.target)
  54. this.target = this._viewer.renderManager.createTarget<SSAOPluginTarget>(
  55. {
  56. depthBuffer: false,
  57. type: this.bufferType,
  58. sizeMultiplier: this.sizeMultiplier,
  59. // magFilter: NearestFilter,
  60. // minFilter: NearestFilter,
  61. // generateMipmaps: false,
  62. // encoding: LinearEncoding,
  63. })
  64. this.texture = this.target.texture
  65. this.texture.name = 'ssaoBuffer'
  66. // if (this._pass) this._pass.target = this.target
  67. }
  68. protected _disposeTarget() {
  69. if (!this._viewer) return
  70. if (this.target) {
  71. this._viewer.renderManager.disposeTarget(this.target)
  72. this.target = undefined
  73. }
  74. this.texture = undefined
  75. }
  76. private _gbufferUnpackExtension = undefined as MaterialExtension|undefined
  77. private _gbufferUnpackExtensionChanged = ()=>{
  78. if (!this._pass || !this._viewer) throw new Error('SSAOPlugin: pass/viewer not created yet')
  79. const newExtension = this._viewer.renderManager.gbufferUnpackExtension
  80. if (this._gbufferUnpackExtension === newExtension) return
  81. if (this._gbufferUnpackExtension) this._pass.material.unregisterMaterialExtensions([this._gbufferUnpackExtension])
  82. this._gbufferUnpackExtension = newExtension
  83. if (this._gbufferUnpackExtension) this._pass.material.registerMaterialExtensions([this._gbufferUnpackExtension])
  84. else this._viewer.console.warn('SSAOPlugin: GBuffer unpack extension removed')
  85. }
  86. protected _createPass() {
  87. if (!this._viewer) throw new Error('SSAOPlugin: viewer not set')
  88. if (!this._viewer.renderManager.gbufferTarget || !this._viewer.renderManager.gbufferUnpackExtension)
  89. throw new Error('SSAOPlugin: GBuffer target not created. GBufferPlugin or DepthBufferPlugin is required.')
  90. this._createTarget(true)
  91. return new SSAOPluginPass(this.passId, ()=>this.target)
  92. }
  93. onAdded(viewer: ThreeViewer) {
  94. super.onAdded(viewer)
  95. viewer.forPlugin(GBufferPlugin, (gbuffer) => {
  96. gbuffer.registerGBufferUpdater(this.constructor.PluginType, this.updateGBufferFlags.bind(this))
  97. }, (gbuffer)=>{
  98. gbuffer.unregisterGBufferUpdater(this.constructor.PluginType)
  99. })
  100. this._gbufferUnpackExtensionChanged()
  101. viewer.renderManager.addEventListener('gbufferUnpackExtensionChanged', this._gbufferUnpackExtensionChanged)
  102. }
  103. onRemove(viewer: ThreeViewer): void {
  104. this._disposeTarget()
  105. return super.onRemove(viewer)
  106. }
  107. fromJSON(data: any, meta?: any): this|null|Promise<this|null> {
  108. // legacy
  109. if (data.passes?.ssao) {
  110. data = {...data}
  111. data.pass = data.passes.ssao
  112. delete data.passes
  113. if (data.pass.enabled !== undefined) data.enabled = data.pass.enabled
  114. }
  115. return super.fromJSON(data, meta)
  116. }
  117. updateGBufferFlags(data: Vector4, c: GBufferUpdaterContext): void {
  118. if (!c.material || !c.material.userData) return
  119. const disabled = c.material.userData.ssaoCastDisabled || c.material.userData.pluginsDisabled
  120. const x = disabled ? 0 : 1
  121. data.w = updateBit(data.w, 3, x)
  122. if (disabled && this._pass) this._pass.checkGBufferFlag = true
  123. }
  124. /**
  125. * @deprecated use {@link target} instead
  126. */
  127. get aoTarget() {
  128. console.warn('SSAOPlugin: aoTarget is deprecated, use target instead')
  129. return this.target
  130. }
  131. }
  132. @uiFolderContainer('SSAO Pass')
  133. export class SSAOPluginPass extends ExtendedShaderPass implements IPipelinePass {
  134. before = ['render']
  135. after = ['gbuffer', 'depth']
  136. required = ['render'] // gbuffer required check done in plugin.
  137. // todo bilateralPass
  138. // @serialize() readonly bilateralPass: BilateralFilterPass
  139. // todo old deserialize
  140. // @serialize() readonly parameters: SSAOParams = {
  141. // intensity: 0.25,
  142. // occlusionWorldRadius: 1,
  143. // bias: 0.001,
  144. // falloff: 1.3,
  145. // }
  146. @serialize()
  147. @uiSlider('Intensity', [0, 4], 0.01)
  148. @onChange2(SSAOPluginPass.prototype.setDirty)
  149. intensity = 0.25
  150. @serialize()
  151. @uiSlider('Occlusion World Radius', [0.1, 8], 0.01)
  152. @onChange2(SSAOPluginPass.prototype.setDirty)
  153. occlusionWorldRadius = 1
  154. @serialize()
  155. @uiSlider('Bias', [0.00001, 0.01], 0.00001)
  156. @onChange2(SSAOPluginPass.prototype.setDirty)
  157. bias = 0.001
  158. @serialize()
  159. @uiSlider('Falloff', [0.01, 3], 0.01)
  160. @onChange2(SSAOPluginPass.prototype.setDirty)
  161. falloff = 1.3
  162. @serialize()
  163. @uiSlider('Num Samples', [1, 11], 1)
  164. @matDefine('NUM_SAMPLES', undefined, undefined, SSAOPluginPass.prototype.setDirty)
  165. numSamples = 8
  166. /**
  167. * Whether to check for gbuffer flag or not. This is used to disable SSAO casting by some objects. its enabled automatically by the SSAOPlugin when required.
  168. * This is disabled by default so that we dont read texture for no reason.
  169. */
  170. @matDefineBool('CHECK_GBUFFER_FLAG')
  171. checkGBufferFlag = false
  172. // todo after bilateralPass is implemented
  173. // @bindToValue({obj: 'bilateralPass', key: 'enabled', onChange: 'setDirty'})
  174. // smoothEnabled = true
  175. // todo after bilateralPass is implemented
  176. // @bindToValue({obj: 'bilateralPass', key: 'enabled', onChange: 'setDirty'})
  177. // smoothEdgeSharpness = true
  178. constructor(public readonly passId: IPassID, public target?: ValOrFunc<WebGLRenderTarget|undefined>) {
  179. super({
  180. defines: {
  181. ['LINEAR_DEPTH']: 1, // todo set from unpack extension
  182. ['NUM_SAMPLES']: 11,
  183. ['NUM_SPIRAL_TURNS']: 3,
  184. ['SSAO_PACKING']: 1, // 1 is (r: ssao, gba: depth), 2 is (rgb: ssao, a: 1), 3 is (rgba: packed_ssao), 4 is (rgb: packed_ssao, a: 1)
  185. ['PERSPECTIVE_CAMERA']: 1, // set in PerspectiveCamera2
  186. ['CHECK_GBUFFER_FLAG']: 0,
  187. },
  188. uniforms: {
  189. tLastThis: {value: null},
  190. screenSize: {value: new Vector2(0, 0)}, // set in ExtendedRenderMaterial
  191. saoData: {value: new Vector4()},
  192. frameCount: {value: 0}, // set in RenderManager
  193. cameraNearFar: {value: new Vector2(0.1, 1000)}, // set in PerspectiveCamera2
  194. projection: {value: new Matrix4()}, // set in PerspectiveCamera2
  195. saoBiasEpsilon: {value: new Vector3(1, 1, 1)},
  196. },
  197. vertexShader: shaderUtils.defaultVertex,
  198. fragmentShader: ssaoPass,
  199. }, 'tDiffuse') // why is tLastThis not here. because encoding and size doesnt matter?
  200. this.needsSwap = false
  201. this.clear = true
  202. // this.bilateralPass = new BilateralFilterPass(this._target as any, gBufferUnpack, 'rrrr')
  203. // this._multiplyPass = new GenericBlendTexturePass(this._target.texture as any, 'c = vec4((1.0-b.r) * a.xyz, a.a);')
  204. // this._getUiConfig = this._getUiConfig.bind(this)
  205. }
  206. render(renderer: IWebGLRenderer, writeBuffer: WebGLRenderTarget, readBuffer: WebGLRenderTarget, deltaTime: number, maskActive: boolean) {
  207. if (!this.enabled) return
  208. const target = getOrCall(this.target)
  209. if (!target) {
  210. console.warn('SSAOPluginPass: target not defined')
  211. return
  212. }
  213. this._updateParameters()
  214. // if (!this.material.defines.HAS_GBUFFER) {
  215. // console.warn('SSAOPluginPass: DepthNormalBuffer required for ssao')
  216. // }
  217. renderer.renderManager.blit(writeBuffer, {
  218. source: target.texture,
  219. })
  220. this.uniforms.tLastThis.value = writeBuffer.texture
  221. super.render(renderer, target, readBuffer, deltaTime, maskActive)
  222. // todo
  223. // if (this.smoothEnabled) {
  224. // this.bilateralPass.render(renderer, writeBuffer, readBuffer, deltaTime, maskActive)
  225. // }
  226. }
  227. private _updateParameters() {
  228. // const projectionScale = 1 / (Math.tan(DEG2RAD * (camera as any).fov / 2) * 2);
  229. const saoData = this.material.uniforms.saoData.value
  230. // saoData.x = projectionScale;
  231. saoData.y = this.intensity
  232. saoData.z = this.occlusionWorldRadius
  233. // saoData.w = this.accIndex_++;
  234. const saoBiasEpsilon = this.material.uniforms.saoBiasEpsilon.value
  235. saoBiasEpsilon.x = this.bias
  236. saoBiasEpsilon.y = 0.001
  237. saoBiasEpsilon.z = this.falloff
  238. // this.material.uniforms.size.value.set(this._target.texture.image?.width, this._target.texture.image?.height)
  239. }
  240. beforeRender(_: IScene, camera: ICamera, renderManager: IRenderManager) {
  241. if (!this.enabled) return
  242. this.updateShaderProperties([camera, renderManager])
  243. }
  244. readonly materialExtension: MaterialExtension = {
  245. extraUniforms: {
  246. tSSAOMap: ()=>({value: getOrCall(this.target)?.texture ?? null}),
  247. },
  248. shaderExtender: (shader, _material, _renderer) => {
  249. if (!shader.defines.SSAO_ENABLED) return
  250. // todo: only SSAO_PACKING = 1 and 2 is supported. Not 3 and 4 right now.
  251. shader.fragmentShader = shaderReplaceString(shader.fragmentShader, '#include <aomap_fragment>', ssaoPatch)
  252. },
  253. onObjectRender: (_object, material, renderer: any) => {
  254. // const opaque = !material.transparent && (!material.transmission || material.transmission < 0.001)
  255. const x: any = this.enabled && // opaque &&
  256. renderer.userData.screenSpaceRendering !== false &&
  257. !material.userData?.pluginsDisabled &&
  258. !material.userData?.ssaoDisabled ? 1 : 0
  259. if (material.defines!.SSAO_ENABLED !== x) {
  260. material.defines!.SSAO_ENABLED = x
  261. material.needsUpdate = true
  262. }
  263. },
  264. parsFragmentSnippet: (renderer)=>glsl`
  265. uniform sampler2D tSSAOMap;
  266. ${getTexelDecoding('tSSAOMap', getOrCall(this.target)?.texture, renderer!.capabilities.isWebGL2)}
  267. #include <simpleCameraHelpers>
  268. `,
  269. computeCacheKey: () => {
  270. return (this.enabled ? '1' : '0') + getOrCall(this.target)?.texture?.colorSpace
  271. },
  272. uuid: SSAOPlugin.PluginType,
  273. ...uiConfigMaterialExtension(this._getUiConfig.bind(this), SSAOPlugin.PluginType),
  274. isCompatible: material => {
  275. return (material as PhysicalMaterial).isPhysicalMaterial
  276. },
  277. }
  278. /**
  279. * Returns a uiConfig to toggle SSAO on a material.
  280. * This uiConfig is added to each material by extension
  281. * @param material
  282. * @private
  283. */
  284. protected _getUiConfig(material: IMaterial) {
  285. return {
  286. type: 'folder',
  287. label: 'SSAO',
  288. children: [
  289. {
  290. type: 'checkbox',
  291. label: 'Enabled',
  292. get value() {
  293. return !(material.userData.ssaoDisabled ?? false)
  294. },
  295. set value(v) {
  296. if (v === !(material.userData.ssaoDisabled ?? false)) return
  297. material.userData.ssaoDisabled = !v
  298. material.setDirty()
  299. },
  300. onChange: this.setDirty,
  301. },
  302. {
  303. type: 'checkbox',
  304. label: 'Cast SSAO',
  305. get value() {
  306. return !(material.userData.ssaoCastDisabled ?? false)
  307. },
  308. set value(v) {
  309. if (v === !(material.userData.ssaoCastDisabled ?? false)) return
  310. material.userData.ssaoCastDisabled = !v
  311. material.setDirty()
  312. },
  313. onChange: this.setDirty,
  314. },
  315. ],
  316. }
  317. }
  318. }
  319. declare module '../../core/IMaterial' {
  320. interface IMaterialUserData {
  321. /**
  322. * Disable SSAOPlugin for this material.
  323. */
  324. ssaoDisabled?: boolean
  325. /**
  326. * Cast SSAO on other objects.
  327. * if casting is not working when this is false, ensure render to depth is true, like for transparent objects
  328. */
  329. ssaoCastDisabled?: boolean
  330. }
  331. }