threepipe
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

MaterialExtender.ts 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import {IMaterial, IMaterialUserData} from '../core'
  2. import {getOrCall} from 'ts-browser-helpers'
  3. import {shaderReplaceString} from '../utils/shader-helpers'
  4. import {Object3D, Shader, WebGLRenderer} from 'three'
  5. import {MaterialExtension} from './MaterialExtension'
  6. import {generateUUID} from '../three/utils/misc'
  7. export class MaterialExtender {
  8. static VoidMain = 'void main()'
  9. static ApplyMaterialExtensions(material: IMaterial, shader: Shader, materialExtensions: MaterialExtension[], renderer: WebGLRenderer) {
  10. for (const materialExtension of materialExtensions) {
  11. this.ApplyMaterialExtension(material, shader, materialExtension, renderer)
  12. }
  13. }
  14. static ApplyMaterialExtension(material: IMaterial, shader: Shader, materialExtension: MaterialExtension, renderer: WebGLRenderer) {
  15. // Add parsFragmentSnippet just before void main in fragment shader
  16. let a = getOrCall(materialExtension.parsFragmentSnippet, renderer, material) ?? ''
  17. if (a.length) {
  18. shader.fragmentShader = shaderReplaceString(shader.fragmentShader, this.VoidMain, '\n' + a + '\n', {prepend: true})
  19. }
  20. // Add parsVertexSnippet just before void main in vertex shader
  21. a = getOrCall(materialExtension.parsVertexSnippet, renderer, material) ?? ''
  22. if (a.length) {
  23. shader.vertexShader = shaderReplaceString(shader.vertexShader, this.VoidMain, '\n' + a + '\n', {prepend: true})
  24. }
  25. // Add extra uniforms
  26. if (materialExtension.extraUniforms) {
  27. shader.uniforms = Object.assign(shader.uniforms, materialExtension.extraUniforms)
  28. }
  29. // Add extra defines and set needsUpdate to true if needed
  30. if (materialExtension.extraDefines)
  31. updateMaterialDefines(materialExtension.extraDefines, material)
  32. // Call shaderExtender if defined
  33. materialExtension.shaderExtender?.(shader, material, renderer)
  34. // Save last shader so that it can be used to check if shader has changed in extensions
  35. material.lastShader = shader
  36. }
  37. static CacheKeyForExtensions(material: IMaterial, materialExtensions: MaterialExtension[]): string {
  38. let r = ''
  39. for (const materialExtension of materialExtensions) {
  40. r += this.CacheKeyForExtension(material, materialExtension)
  41. }
  42. return r
  43. }
  44. static CacheKeyForExtension(material: IMaterial, materialExtension: MaterialExtension): string {
  45. let r = ''
  46. if (materialExtension.computeCacheKey) r += getOrCall(materialExtension.computeCacheKey, material)
  47. if (materialExtension.extraDefines) r += Object.values(materialExtension.extraDefines).join('')
  48. return r
  49. }
  50. static RegisterExtensions(material: IMaterial, customMaterialExtensions?: MaterialExtension[]): MaterialExtension[] {
  51. const exts = []
  52. if (!Array.isArray(material.materialExtensions)) material.materialExtensions = []
  53. if (customMaterialExtensions)
  54. for (const ext of customMaterialExtensions) {
  55. if (!ext.isCompatible || !ext.isCompatible(material) || material.materialExtensions.includes(ext)) continue
  56. else exts.push(ext)
  57. if (!ext.uuid) ext.uuid = generateUUID()
  58. if (!ext.__setDirty) ext.__setDirty = ()=>{
  59. if (!ext.updateVersion) ext.updateVersion = 0
  60. ext.updateVersion++
  61. }
  62. if (!ext.setDirty) ext.setDirty = ext.__setDirty
  63. }
  64. material.materialExtensions = [...material.materialExtensions || [], ...exts]
  65. if (!(material as any).__ext_beforeRenderListen) {
  66. (material as any).__ext_beforeRenderListen = true
  67. material.addEventListener('beforeRender', materialBeforeRender)
  68. }
  69. if (!(material as any).__ext_afterRenderListen) {
  70. (material as any).__ext_afterRenderListen = true
  71. material.addEventListener('afterRender', materialAfterRender)
  72. }
  73. return exts
  74. }
  75. static UnregisterExtensions(material: IMaterial, customMaterialExtensions?: MaterialExtension[]) {
  76. if (customMaterialExtensions) {
  77. material.materialExtensions = material.materialExtensions?.filter((v)=>!customMaterialExtensions.includes(v)) || []
  78. }
  79. if (!material.materialExtensions?.length) {
  80. material.removeEventListener('beforeRender', materialBeforeRender)
  81. material.removeEventListener('afterRender', materialAfterRender)
  82. ;(material as any).__ext_beforeRenderListen = false
  83. ;(material as any).__ext_afterRenderListen = false
  84. }
  85. }
  86. }
  87. function updateMaterialDefines(defines: any, material: IMaterial) {
  88. if (!material.defines) {
  89. console.warn('Material does not have defines', material) // todo: check when material.defines is undefined
  90. material.defines = {}
  91. }
  92. let flag = false
  93. const entries = Object.entries(defines)
  94. for (const [key, val] of entries) {
  95. if (val === undefined) {
  96. if (material.defines[key] !== undefined) {
  97. delete material.defines[key]
  98. flag = true
  99. }
  100. } else if (material.defines[key] !== val) {
  101. material.defines[key] = val
  102. flag = true
  103. }
  104. }
  105. if (flag) material.needsUpdate = true
  106. }
  107. function materialBeforeRender({target, object, renderer}:{object?: Object3D, renderer?: WebGLRenderer, target: IMaterial}) {
  108. const material = target
  109. if (!material || !object || !renderer) throw new Error('Invalid material, object or renderer')
  110. if (!material.materialExtensions) return
  111. for (const value of material.materialExtensions) {
  112. value.onObjectRender?.(object, material, renderer)
  113. if ((material as any).lastShader) {
  114. const updater = getOrCall(value.updaters) || []
  115. for (const v2 of updater) v2 && v2.updateShaderProperties((material as any).lastShader)
  116. }
  117. const udVersion: keyof IMaterialUserData = '_' + value.uuid + '_version' as any
  118. if (value.updateVersion !== material.userData[udVersion]) {
  119. material.userData[udVersion] = value.updateVersion
  120. material.needsUpdate = true
  121. }
  122. }
  123. }
  124. function materialAfterRender({target, object, renderer}:{object?: Object3D, renderer?: WebGLRenderer, target: IMaterial}) {
  125. const material = target
  126. if (!material || !object || !renderer) throw new Error('Invalid material, object or renderer')
  127. if (!material.materialExtensions) return
  128. for (const value of material.materialExtensions) {
  129. value.onAfterRender?.(object, material, renderer)
  130. }
  131. }