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.

GBufferPlugin.ts 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. import {
  2. BufferGeometry,
  3. Camera,
  4. ClampToEdgeWrapping,
  5. Color,
  6. DepthTexture,
  7. DoubleSide,
  8. FloatType,
  9. GLSL1,
  10. GLSL3,
  11. IUniform,
  12. NoBlending,
  13. NormalMapTypes,
  14. Object3D,
  15. Scene,
  16. ShaderMaterialParameters,
  17. TangentSpaceNormalMap,
  18. Texture,
  19. TextureDataType,
  20. UniformsLib,
  21. UniformsUtils,
  22. UnsignedByteType,
  23. UnsignedIntType,
  24. UnsignedShortType,
  25. Vector2,
  26. Vector4,
  27. WebGLMultipleRenderTargets,
  28. WebGLRenderer,
  29. WebGLRenderTarget,
  30. } from 'three'
  31. import {GBufferRenderPass} from '../../postprocessing'
  32. import {ThreeViewer} from '../../viewer'
  33. import {MaterialExtension, updateMaterialDefines} from '../../materials'
  34. import {PipelinePassPlugin} from '../base/PipelinePassPlugin'
  35. import {uiFolderContainer, uiImage} from 'uiconfig.js'
  36. import {shaderReplaceString} from '../../utils'
  37. import GBufferUnpack from './shaders/GBufferPlugin.unpack.glsl'
  38. import GBufferMatVert from './shaders/GBufferPlugin.mat.vert.glsl'
  39. import GBufferMatFrag from './shaders/GBufferPlugin.mat.frag.glsl'
  40. import {
  41. ICamera,
  42. IMaterial,
  43. IMaterialParameters,
  44. IRenderManager,
  45. IScene,
  46. ITexture,
  47. PhysicalMaterial,
  48. ShaderMaterial2,
  49. } from '../../core'
  50. export type GBufferPluginEventTypes = ''
  51. export type GBufferPluginTarget = WebGLMultipleRenderTargets | WebGLRenderTarget
  52. // export type GBufferPluginTarget = WebGLRenderTarget
  53. export type GBufferPluginPass = GBufferRenderPass<'gbuffer', GBufferPluginTarget>
  54. export interface GBufferUpdaterContext {
  55. material: IMaterial, renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D
  56. }
  57. export interface GBufferUpdater {
  58. updateGBufferFlags: (data: Vector4, context: GBufferUpdaterContext) => void
  59. }
  60. /**
  61. * G-Buffer Plugin
  62. *
  63. * Adds a pre-render pass to render the g-buffer(depth+normal+flags) to render target(s) that can be used as gbuffer and for postprocessing.
  64. * @category Plugins
  65. */
  66. @uiFolderContainer('G-Buffer Plugin')
  67. export class GBufferPlugin
  68. extends PipelinePassPlugin<GBufferPluginPass, 'gbuffer', GBufferPluginEventTypes> {
  69. readonly passId = 'gbuffer'
  70. public static readonly PluginType = 'GBuffer'
  71. target?: GBufferPluginTarget
  72. // @uiConfig(/* {readOnly: true}*/) // todo: fix bug in uiconfig or tpImageGenerator because of which 0 index is not showing in the UI, when we uncomment this
  73. textures: Texture[] = []
  74. @uiImage(/* {readOnly: true}*/)
  75. get normalDepthTexture(): ITexture|undefined {
  76. return this.textures[0]
  77. }
  78. @uiImage(/* {readOnly: true}*/)
  79. get flagsTexture(): ITexture|undefined {
  80. return this.textures[1]
  81. }
  82. @uiImage(/* {readOnly: true}*/)
  83. get depthTexture(): (ITexture&DepthTexture)|undefined {
  84. return this.target?.depthTexture
  85. }
  86. // @uiConfig() // not supported in this material yet
  87. material?: GBufferMaterial
  88. // @onChange(GBufferPlugin.prototype._depthPackingChanged)
  89. // @uiDropdown('Depth Packing', threeConstMappings.DepthPackingStrategies.uiConfig) packing: DepthPackingStrategies
  90. // @onChange2(GBufferPlugin.prototype._createTargetAndMaterial)
  91. // @uiDropdown('Buffer Type', threeConstMappings.TextureDataType.uiConfig)
  92. readonly bufferType: TextureDataType // cannot be changed after creation (for now)
  93. // @uiToggle()
  94. // @onChange2(GBufferPlugin.prototype._createTargetAndMaterial)
  95. readonly isPrimaryGBuffer: boolean // cannot be changed after creation (for now)
  96. // protected _depthPackingChanged() {
  97. // this.material.depthPacking = this.depthPacking
  98. // this.material.needsUpdate = true
  99. // if (this.unpackExtension && this.unpackExtension.extraDefines) {
  100. // this.unpackExtension.extraDefines.DEPTH_PACKING = this.depthPacking
  101. // this.unpackExtension.setDirty?.()
  102. // }
  103. // this.setDirty()
  104. // }
  105. unpackExtension: MaterialExtension = {
  106. shaderExtender: (shader)=>{
  107. const includes = ['gbuffer_unpack', 'packing'] as const
  108. const include = includes.find(i=>shader.fragmentShader.includes(`#include <${i}>`))
  109. shader.fragmentShader = shaderReplaceString(shader.fragmentShader,
  110. `#include <${include}>`,
  111. '\n' + GBufferUnpack + '\n', {append: include === 'packing'})
  112. },
  113. extraUniforms: {
  114. tNormalDepth: ()=>({value: this.normalDepthTexture}),
  115. tGBufferFlags: ()=>({value: this.flagsTexture}),
  116. tGBufferDepthTexture: ()=>({value: this.depthTexture}),
  117. },
  118. extraDefines: {
  119. // ['GBUFFER_PACKING']: BasicDepthPacking,
  120. ['HAS_NORMAL_DEPTH_BUFFER']: ()=>this.normalDepthTexture ? 1 : undefined,
  121. ['GBUFFER_HAS_DEPTH_TEXTURE']: ()=>this.depthTexture ? 1 : undefined,
  122. ['GBUFFER_HAS_FLAGS']: ()=>this.flagsTexture ? 1 : undefined,
  123. // ['HAS_FLAGS_BUFFER']: ()=>this.flagsTexture ? 1 : undefined,
  124. ['HAS_GBUFFER']: ()=>this.isPrimaryGBuffer && this.normalDepthTexture ? 1 : undefined,
  125. // LINEAR_DEPTH: 1, // to tell that the depth is linear. todo; see SSAOPlugin. also add support in DepthBufferPlugin?
  126. },
  127. priority: 100,
  128. isCompatible: () => true,
  129. }
  130. private _isPrimaryGBufferSet = false
  131. protected _createTargetAndMaterial(recreateTarget = true) {
  132. if (!this._viewer) return
  133. if (recreateTarget) this._disposeTarget()
  134. const useMultiple = this._viewer?.renderManager.isWebGL2 && this.renderFlagsBuffer
  135. if (!this.target) {
  136. this.target = this._viewer.renderManager.createTarget<GBufferPluginTarget>(
  137. {
  138. depthBuffer: true,
  139. samples: this._viewer.renderManager.zPrepass && this.isPrimaryGBuffer ? // requirement for zPrepass
  140. this._viewer.renderManager.composerTarget.samples || 0 : 0,
  141. type: this.bufferType,
  142. textureCount: useMultiple ? 2 : 1,
  143. depthTexture: this.renderDepthTexture,
  144. depthTextureType: this.depthTextureType,
  145. // magFilter: NearestFilter,
  146. // minFilter: NearestFilter,
  147. // generateMipmaps: false,
  148. // encoding: LinearEncoding,
  149. wrapS: ClampToEdgeWrapping,
  150. wrapT: ClampToEdgeWrapping,
  151. })
  152. if (Array.isArray(this.target.texture)) {
  153. this.target.texture[0].name = 'gbufferDepthNormal'
  154. this.target.texture[1].name = 'gbufferFlags'
  155. this.textures = this.target.texture
  156. // todo flag buffer filtering?
  157. // const flagTexture = this.flagsTexture
  158. // flagTexture.generateMipmaps = false
  159. // flagTexture.minFilter = NearestFilter
  160. // flagTexture.magFilter = NearestFilter
  161. } else {
  162. this.target.texture.name = 'gbufferDepthNormal'
  163. this.textures.push(this.target.texture)
  164. }
  165. }
  166. if (!this.material) {
  167. this.material = new GBufferMaterial(useMultiple, {
  168. blending: NoBlending,
  169. transparent: true,
  170. })
  171. }
  172. if (this._pass) this._pass.target = this.target
  173. if (this.isPrimaryGBuffer) {
  174. this._viewer.renderManager.gbufferTarget = this.target
  175. this._viewer.renderManager.gbufferUnpackExtension = this.unpackExtension
  176. this._viewer.renderManager.screenPass.material.registerMaterialExtensions([this.unpackExtension])
  177. this._isPrimaryGBufferSet = true
  178. }
  179. }
  180. protected _disposeTarget() {
  181. if (!this._viewer) return
  182. if (this.target) {
  183. this._viewer.renderManager.disposeTarget(this.target)
  184. this.target = undefined
  185. }
  186. this.textures = []
  187. if (this._isPrimaryGBufferSet) { // using a separate flag as when isPrimaryGBuffer is changed, we cannot check it.
  188. this._viewer.renderManager.gbufferTarget = undefined
  189. this._viewer.renderManager.gbufferUnpackExtension = undefined
  190. // this._viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this.unpackExtension]) // todo
  191. this._isPrimaryGBufferSet = false
  192. }
  193. }
  194. protected _createPass() {
  195. this._createTargetAndMaterial(true)
  196. if (!this.target) throw new Error('GBufferPlugin: target not created')
  197. if (!this.material) throw new Error('GBufferPlugin: material not created')
  198. this.material.userData.isGBufferMaterial = true
  199. const pass = new GBufferRenderPass(this.passId, this.target, this.material, new Color(1, 1, 1), 1)
  200. const preprocessMaterial = pass.preprocessMaterial
  201. pass.preprocessMaterial = (m) => preprocessMaterial(m, m.userData.renderToDepth) // if renderToDepth is undefined then renderToGbuffer is taken internally
  202. pass.before = ['render']
  203. pass.after = []
  204. pass.required = ['render']
  205. return pass
  206. }
  207. protected _beforeRender(scene: IScene, camera: ICamera, renderManager: IRenderManager): boolean {
  208. if (!super._beforeRender(scene, camera, renderManager) || !this.material) return false
  209. camera.updateShaderProperties(this.material)
  210. return true
  211. }
  212. constructor(
  213. bufferType: TextureDataType = UnsignedByteType,
  214. isPrimaryGBuffer = true,
  215. enabled = true,
  216. public renderFlagsBuffer: boolean = true,
  217. public renderDepthTexture: boolean = false,
  218. public depthTextureType: typeof UnsignedShortType | typeof UnsignedIntType | typeof FloatType /* | typeof UnsignedInt248Type*/ = UnsignedIntType,
  219. // packing: DepthPackingStrategies = BasicDepthPacking,
  220. ) {
  221. super()
  222. this.enabled = enabled
  223. this.bufferType = bufferType
  224. this.isPrimaryGBuffer = isPrimaryGBuffer
  225. // this.depthPacking = depthPacking
  226. }
  227. registerGBufferUpdater(key: string, updater: GBufferUpdater['updateGBufferFlags']): void {
  228. if (this.material) this.material.flagUpdaters.set(key, updater)
  229. }
  230. unregisterGBufferUpdater(key: string): void {
  231. if (this.material) this.material.flagUpdaters.delete(key)
  232. }
  233. onRemove(viewer: ThreeViewer): void {
  234. this._disposeTarget()
  235. this.material?.dispose()
  236. this.material = undefined
  237. return super.onRemove(viewer)
  238. }
  239. /**
  240. * @deprecated use {@link normalDepthTexture} instead
  241. */
  242. getDepthNormal() {
  243. return this.textures.length > 0 ? this.textures[0] : undefined
  244. }
  245. /**
  246. * @deprecated use {@link flagsTexture} instead
  247. */
  248. getFlagsTexture() {
  249. return this.textures.length > 1 ? this.textures[1] : undefined
  250. }
  251. /**
  252. * @deprecated use {@link target} instead
  253. */
  254. getTarget() {
  255. return this.target
  256. }
  257. /**
  258. * @deprecated use {@link unpackExtension} instead
  259. */
  260. getUnpackSnippet(): string {
  261. return GBufferUnpack
  262. }
  263. /**
  264. * @deprecated use {@link unpackExtension} instead, it adds the same uniforms and defines
  265. * @param material
  266. */
  267. updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}, needsUpdate?: boolean}): this {
  268. if (material.uniforms.tNormalDepth) material.uniforms.tNormalDepth.value = this.normalDepthTexture ?? undefined
  269. else this._viewer?.console.warn('BaseRenderer: no uniform: tNormalDepth')
  270. if (material.uniforms.tGBufferFlags) {
  271. material.uniforms.tGBufferFlags.value = this.flagsTexture ?? undefined
  272. const t = material.uniforms.tGBufferFlags.value ? 1 : 0
  273. if (t !== material.defines.GBUFFER_HAS_FLAGS) {
  274. material.defines.GBUFFER_HAS_FLAGS = t
  275. material.needsUpdate = true
  276. }
  277. }
  278. return this
  279. }
  280. }
  281. /**
  282. * Renders DepthNormal to a texture and flags to another
  283. */
  284. export class GBufferMaterial extends ShaderMaterial2 {
  285. constructor(multipleRT = true, parameters?: ShaderMaterialParameters & IMaterialParameters) {
  286. super({
  287. vertexShader: GBufferMatVert,
  288. fragmentShader: GBufferMatFrag,
  289. uniforms: UniformsUtils.merge([
  290. UniformsLib.common,
  291. UniformsLib.bumpmap,
  292. UniformsLib.normalmap,
  293. UniformsLib.displacementmap,
  294. {
  295. cameraNearFar: {value: new Vector2(0.1, 1000)}, // this has to be set from outside
  296. flags: {value: new Vector4(255, 255, 255, 255)},
  297. },
  298. ]),
  299. defines: {
  300. // eslint-disable-next-line @typescript-eslint/naming-convention
  301. IS_GLSL3: multipleRT ? '1' : '0',
  302. },
  303. glslVersion: multipleRT ? GLSL3 : GLSL1,
  304. ...parameters,
  305. })
  306. this.reset()
  307. }
  308. flagUpdaters: Map<string, GBufferUpdater['updateGBufferFlags']> = new Map()
  309. normalMapType: NormalMapTypes = TangentSpaceNormalMap
  310. flatShading = false
  311. onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D) {
  312. super.onBeforeRender(renderer, scene, camera, geometry, object)
  313. let material = (object as any).material as IMaterial & Partial<PhysicalMaterial>
  314. if (Array.isArray(material)) { // todo: add support for multi materials.
  315. material = material[0]
  316. }
  317. if (!material) return
  318. const setMap = (key: keyof IMaterial)=>{
  319. const map = material[key]
  320. if (!map) return
  321. this.uniforms[key].value = map
  322. if (!this.uniforms[key + 'Transform']) console.error('GBufferMaterial: ' + key + 'Transform is not defined in uniform')
  323. else renderer.materials.refreshTransformUniform(map, this.uniforms[key + 'Transform'])
  324. }
  325. setMap('map')
  326. if (material.side !== undefined) this.side = material.side ?? DoubleSide
  327. setMap('alphaMap')
  328. if (material.alphaTest !== undefined) this.alphaTest = material.alphaTest < 1e-4 ? 1e-4 : material.alphaTest
  329. setMap('bumpMap')
  330. if (material.bumpScale !== undefined) this.uniforms.bumpScale.value = material.bumpScale
  331. setMap('normalMap')
  332. if (material.normalScale !== undefined) this.uniforms.normalScale.value.copy(material.normalScale)
  333. if (material.normalMapType !== undefined) this.normalMapType = material.normalMapType
  334. if (material.flatShading !== undefined) this.flatShading = material.flatShading
  335. setMap('displacementMap')
  336. if (material.displacementScale !== undefined) this.uniforms.displacementScale.value = material.displacementScale
  337. if (material.displacementBias !== undefined) this.uniforms.displacementBias.value = material.displacementBias
  338. if (material.wireframe !== undefined) this.wireframe = material.wireframe
  339. if (material.wireframeLinewidth !== undefined) this.wireframeLinewidth = material.wireframeLinewidth
  340. /*
  341. GBuffer Flags has the following data
  342. 1st Rendertarget has Depth and Normal buffers
  343. 2nd Render Target::
  344. x : Empty
  345. y : first 3 bits lut index, second 5 bits bevel radius
  346. z : material id (userData.gBufferData?.materialId, userData.matId)
  347. w : this field is for setting bits - lutEnable-0, tonemap-1, bloom-2, ssao(cast)-3, dof-4
  348. */
  349. this.uniforms.flags.value.set(255, 255, 255, 255)
  350. const materialId = material.userData.gBufferData?.materialId ?? material.userData.matId // matId for backward compatibility
  351. this.uniforms.flags.value.z = materialId || 0
  352. this.flagUpdaters.forEach((updater)=> updater(this.uniforms.flags.value, {material, renderer, scene, camera, geometry, object}))
  353. this.uniforms.flags.value.x /= 255
  354. this.uniforms.flags.value.y /= 255
  355. this.uniforms.flags.value.z /= 255
  356. this.uniforms.flags.value.w /= 255
  357. this.uniformsNeedUpdate = true
  358. updateMaterialDefines({
  359. // ['USE_ALPHAMAP']: this.uniforms.alphaMap.value ? 1 : undefined,
  360. ['ALPHAMAP_UV']: this.uniforms.alphaMap.value ? 'uv' : undefined, // todo use getChannel, see WebGLPrograms.js
  361. ['USE_DISPLACEMENTMAP']: this.uniforms.displacementMap.value ? 1 : undefined,
  362. ['DISPLACEMENTMAP_UV']: this.uniforms.displacementMap.value ? 'uv' : undefined, // todo use getChannel, see WebGLPrograms.js
  363. ['ALPHA_I_RGBA_PACKING']: material.userData.ALPHA_I_RGBA_PACKING ? 1 : undefined,
  364. ['FORCED_LINEAR_DEPTH']: material.userData.forcedLinearDepth ?? undefined, // todo add to DepthBufferPlugin as well.
  365. }, material)
  366. // todo: do the same in DepthBufferPlugin and NormalBufferPlugin
  367. // what about the material extension settings in the userData of the source materials?
  368. if (material.materialExtensions?.length) {
  369. this.registerMaterialExtensions(material.materialExtensions)
  370. }
  371. // this.transparent = true
  372. this.needsUpdate = true
  373. }
  374. onAfterRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D) {
  375. super.onAfterRender(renderer, scene, camera, geometry, object)
  376. let material = (object as any).material as IMaterial & Partial<PhysicalMaterial>
  377. if (Array.isArray(material)) { // todo: add support for multi materials.
  378. material = material[0]
  379. }
  380. if (!material) return
  381. if (material.materialExtensions?.length) {
  382. this.unregisterMaterialExtensions(material.materialExtensions)
  383. }
  384. this.reset()
  385. }
  386. reset() {
  387. this.uniforms.map.value = null
  388. this.side = DoubleSide
  389. this.uniforms.alphaMap.value = null
  390. this.alphaTest = 0.001
  391. this.uniforms.bumpMap.value = null
  392. this.uniforms.bumpScale.value = 1
  393. this.uniforms.normalMap.value = null
  394. this.uniforms.normalScale.value.set(1, 1)
  395. this.normalMapType = TangentSpaceNormalMap
  396. this.flatShading = false
  397. this.uniforms.displacementMap.value = null
  398. this.uniforms.displacementScale.value = 1
  399. this.uniforms.displacementBias.value = 0
  400. this.uniforms.flags.value.set(255, 255, 255, 255)
  401. this.wireframe = false
  402. this.wireframeLinewidth = 1
  403. }
  404. }
  405. /**
  406. * @deprecated use GBufferMaterial instead
  407. */
  408. export class DepthNormalMaterial extends GBufferMaterial {
  409. constructor(multipleRT: boolean, parameters?: ShaderMaterialParameters & IMaterialParameters) {
  410. super(multipleRT, parameters)
  411. console.warn('DepthNormalMaterial is deprecated, use GBufferMaterial instead')
  412. }
  413. }