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.

GLTFMaterialExtrasExtension.ts 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import type {GLTF, GLTFLoaderPlugin, GLTFParser} from 'three/examples/jsm/loaders/GLTFLoader'
  2. import {ThreeSerialization} from '../../utils/serialization'
  3. import {Color, DoubleSide, Material} from 'three'
  4. import type {GLTFExporterPlugin, GLTFWriter} from 'three/examples/jsm/exporters/GLTFExporter'
  5. import {ITexture} from '../../core'
  6. export class GLTFMaterialExtrasExtension {
  7. static readonly WebGiMaterialExtrasExtension = 'WEBGI_material_extras'
  8. /**
  9. * for physical material
  10. * Also {@link Export}
  11. * @param loadConfigResources
  12. */
  13. static Import = (loadConfigResources: (res: any)=>any)=> (parser: GLTFParser): GLTFLoaderPlugin=>({
  14. name: '__' + GLTFMaterialExtrasExtension.WebGiMaterialExtrasExtension, // __ is prefix so that the extension is added to userdata, and we can process later in afterRoot
  15. afterRoot: async(result: GLTF) => {
  16. const scenes = result.scenes || (result.scene ? [result.scene] : [])
  17. for (const s of scenes) {
  18. const resExt = s.userData?.gltfExtensions?.[GLTFMaterialExtrasExtension.WebGiMaterialExtrasExtension] // Note: see exporter for details of material extra resources in scene.
  19. const resources = resExt?.resources ? await loadConfigResources(resExt.resources) : {}
  20. s.traverse((obj: any)=>{
  21. const o = obj?.material
  22. if (!o?.isMaterial) return // todo array materials
  23. const ext = o.userData?.gltfExtensions?.[GLTFMaterialExtrasExtension.WebGiMaterialExtrasExtension]
  24. if (!ext) return
  25. // extras from MaterialLoader.js
  26. if (ext.emissiveIntensity !== undefined) o.emissiveIntensity = ext.emissiveIntensity // kept for old versions, this is not saved in extras because of KHR_materials_emissive_strength
  27. // bumpMap, displacementMap, lightMap, alphaMap moved to separate extensions
  28. // if (material.shininess !== undefined) dat.shininess = material.shininess
  29. if (ext.fog !== undefined) o.fog = ext.fog
  30. if (ext.flatShading !== undefined) o.flatShading = ext.flatShading
  31. if (ext.blending !== undefined) o.blending = ext.blending
  32. // if (ext.combine !== undefined) o.combine = ext.combine
  33. if (ext.side !== undefined) o.side = ext.side
  34. if (ext.shadowSide !== undefined) o.shadowSide = ext.shadowSide
  35. if (ext.depthFunc !== undefined) o.depthFunc = ext.depthFunc
  36. if (ext.depthTest !== undefined) o.depthTest = ext.depthTest
  37. if (ext.depthWrite !== undefined) o.depthWrite = ext.depthWrite
  38. if (ext.colorWrite !== undefined) o.colorWrite = ext.colorWrite
  39. if (ext.vertexColors !== undefined) o.vertexColors = ext.vertexColors // this is override, it is also set in GLTFLoader if geometry has vertex colors, todo: check how to do this in a better way
  40. if (ext.alphaTest !== undefined) o.alphaTest = ext.alphaTest
  41. if (ext.alphaHash !== undefined) o.alphaHash = ext.alphaHash
  42. // if (ext.transparent !== undefined) o.transparent = ext.transparent // this is set by GLTFLoader based on alpha mode
  43. if (ext.envMapIntensity !== undefined) o.envMapIntensity = ext.envMapIntensity // for when separateEnvMapIntensity is true
  44. // if (ext.envMapSlotKey !== undefined) o.envMapSlotKey = ext.envMapSlotKey // in userdata
  45. if (ext.blendSrc !== undefined) o.blendSrc = ext.blendSrc
  46. if (ext.blendDst !== undefined) o.blendDst = ext.blendDst
  47. if (ext.blendEquation !== undefined) o.blendEquation = ext.blendEquation
  48. if (ext.blendSrcAlpha !== undefined) o.blendSrcAlpha = ext.blendSrcAlpha
  49. if (ext.blendDstAlpha !== undefined) o.blendDstAlpha = ext.blendDstAlpha
  50. if (ext.blendEquationAlpha !== undefined) o.blendEquationAlpha = ext.blendEquationAlpha
  51. if (ext.blendColor !== undefined && o.blendColor !== undefined) (o.blendColor as Color).setHex(ext.blendColor)
  52. if (ext.blendAlpha !== undefined) o.blendAlpha = ext.blendAlpha
  53. // if (ext.stencilWrite !== undefined) o.stencilWrite = ext.stencilWrite
  54. // if (ext.stencilWriteMask !== undefined) o.stencilWriteMask = ext.stencilWriteMask
  55. // if (ext.stencilFunc !== undefined) o.stencilFunc = ext.stencilFunc
  56. // if (ext.stencilRef !== undefined) o.stencilRef = ext.stencilRef
  57. // if (ext.stencilFuncMask !== undefined) o.stencilFuncMask = ext.stencilFuncMask
  58. // if (ext.stencilFail !== undefined) o.stencilFail = ext.stencilFail
  59. // if (ext.stencilZFail !== undefined) o.stencilZFail = ext.stencilZFail
  60. // if (ext.stencilZPass !== undefined) o.stencilZPass = ext.stencilZPass
  61. if (ext.wireframe !== undefined) o.wireframe = ext.wireframe
  62. if (ext.wireframeLinewidth !== undefined) o.wireframeLinewidth = ext.wireframeLinewidth
  63. if (ext.wireframeLinecap !== undefined) o.wireframeLinecap = ext.wireframeLinecap
  64. if (ext.wireframeLinejoin !== undefined) o.wireframeLinejoin = ext.wireframeLinejoin
  65. if (ext.rotation !== undefined) o.rotation = ext.rotation
  66. // if (ext.linewidth !== 1) o.linewidth = ext.linewidth
  67. // if (ext.dashSize !== undefined) o.dashSize = ext.dashSize
  68. // if (ext.gapSize !== undefined) o.gapSize = ext.gapSize
  69. // if (ext.scale !== undefined) o.scale = ext.scale
  70. if (ext.polygonOffset !== undefined) o.polygonOffset = ext.polygonOffset
  71. if (ext.polygonOffsetFactor !== undefined) o.polygonOffsetFactor = ext.polygonOffsetFactor
  72. if (ext.polygonOffsetUnits !== undefined) o.polygonOffsetUnits = ext.polygonOffsetUnits
  73. if (ext.dithering !== undefined) o.dithering = ext.dithering
  74. if (ext.alphaToCoverage !== undefined) o.alphaToCoverage = ext.alphaToCoverage
  75. if (ext.premultipliedAlpha !== undefined) o.premultipliedAlpha = ext.premultipliedAlpha
  76. // if (ext.visible !== undefined) o.visible = ext.visible
  77. if (ext.toneMapped !== undefined) o.toneMapped = ext.toneMapped
  78. // we are ignoring the normalScale set from the GLTFLoader, because it is not correct in some cases.
  79. // todo: check if this is still the case
  80. if (ext.normalScale !== undefined && o.normalScale !== undefined) {
  81. if (Array.isArray(ext.normalScale)) o.normalScale.fromArray(ext.normalScale)
  82. else if (typeof ext.normalScale === 'number') o.normalScale.set(ext.normalScale, ext.normalScale)
  83. else console.warn('normalScale is not an array or number', ext.normalScale)
  84. }
  85. if (ext.reflectivity !== undefined) o.reflectivity = ext.reflectivity // this is not present in the extras exporter because KHR_materials_ior, todo: kept for backward compatibility, remove ?
  86. // if (ext.refractionRatio !== undefined) o.refractionRatio = ext.refractionRatio
  87. // todo forceSinglePass
  88. // todo: make extension for these like GLTFMaterialsBumpMapExtension
  89. // if ( ext.gradientMap !== undefined ) o.gradientMap = getTexture( json.gradientMap );
  90. Object.entries(ext).forEach(([key, value]: [string, any])=>{
  91. if (key.startsWith('_')) return
  92. if (value && value.resource && typeof value.resource === 'string') {
  93. o[key] = ThreeSerialization.Deserialize(value, o[key], resources)
  94. }
  95. })
  96. delete o.userData.gltfExtensions[GLTFMaterialExtrasExtension.WebGiMaterialExtrasExtension]
  97. // legacy bump map scale fix, test model - test model - http://asset-samples.threepipe.org/tests/bumpmap_normalize_migrate.glb
  98. const assetVersion = parser.json?.asset?.version ? parseFloat(parser.json?.asset?.version) : null
  99. // https://github.com/repalash/three.js/commit/7b13bb515866f6a002928bd28d0a793cafeaeb1a
  100. if ((o.userData.legacyBumpScale || assetVersion && assetVersion <= 2.0) && (o as any)?.bumpScale !== undefined && o?.bumpMap && o.defines) {
  101. console.warn('MaterialManager: Old format material loaded, bump map might be incorrect.', o, (o as any).bumpScale)
  102. o.defines.BUMP_MAP_SCALE_LEGACY = '1'
  103. o.userData.legacyBumpScale = true
  104. o.needsUpdate = true
  105. }
  106. })
  107. // todo: check for resources that are not used and dispose them? see todo in ThreeViewer.fromJSON
  108. if (resExt) delete s.userData.gltfExtensions[GLTFMaterialExtrasExtension.WebGiMaterialExtrasExtension]
  109. }
  110. },
  111. })
  112. /**
  113. * Also see {@link Import}
  114. * @param w
  115. * @constructor
  116. */
  117. static Export = (w: GLTFWriter): GLTFExporterPlugin&{materialExternalResources:any, serializedMeta: any}=> ({
  118. writeMaterial(material: Material & any, matDef: any) {
  119. if (!material?.isMaterial) return
  120. if (!matDef.extensions) matDef.extensions = {}
  121. const dat: any = {}
  122. // non-default stuff from MaterialLoader.js
  123. // hardcode fix for: emissive components are limited to stay within the 0 - 1 range to accommodate glTF spec. see threejs: #21849 and #22000.
  124. // not needed anymore.
  125. // if (material.emissiveIntensity !== undefined && material.emissive?.isColor) {
  126. // const emissive = material.emissive.clone().multiplyScalar(material.emissiveIntensity)
  127. // const maxEmissiveComponent = Math.max(emissive.r, emissive.g, emissive.b)
  128. // if (maxEmissiveComponent > 1) {
  129. // dat.emissiveIntensity = maxEmissiveComponent
  130. // }
  131. // }
  132. // bumpMap, lightMap, alphaMap moved to separate extensions
  133. // if (material.shininess !== undefined) dat.shininess = material.shininess
  134. if (material.fog !== undefined) dat.fog = material.fog
  135. if (material.flatShading !== undefined) dat.flatShading = material.flatShading
  136. if (material.blending !== undefined) dat.blending = material.blending
  137. // if (material.combine !== undefined) dat.combine = material.combine
  138. if (material.side !== undefined && material.side !== DoubleSide) dat.side = material.side // DoubleSide handled in GLTF
  139. if (material.shadowSide !== undefined) dat.shadowSide = material.shadowSide
  140. if (material.depthFunc !== undefined) dat.depthFunc = material.depthFunc
  141. if (material.depthTest !== undefined) dat.depthTest = material.depthTest
  142. if (material.depthWrite !== undefined) dat.depthWrite = material.depthWrite
  143. if (material.colorWrite !== undefined) dat.colorWrite = material.colorWrite
  144. if (material.vertexColors !== undefined) dat.vertexColors = material.vertexColors // this is override, it is also set in GLTFLoader if geometry has vertex colors, todo: check how to do this in a better way
  145. if (material.alphaTest !== undefined) dat.alphaTest = material.alphaTest
  146. if (material.alphaHash !== undefined) dat.alphaHash = material.alphaHash
  147. if (material.envMapIntensity !== undefined) dat.envMapIntensity = material.envMapIntensity // for when separateEnvMapIntensity is true
  148. // if (material.envMapSlotKey !== undefined) dat.envMapSlotKey = material.envMapSlotKey // in userData
  149. if (material.blendSrc !== undefined) dat.blendSrc = material.blendSrc
  150. if (material.blendDst !== undefined) dat.blendDst = material.blendDst
  151. if (material.blendEquation !== undefined) dat.blendEquation = material.blendEquation
  152. if (material.blendSrcAlpha !== undefined) dat.blendSrcAlpha = material.blendSrcAlpha
  153. if (material.blendDstAlpha !== undefined) dat.blendDstAlpha = material.blendDstAlpha
  154. if (material.blendEquationAlpha !== undefined) dat.blendEquationAlpha = material.blendEquationAlpha
  155. if (material.blendColor !== undefined) dat.blendColor = (material.blendColor as Color).getHex()
  156. if (material.blendAlpha !== undefined) dat.blendAlpha = material.blendAlpha
  157. // if (material.stencilWrite !== undefined) dat.stencilWrite = material.stencilWrite
  158. // if (material.stencilWriteMask !== undefined) dat.stencilWriteMask = material.stencilWriteMask
  159. // if (material.stencilFunc !== undefined) dat.stencilFunc = material.stencilFunc
  160. // if (material.stencilRef !== undefined) dat.stencilRef = material.stencilRef
  161. // if (material.stencilFuncMask !== undefined) dat.stencilFuncMask = material.stencilFuncMask
  162. // if (material.stencilFail !== undefined) dat.stencilFail = material.stencilFail
  163. // if (material.stencilZFail !== undefined) dat.stencilZFail = material.stencilZFail
  164. // if (material.stencilZPass !== undefined) dat.stencilZPass = material.stencilZPass
  165. if (material.wireframe !== undefined) dat.wireframe = material.wireframe
  166. if (material.wireframeLinewidth !== undefined) dat.wireframeLinewidth = material.wireframeLinewidth
  167. if (material.wireframeLinecap !== undefined) dat.wireframeLinecap = material.wireframeLinecap
  168. if (material.wireframeLinejoin !== undefined) dat.wireframeLinejoin = material.wireframeLinejoin
  169. if (material.rotation !== undefined) dat.rotation = material.rotation
  170. // if (material.linewidth !== 1) dat.linewidth = material.linewidth
  171. // if (material.dashSize !== undefined) dat.dashSize = material.dashSize
  172. // if (material.gapSize !== undefined) dat.gapSize = material.gapSize
  173. // if (material.scale !== undefined) dat.scale = material.scale
  174. if (material.polygonOffset !== undefined) dat.polygonOffset = material.polygonOffset
  175. if (material.polygonOffsetFactor !== undefined) dat.polygonOffsetFactor = material.polygonOffsetFactor
  176. if (material.polygonOffsetUnits !== undefined) dat.polygonOffsetUnits = material.polygonOffsetUnits
  177. if (material.dithering !== undefined) dat.dithering = material.dithering
  178. if (material.alphaToCoverage !== undefined) dat.alphaToCoverage = material.alphaToCoverage
  179. if (material.premultipliedAlpha !== undefined) dat.premultipliedAlpha = material.premultipliedAlpha
  180. // if (material.visible !== undefined) dat.visible = material.visible
  181. if (material.toneMapped !== undefined) dat.toneMapped = material.toneMapped
  182. // ignoring data from the GLTFExporter.
  183. if (material.normalScale !== undefined) dat.normalScale = [material.normalScale.x, material.normalScale.y]
  184. // if (material.reflectivity !== undefined) dat.reflectivity = material.reflectivity // see KHR_materials_ior, and comments in parser.
  185. // if (material.refractionRatio !== undefined) dat.refractionRatio = material.refractionRatio
  186. // todo: make extension for this like GLTFMaterialsBumpMapExtension
  187. // if ( material.gradientMap !== undefined ) dat.gradientMap = getTexture( json.gradientMap );
  188. const resources = this.materialExternalResources[material.uuid]
  189. if (resources) {
  190. Object.entries(resources).forEach(([k, v]: [string, any|ITexture]) => {
  191. if (k.startsWith('_')) return
  192. let setFlag = false
  193. if (v?.userData && v.userData.embedUrlImagePreviews === undefined) { // check ThreeSerialization texture serialization and GLTFWriter2.processTexture
  194. v.userData.embedUrlImagePreviews = w.options.exporterOptions?.embedUrlImagePreviews
  195. setFlag = true
  196. }
  197. dat[k] = ThreeSerialization.Serialize(v, this.serializedMeta)
  198. if (v?.userData && setFlag) delete v.userData.embedUrlImagePreviews
  199. })
  200. }
  201. if (Object.keys(dat).length > 0) {
  202. matDef.extensions[GLTFMaterialExtrasExtension.WebGiMaterialExtrasExtension] = dat
  203. w.extensionsUsed[GLTFMaterialExtrasExtension.WebGiMaterialExtrasExtension] = true
  204. }
  205. },
  206. materialExternalResources: {},
  207. serializedMeta: {
  208. images: {},
  209. textures: {},
  210. },
  211. beforeParse(input) {
  212. this.materialExternalResources = {}
  213. // externalImagesInExtras: this is required because gltf-transform doesnt support external images in glb
  214. // see https://github.com/donmccurdy/glTF-Transform/discussions/644
  215. if (!w.options.externalImagesInExtras) return
  216. const materials: (Material&any)[] = [];
  217. (Array.isArray(input) ? input : [input]).forEach(obj=>{
  218. obj?.traverse((o: any)=>{
  219. if (o && o.material?.isMaterial) materials.push(o.material)
  220. })
  221. })
  222. materials.forEach(material=>{
  223. if (material) {
  224. if (!this.materialExternalResources[material.uuid])
  225. this.materialExternalResources[material.uuid] = {}
  226. this.materialExternalResources[material.uuid].__materialRef = material
  227. Object.entries(material).forEach(([k, v]: [string, any])=>{
  228. if (k.startsWith('_')) return
  229. if (!v) return
  230. if (!v.isTexture) return
  231. if (
  232. v.userData.rootPath
  233. && (v.userData.rootPath.startsWith('http') || v.userData.rootPath.startsWith('data:'))
  234. ) {
  235. material[k] = null
  236. this.materialExternalResources[material.uuid][k] = v
  237. }
  238. })
  239. }
  240. })
  241. },
  242. afterParse(_) {
  243. const vals = Object.values(this.materialExternalResources)
  244. if (vals.length < 1) return
  245. vals.forEach((resources: any)=>{
  246. const mat = resources.__materialRef
  247. if (!mat) return
  248. Object.entries(resources).forEach(([k, v]: [string, any])=>{
  249. if (k.startsWith('_')) return
  250. if (!v) return
  251. mat[k] = v
  252. })
  253. delete this.materialExternalResources[mat.uuid]
  254. })
  255. const scene = w.json.scenes[w.json.scene || 0]
  256. if (!scene.extensions) scene.extensions = {}
  257. scene.extensions[GLTFMaterialExtrasExtension.WebGiMaterialExtrasExtension] = {
  258. resources: this.serializedMeta,
  259. }
  260. w.extensionsUsed[GLTFMaterialExtrasExtension.WebGiMaterialExtrasExtension] = true
  261. // console.log(w)
  262. },
  263. })
  264. // see GLTFDracoExportPlugin
  265. static Textures: Record<string, string|number>|undefined = undefined
  266. }