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.

iMaterialCommons.ts 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import {
  2. AddEquation,
  3. AlwaysStencilFunc,
  4. ColorManagement,
  5. FrontSide,
  6. KeepStencilOp,
  7. LessEqualDepth,
  8. Material,
  9. MaterialParameters,
  10. NormalBlending,
  11. OneMinusSrcAlphaFactor,
  12. Scene,
  13. Shader,
  14. SrcAlphaFactor,
  15. WebGLRenderer,
  16. } from 'three'
  17. import {copyProps} from 'ts-browser-helpers'
  18. import {copyMaterialUserData} from '../../utils/serialization'
  19. import {MaterialExtender, MaterialExtension} from '../../materials'
  20. import {IScene} from '../IScene'
  21. import {IMaterial, IMaterialEventMap, IMaterialSetDirtyOptions} from '../IMaterial'
  22. import {isInScene} from '../../three/utils'
  23. /**
  24. * Map of all material properties and their default values in three.js - Material.js
  25. * This is used to copy properties and serialize/deserialize them.
  26. * @note: Upgrade note: keep updated from three.js/src/Material.js:22
  27. */
  28. export const threeMaterialPropList = {
  29. // uuid: '', // DONT COPY, should remain commented
  30. name: '',
  31. blending: NormalBlending,
  32. side: FrontSide,
  33. vertexColors: false,
  34. opacity: 1,
  35. transparent: false,
  36. blendSrc: SrcAlphaFactor,
  37. blendDst: OneMinusSrcAlphaFactor,
  38. blendEquation: AddEquation,
  39. blendSrcAlpha: null,
  40. blendDstAlpha: null,
  41. blendEquationAlpha: null,
  42. blendColor: '#000000',
  43. blendAlpha: 0,
  44. depthFunc: LessEqualDepth,
  45. depthTest: true,
  46. depthWrite: true,
  47. stencilWriteMask: 0xff,
  48. stencilFunc: AlwaysStencilFunc,
  49. stencilRef: 0,
  50. stencilFuncMask: 0xff,
  51. stencilFail: KeepStencilOp,
  52. stencilZFail: KeepStencilOp,
  53. stencilZPass: KeepStencilOp,
  54. stencilWrite: false,
  55. clippingPlanes: null,
  56. clipIntersection: false,
  57. clipShadows: false,
  58. shadowSide: null,
  59. colorWrite: true,
  60. precision: null,
  61. polygonOffset: false,
  62. polygonOffsetFactor: 0,
  63. polygonOffsetUnits: 0,
  64. dithering: false,
  65. alphaToCoverage: false,
  66. premultipliedAlpha: false,
  67. forceSinglePass: false,
  68. visible: true,
  69. toneMapped: true,
  70. userData: {},
  71. // wireframeLinecap: 'round',
  72. // wireframeLinejoin: 'round',
  73. alphaTest: 0,
  74. alphaHash: false,
  75. // fog: true,
  76. }
  77. export const iMaterialCommons = {
  78. threeMaterialPropList,
  79. setDirty: function(this: IMaterial, options?: IMaterialSetDirtyOptions): void {
  80. if (options?.needsUpdate !== false) this.needsUpdate = true
  81. this.dispatchEvent({bubbleToObject: true, bubbleToParent: true, ...options, type: 'materialUpdate'}) // this sets sceneUpdate in root scene
  82. if (options?.last !== false) this.uiConfig?.uiRefresh?.(true, 'postFrame', 1)
  83. },
  84. setValues: (superSetValues: Material['setValues']): IMaterial['setValues'] =>
  85. function(this: IMaterial, parameters: Material | (MaterialParameters & {type?: string})): IMaterial {
  86. // legacy check for old color management(non-sRGB) in material.setValues todo: move this to Material.fromJSON
  87. const legacyColors = (parameters as any)?.metadata && (parameters as any)?.metadata.version <= 4.5
  88. const lastColorManagementEnabled = ColorManagement.enabled
  89. if (legacyColors) ColorManagement.enabled = false
  90. const propList = this.constructor.MaterialProperties
  91. const params: any = !propList ? {...parameters} : copyProps(parameters, {} as any, Array.from(Object.keys(propList)))
  92. // remove undefined values
  93. for (const key of Object.keys(params)) if (params[key] === undefined) delete params[key]
  94. const userData = params.userData
  95. delete params.userData
  96. // todo: can migrate to @serialize for properties which have UI etc and use super.setValues for the rest like threeMaterialPropList
  97. superSetValues.call(this, params)
  98. if (userData) copyMaterialUserData(this.userData, userData)
  99. // bump map scale fix todo: move this to Material.fromJSON
  100. // https://github.com/repalash/three.js/commit/7b13bb515866f6a002928bd28d0a793cafeaeb1a
  101. const legacyBumpScale = (parameters as any)?.metadata && (parameters as any)?.metadata.version <= 4.6
  102. if ((legacyBumpScale || this.userData.legacyBumpScale) && (this as any)?.bumpScale !== undefined && this?.bumpMap && this.defines) {
  103. console.warn('MaterialManager: Old format material loaded, bump map might be incorrect.', parameters, (parameters as any).bumpScale)
  104. this.defines.BUMP_MAP_SCALE_LEGACY = '1'
  105. this.userData.legacyBumpScale = true
  106. this.needsUpdate = true
  107. }
  108. if (legacyColors) ColorManagement.enabled = lastColorManagementEnabled
  109. this.setDirty?.()
  110. return this
  111. },
  112. dispose: (superDispose: Material['dispose']): IMaterial['dispose'] =>
  113. function(this: IMaterial, force = true): void {
  114. if (!force && (this.userData.disposeOnIdle === false || isInScene(this))) return
  115. superDispose.call(this)
  116. },
  117. clone: (superClone: Material['clone']): IMaterial['clone'] =>
  118. function(this: IMaterial, track = false): IMaterial {
  119. if (track) {
  120. if (!this.userData.cloneId) {
  121. this.userData.cloneId = '0'
  122. }
  123. if (!this.userData.cloneCount) {
  124. this.userData.cloneCount = 0
  125. }
  126. this.userData.cloneCount += 1
  127. }
  128. const material: IMaterial = this.generator?.({})?.setValues(this, false) ?? superClone.call(this)
  129. if (track) {
  130. material.userData.cloneId = material.userData.cloneId + '_' + this.userData.cloneCount
  131. material.userData.cloneCount = 0
  132. material.name = (material.name || 'mat') + '_' + material.userData.cloneId
  133. }
  134. return material
  135. },
  136. dispatchEvent: (superDispatchEvent: Material['dispatchEvent']): IMaterial['dispatchEvent'] =>
  137. function(this: IMaterial, event): void {
  138. superDispatchEvent.call(this, event)
  139. const type = event.type
  140. if ((event as IMaterialEventMap['materialUpdate']).bubbleToObject && (
  141. type === 'beforeDeserialize' || type === 'materialUpdate' || type === 'textureUpdate' // todo - add more events
  142. )) {
  143. this.appliedMeshes.forEach(m => m.dispatchEvent({...event, material: this, type}))
  144. }
  145. },
  146. customProgramCacheKey: function(this: IMaterial): string {
  147. return MaterialExtender.CacheKeyForExtensions(this, this.materialExtensions) + this.userData.inverseAlphaMap
  148. },
  149. registerMaterialExtensions: function(this: IMaterial, customMaterialExtensions: MaterialExtension[]): void {
  150. MaterialExtender.RegisterExtensions(this, customMaterialExtensions)
  151. },
  152. unregisterMaterialExtensions: function(this: IMaterial, customMaterialExtensions: MaterialExtension[]): void {
  153. MaterialExtender.UnregisterExtensions(this, customMaterialExtensions)
  154. },
  155. // shader is not Shader but WebglUniforms.getParameters return value type so includes defines
  156. onBeforeCompile: function(this: IMaterial, shader: Shader, renderer: WebGLRenderer): void {
  157. if (this.materialExtensions) MaterialExtender.ApplyMaterialExtensions(this, shader, this.materialExtensions, renderer)
  158. this.dispatchEvent({type: 'beforeCompile', shader, renderer})
  159. shader.fragmentShader = shader.fragmentShader.replaceAll('#glMarker', '// ')
  160. shader.vertexShader = shader.vertexShader.replaceAll('#glMarker', '// ')
  161. },
  162. onBeforeRender: function(this: IMaterial, renderer, scene: Scene & Partial<IScene>, camera, geometry, object) {
  163. if (this.envMapIntensity !== undefined && !this.userData.separateEnvMapIntensity && scene.envMapIntensity !== undefined) {
  164. this.userData.__envIntensity = this.envMapIntensity
  165. this.envMapIntensity = scene.envMapIntensity
  166. }
  167. if (this.defines && this.envMap !== undefined && scene.fixedEnvMapDirection !== undefined) {
  168. if (scene.fixedEnvMapDirection) {
  169. if (!this.defines.FIX_ENV_DIRECTION) {
  170. this.defines.FIX_ENV_DIRECTION = '1'
  171. this.needsUpdate = true
  172. }
  173. } else if (this.defines.FIX_ENV_DIRECTION !== undefined) {
  174. delete this.defines.FIX_ENV_DIRECTION
  175. this.needsUpdate = true
  176. }
  177. }
  178. this.dispatchEvent({type: 'beforeRender', renderer, scene, camera, geometry, object})
  179. } as IMaterial['onBeforeRender'],
  180. onAfterRender: function(this: IMaterial, renderer, scene: Scene & Partial<IScene>, camera, geometry, object) {
  181. if (this.userData.__envIntensity !== undefined) {
  182. this.envMapIntensity = this.userData.__envIntensity
  183. delete this.userData.__envIntensity
  184. }
  185. this.dispatchEvent({type: 'afterRender', renderer, scene, camera, geometry, object})
  186. } as IMaterial['onAfterRender'],
  187. onBeforeCompileOverride: (superOnBeforeCompile: Material['onBeforeCompile']): IMaterial['onBeforeCompile'] =>
  188. function(this: IMaterial, shader: Shader, renderer: WebGLRenderer): void {
  189. iMaterialCommons.onBeforeCompile.call(this, shader, renderer)
  190. superOnBeforeCompile.call(this, shader, renderer)
  191. },
  192. onBeforeRenderOverride: (superOnBeforeRender: Material['onBeforeRender']): IMaterial['onBeforeRender'] =>
  193. function(this: IMaterial, ...args: Parameters<Material['onBeforeRender']>): void {
  194. superOnBeforeRender.call(this, ...args)
  195. iMaterialCommons.onBeforeRender.call(this, ...args)
  196. },
  197. onAfterRenderOverride: (superOnAfterRender: Material['onAfterRender']): IMaterial['onAfterRender'] =>
  198. function(this: IMaterial, ...args: Parameters<Material['onAfterRender']>): void {
  199. superOnAfterRender.call(this, ...args)
  200. iMaterialCommons.onAfterRender.call(this, ...args)
  201. },
  202. customProgramCacheKeyOverride: (superCustomPropertyCacheKey: Material['customProgramCacheKey']): IMaterial['customProgramCacheKey'] =>
  203. function(this: IMaterial): string {
  204. return superCustomPropertyCacheKey.call(this) + iMaterialCommons.customProgramCacheKey.call(this)
  205. },
  206. upgradeMaterial: upgradeMaterial,
  207. // todo;
  208. } as const
  209. /**
  210. * Convert a standard three.js {@link Material} to {@link IMaterial}
  211. */
  212. export function upgradeMaterial(this: IMaterial): IMaterial {
  213. if (!this.isMaterial) {
  214. console.error('Material is not a material', this)
  215. return this
  216. }
  217. if (!this.setDirty) this.setDirty = iMaterialCommons.setDirty
  218. if (!this.appliedMeshes) this.appliedMeshes = new Set()
  219. if (!this.userData) this.userData = {}
  220. this.userData.uuid = this.uuid // for serialization
  221. // legacy
  222. if (!this.userData.setDirty) this.userData.setDirty = (e: any) => {
  223. console.warn('userData.setDirty is deprecated. Use setDirty instead.')
  224. this.setDirty(e)
  225. }
  226. if (this.assetType === 'material') return this // already upgraded
  227. this.assetType = 'material'
  228. this.setValues = iMaterialCommons.setValues(this.setValues)
  229. this.dispose = iMaterialCommons.dispose(this.dispose)
  230. this.clone = iMaterialCommons.clone(this.clone)
  231. this.dispatchEvent = iMaterialCommons.dispatchEvent(this.dispatchEvent)
  232. // material extensions
  233. if (!this.extraUniformsToUpload) this.extraUniformsToUpload = {}
  234. if (!this.materialExtensions) this.materialExtensions = []
  235. if (!this.registerMaterialExtensions) this.registerMaterialExtensions = iMaterialCommons.registerMaterialExtensions
  236. if (!this.unregisterMaterialExtensions) this.unregisterMaterialExtensions = iMaterialCommons.unregisterMaterialExtensions
  237. this.onBeforeCompile = iMaterialCommons.onBeforeCompileOverride(this.onBeforeCompile)
  238. this.onBeforeRender = iMaterialCommons.onBeforeRenderOverride(this.onBeforeRender)
  239. this.onAfterRender = iMaterialCommons.onAfterRenderOverride(this.onAfterRender)
  240. this.customProgramCacheKey = iMaterialCommons.customProgramCacheKeyOverride(this.customProgramCacheKey)
  241. // todo: add uiconfig, serialization, other stuff from UnlitMaterial?
  242. // dispose uiconfig etc. on dispose
  243. return this
  244. }