babylon.glTFMaterial.ts 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163
  1. /// <reference path="../../../../dist/babylon.glTF2Interface.d.ts"/>
  2. module BABYLON.GLTF2 {
  3. /**
  4. * Interface for storing specular glossiness factors
  5. * @hidden
  6. */
  7. interface _IPBRSpecularGlossiness {
  8. /**
  9. * Represents the linear diffuse factors of the material
  10. */
  11. diffuseColor: BABYLON.Color3;
  12. /**
  13. * Represents the linear specular factors of the material
  14. */
  15. specularColor: BABYLON.Color3;
  16. /**
  17. * Represents the smoothness of the material
  18. */
  19. glossiness: number;
  20. }
  21. /**
  22. * Interface for storing metallic roughness factors
  23. * @hidden
  24. */
  25. interface _IPBRMetallicRoughness {
  26. /**
  27. * Represents the albedo color of the material
  28. */
  29. baseColor: BABYLON.Color3;
  30. /**
  31. * Represents the metallness of the material
  32. */
  33. metallic: number;
  34. /**
  35. * Represents the roughness of the material
  36. */
  37. roughness: number;
  38. /**
  39. * The metallic roughness texture as a base64 string
  40. */
  41. metallicRoughnessTextureBase64?: Nullable<string>;
  42. /**
  43. * The base color texture as a base64 string
  44. */
  45. baseColorTextureBase64?: Nullable<string>;
  46. }
  47. /**
  48. * Utility methods for working with glTF material conversion properties. This class should only be used internally
  49. * @hidden
  50. */
  51. export class _GLTFMaterial {
  52. /**
  53. * Represents the dielectric specular values for R, G and B
  54. */
  55. private static readonly _dielectricSpecular: Color3 = new Color3(0.04, 0.04, 0.04);
  56. /**
  57. * Allows the maximum specular power to be defined for material calculations
  58. */
  59. private static _maxSpecularPower = 1024;
  60. /**
  61. * Numeric tolerance value
  62. */
  63. private static _epsilon = 1e-6;
  64. /**
  65. * Specifies if two colors are approximately equal in value
  66. * @param color1 first color to compare to
  67. * @param color2 second color to compare to
  68. * @param epsilon threshold value
  69. */
  70. private static FuzzyEquals(color1: Color3, color2: Color3, epsilon: number): boolean {
  71. return Scalar.WithinEpsilon(color1.r, color2.r, epsilon) &&
  72. Scalar.WithinEpsilon(color1.g, color2.g, epsilon) &&
  73. Scalar.WithinEpsilon(color1.b, color2.b, epsilon);
  74. }
  75. /**
  76. * Gets the materials from a Babylon scene and converts them to glTF materials
  77. * @param scene babylonjs scene
  78. * @param mimeType texture mime type
  79. * @param images array of images
  80. * @param textures array of textures
  81. * @param materials array of materials
  82. * @param imageData mapping of texture names to base64 textures
  83. * @param hasTextureCoords specifies if texture coordinates are present on the material
  84. */
  85. public static _ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
  86. for (let babylonMaterial of babylonMaterials) {
  87. if (babylonMaterial instanceof StandardMaterial) {
  88. _GLTFMaterial._ConvertStandardMaterial(babylonMaterial, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
  89. }
  90. else if (babylonMaterial instanceof PBRMetallicRoughnessMaterial) {
  91. _GLTFMaterial._ConvertPBRMetallicRoughnessMaterial(babylonMaterial, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
  92. }
  93. else if (babylonMaterial instanceof PBRMaterial) {
  94. _GLTFMaterial._ConvertPBRMaterial(babylonMaterial, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
  95. }
  96. else {
  97. Tools.Error("Unsupported material type: " + babylonMaterial.name);
  98. }
  99. }
  100. }
  101. /**
  102. * Makes a copy of the glTF material without the texture parameters
  103. * @param originalMaterial original glTF material
  104. * @returns glTF material without texture parameters
  105. */
  106. public static _StripTexturesFromMaterial(originalMaterial: IMaterial): IMaterial {
  107. let newMaterial: IMaterial = {};
  108. if (originalMaterial) {
  109. newMaterial.name = originalMaterial.name;
  110. newMaterial.doubleSided = originalMaterial.doubleSided;
  111. newMaterial.alphaMode = originalMaterial.alphaMode;
  112. newMaterial.alphaCutoff = originalMaterial.alphaCutoff;
  113. newMaterial.emissiveFactor = originalMaterial.emissiveFactor;
  114. const originalPBRMetallicRoughness = originalMaterial.pbrMetallicRoughness;
  115. if (originalPBRMetallicRoughness) {
  116. newMaterial.pbrMetallicRoughness = {};
  117. newMaterial.pbrMetallicRoughness.baseColorFactor = originalPBRMetallicRoughness.baseColorFactor;
  118. newMaterial.pbrMetallicRoughness.metallicFactor = originalPBRMetallicRoughness.metallicFactor;
  119. newMaterial.pbrMetallicRoughness.roughnessFactor = originalPBRMetallicRoughness.roughnessFactor;
  120. }
  121. }
  122. return newMaterial;
  123. }
  124. /**
  125. * Specifies if the material has any texture parameters present
  126. * @param material glTF Material
  127. * @returns boolean specifying if texture parameters are present
  128. */
  129. public static _HasTexturesPresent(material: IMaterial): boolean {
  130. if (material.emissiveTexture || material.normalTexture || material.occlusionTexture) {
  131. return true;
  132. }
  133. const pbrMat = material.pbrMetallicRoughness;
  134. if (pbrMat) {
  135. if (pbrMat.baseColorTexture || pbrMat.metallicRoughnessTexture) {
  136. return true;
  137. }
  138. }
  139. return false;
  140. }
  141. /**
  142. * Converts a Babylon StandardMaterial to a glTF Metallic Roughness Material
  143. * @param babylonStandardMaterial
  144. * @returns glTF Metallic Roughness Material representation
  145. */
  146. public static _ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial: StandardMaterial): IMaterialPbrMetallicRoughness {
  147. const P0 = new BABYLON.Vector2(0, 1);
  148. const P1 = new BABYLON.Vector2(0, 0.1);
  149. const P2 = new BABYLON.Vector2(0, 0.1);
  150. const P3 = new BABYLON.Vector2(1300, 0.1);
  151. /**
  152. * Given the control points, solve for x based on a given t for a cubic bezier curve
  153. * @param t a value between 0 and 1
  154. * @param p0 first control point
  155. * @param p1 second control point
  156. * @param p2 third control point
  157. * @param p3 fourth control point
  158. * @returns number result of cubic bezier curve at the specified t
  159. */
  160. function _cubicBezierCurve(t: number, p0: number, p1: number, p2: number, p3: number): number {
  161. return (
  162. (1 - t) * (1 - t) * (1 - t) * p0 +
  163. 3 * (1 - t) * (1 - t) * t * p1 +
  164. 3 * (1 - t) * t * t * p2 +
  165. t * t * t * p3
  166. );
  167. }
  168. /**
  169. * Evaluates a specified specular power value to determine the appropriate roughness value,
  170. * based on a pre-defined cubic bezier curve with specular on the abscissa axis (x-axis)
  171. * and roughness on the ordinant axis (y-axis)
  172. * @param specularPower specular power of standard material
  173. * @returns Number representing the roughness value
  174. */
  175. function _solveForRoughness(specularPower: number): number {
  176. var t = Math.pow(specularPower / P3.x, 0.333333);
  177. return _cubicBezierCurve(t, P0.y, P1.y, P2.y, P3.y);
  178. }
  179. let diffuse = babylonStandardMaterial.diffuseColor.toLinearSpace().scale(0.5);
  180. let opacity = babylonStandardMaterial.alpha;
  181. let specularPower = Scalar.Clamp(babylonStandardMaterial.specularPower, 0, this._maxSpecularPower);
  182. const roughness = _solveForRoughness(specularPower);
  183. const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {
  184. baseColorFactor: [
  185. diffuse.r,
  186. diffuse.g,
  187. diffuse.b,
  188. opacity
  189. ],
  190. metallicFactor: 0,
  191. roughnessFactor: roughness,
  192. };
  193. return glTFPbrMetallicRoughness;
  194. }
  195. /**
  196. * Computes the metallic factor
  197. * @param diffuse diffused value
  198. * @param specular specular value
  199. * @param oneMinusSpecularStrength one minus the specular strength
  200. * @returns metallic value
  201. */
  202. public static _SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number {
  203. if (specular < _GLTFMaterial._dielectricSpecular.r) {
  204. _GLTFMaterial._dielectricSpecular
  205. return 0;
  206. }
  207. const a = _GLTFMaterial._dielectricSpecular.r;
  208. const b = diffuse * oneMinusSpecularStrength / (1.0 - _GLTFMaterial._dielectricSpecular.r) + specular - 2.0 * _GLTFMaterial._dielectricSpecular.r;
  209. const c = _GLTFMaterial._dielectricSpecular.r - specular;
  210. const D = b * b - 4.0 * a * c;
  211. return BABYLON.Scalar.Clamp((-b + Math.sqrt(D)) / (2.0 * a), 0, 1);
  212. }
  213. /**
  214. * Gets the glTF alpha mode from the Babylon Material
  215. * @param babylonMaterial Babylon Material
  216. * @returns The Babylon alpha mode value
  217. */
  218. public static _GetAlphaMode(babylonMaterial: Material): Nullable<MaterialAlphaMode> {
  219. if (babylonMaterial instanceof StandardMaterial) {
  220. const babylonStandardMaterial = babylonMaterial as StandardMaterial;
  221. if ((babylonStandardMaterial.alpha != 1.0) ||
  222. (babylonStandardMaterial.diffuseTexture != null && babylonStandardMaterial.diffuseTexture.hasAlpha) ||
  223. (babylonStandardMaterial.opacityTexture != null)) {
  224. return MaterialAlphaMode.BLEND;
  225. }
  226. else {
  227. return MaterialAlphaMode.OPAQUE;
  228. }
  229. }
  230. else if (babylonMaterial instanceof PBRMetallicRoughnessMaterial) {
  231. const babylonPBRMetallicRoughness = babylonMaterial as PBRMetallicRoughnessMaterial;
  232. switch (babylonPBRMetallicRoughness.transparencyMode) {
  233. case PBRMaterial.PBRMATERIAL_OPAQUE: {
  234. return MaterialAlphaMode.OPAQUE;
  235. }
  236. case PBRMaterial.PBRMATERIAL_ALPHABLEND: {
  237. return MaterialAlphaMode.BLEND;
  238. }
  239. case PBRMaterial.PBRMATERIAL_ALPHATEST: {
  240. return MaterialAlphaMode.MASK;
  241. }
  242. case PBRMaterial.PBRMATERIAL_ALPHATESTANDBLEND: {
  243. Tools.Warn(babylonMaterial.name + ": GLTF Exporter | Alpha test and blend mode not supported in glTF. Alpha blend used instead.");
  244. return MaterialAlphaMode.BLEND;
  245. }
  246. default: {
  247. Tools.Error("Unsupported alpha mode " + babylonPBRMetallicRoughness.transparencyMode);
  248. return null;
  249. }
  250. }
  251. }
  252. else if (babylonMaterial instanceof PBRMaterial) {
  253. const babylonPBRMaterial = babylonMaterial as PBRMaterial;
  254. switch (babylonPBRMaterial.transparencyMode) {
  255. case PBRMaterial.PBRMATERIAL_OPAQUE: {
  256. return MaterialAlphaMode.OPAQUE;
  257. }
  258. case PBRMaterial.PBRMATERIAL_ALPHABLEND: {
  259. return MaterialAlphaMode.BLEND;
  260. }
  261. case PBRMaterial.PBRMATERIAL_ALPHATEST: {
  262. return MaterialAlphaMode.MASK;
  263. }
  264. case PBRMaterial.PBRMATERIAL_ALPHATESTANDBLEND: {
  265. Tools.Warn(babylonMaterial.name + ": GLTF Exporter | Alpha test and blend mode not supported in glTF. Alpha blend used instead.");
  266. return MaterialAlphaMode.BLEND;
  267. }
  268. default: {
  269. Tools.Error("Unsupported alpha mode " + babylonPBRMaterial.transparencyMode);
  270. return null;
  271. }
  272. }
  273. }
  274. else {
  275. Tools.Error("Unsupported Babylon material type");
  276. return null;
  277. }
  278. }
  279. /**
  280. * Converts a Babylon Standard Material to a glTF Material
  281. * @param babylonStandardMaterial BJS Standard Material
  282. * @param mimeType mime type to use for the textures
  283. * @param images array of glTF image interfaces
  284. * @param textures array of glTF texture interfaces
  285. * @param materials array of glTF material interfaces
  286. * @param imageData map of image file name to data
  287. * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
  288. */
  289. public static _ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
  290. const glTFPbrMetallicRoughness = _GLTFMaterial._ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
  291. const glTFMaterial: IMaterial = { name: babylonStandardMaterial.name };
  292. if (babylonStandardMaterial.backFaceCulling != null && !babylonStandardMaterial.backFaceCulling) {
  293. if (!babylonStandardMaterial.twoSidedLighting) {
  294. Tools.Warn(babylonStandardMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
  295. }
  296. glTFMaterial.doubleSided = true;
  297. }
  298. if (hasTextureCoords) {
  299. if (babylonStandardMaterial.diffuseTexture) {
  300. const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.diffuseTexture, mimeType, images, textures, samplers, imageData);
  301. if (glTFTexture != null) {
  302. glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
  303. }
  304. }
  305. if (babylonStandardMaterial.bumpTexture) {
  306. const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.bumpTexture, mimeType, images, textures, samplers, imageData)
  307. if (glTFTexture) {
  308. glTFMaterial.normalTexture = glTFTexture;
  309. if (babylonStandardMaterial.bumpTexture.level !== 1) {
  310. glTFMaterial.normalTexture.scale = babylonStandardMaterial.bumpTexture.level;
  311. }
  312. }
  313. }
  314. if (babylonStandardMaterial.emissiveTexture) {
  315. const glTFEmissiveTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.emissiveTexture, mimeType, images, textures, samplers, imageData)
  316. if (glTFEmissiveTexture) {
  317. glTFMaterial.emissiveTexture = glTFEmissiveTexture;
  318. }
  319. glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0];
  320. }
  321. if (babylonStandardMaterial.ambientTexture) {
  322. const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.ambientTexture, mimeType, images, textures, samplers, imageData);
  323. if (glTFTexture) {
  324. const occlusionTexture: IMaterialOcclusionTextureInfo = {
  325. index: glTFTexture.index
  326. };
  327. glTFMaterial.occlusionTexture = occlusionTexture;
  328. occlusionTexture.strength = 1.0;
  329. }
  330. }
  331. }
  332. if (babylonStandardMaterial.alpha < 1.0 || babylonStandardMaterial.opacityTexture) {
  333. if (babylonStandardMaterial.alphaMode === Engine.ALPHA_COMBINE) {
  334. glTFMaterial.alphaMode = GLTF2.MaterialAlphaMode.BLEND;
  335. }
  336. else {
  337. Tools.Warn(babylonStandardMaterial.name + ": glTF 2.0 does not support alpha mode: " + babylonStandardMaterial.alphaMode.toString());
  338. }
  339. }
  340. if (babylonStandardMaterial.emissiveColor && !this.FuzzyEquals(babylonStandardMaterial.emissiveColor, Color3.Black(), this._epsilon)) {
  341. glTFMaterial.emissiveFactor = babylonStandardMaterial.emissiveColor.asArray();
  342. }
  343. glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
  344. materials.push(glTFMaterial);
  345. }
  346. /**
  347. * Converts a Babylon PBR Metallic Roughness Material to a glTF Material
  348. * @param babylonPBRMetalRoughMaterial BJS PBR Metallic Roughness Material
  349. * @param mimeType mime type to use for the textures
  350. * @param images array of glTF image interfaces
  351. * @param textures array of glTF texture interfaces
  352. * @param materials array of glTF material interfaces
  353. * @param imageData map of image file name to data
  354. * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
  355. */
  356. public static _ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
  357. const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
  358. if (babylonPBRMetalRoughMaterial.baseColor) {
  359. glTFPbrMetallicRoughness.baseColorFactor = [
  360. babylonPBRMetalRoughMaterial.baseColor.r,
  361. babylonPBRMetalRoughMaterial.baseColor.g,
  362. babylonPBRMetalRoughMaterial.baseColor.b,
  363. babylonPBRMetalRoughMaterial.alpha
  364. ];
  365. }
  366. if (babylonPBRMetalRoughMaterial.metallic != null && babylonPBRMetalRoughMaterial.metallic !== 1) {
  367. glTFPbrMetallicRoughness.metallicFactor = babylonPBRMetalRoughMaterial.metallic;
  368. }
  369. if (babylonPBRMetalRoughMaterial.roughness != null && babylonPBRMetalRoughMaterial.roughness !== 1) {
  370. glTFPbrMetallicRoughness.roughnessFactor = babylonPBRMetalRoughMaterial.roughness;
  371. }
  372. const glTFMaterial: IMaterial = {
  373. name: babylonPBRMetalRoughMaterial.name
  374. };
  375. if (babylonPBRMetalRoughMaterial.doubleSided) {
  376. glTFMaterial.doubleSided = babylonPBRMetalRoughMaterial.doubleSided;
  377. }
  378. if (hasTextureCoords) {
  379. if (babylonPBRMetalRoughMaterial.baseTexture != null) {
  380. const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.baseTexture, mimeType, images, textures, samplers, imageData);
  381. if (glTFTexture != null) {
  382. glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
  383. }
  384. }
  385. if (babylonPBRMetalRoughMaterial.normalTexture) {
  386. const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.normalTexture, mimeType, images, textures, samplers, imageData);
  387. if (glTFTexture) {
  388. glTFMaterial.normalTexture = glTFTexture;
  389. if (babylonPBRMetalRoughMaterial.normalTexture.level !== 1) {
  390. glTFMaterial.normalTexture.scale = babylonPBRMetalRoughMaterial.normalTexture.level;
  391. }
  392. }
  393. }
  394. if (babylonPBRMetalRoughMaterial.occlusionTexture) {
  395. const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.occlusionTexture, mimeType, images, textures, samplers, imageData);
  396. if (glTFTexture) {
  397. glTFMaterial.occlusionTexture = glTFTexture;
  398. if (babylonPBRMetalRoughMaterial.occlusionStrength != null) {
  399. glTFMaterial.occlusionTexture.strength = babylonPBRMetalRoughMaterial.occlusionStrength;
  400. }
  401. }
  402. }
  403. if (babylonPBRMetalRoughMaterial.emissiveTexture) {
  404. const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.emissiveTexture, mimeType, images, textures, samplers, imageData);
  405. if (glTFTexture != null) {
  406. glTFMaterial.emissiveTexture = glTFTexture;
  407. }
  408. }
  409. }
  410. if (this.FuzzyEquals(babylonPBRMetalRoughMaterial.emissiveColor, Color3.Black(), this._epsilon)) {
  411. glTFMaterial.emissiveFactor = babylonPBRMetalRoughMaterial.emissiveColor.asArray();
  412. }
  413. if (babylonPBRMetalRoughMaterial.transparencyMode != null) {
  414. const alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMetalRoughMaterial);
  415. if (alphaMode) {
  416. if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
  417. glTFMaterial.alphaMode = alphaMode;
  418. if (alphaMode === MaterialAlphaMode.MASK) {
  419. glTFMaterial.alphaCutoff = babylonPBRMetalRoughMaterial.alphaCutOff;
  420. }
  421. }
  422. }
  423. }
  424. glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
  425. materials.push(glTFMaterial);
  426. }
  427. /**
  428. * Converts an image typed array buffer to a base64 image
  429. * @param buffer typed array buffer
  430. * @param width width of the image
  431. * @param height height of the image
  432. * @param mimeType mimetype of the image
  433. * @returns base64 image string
  434. */
  435. private static _CreateBase64FromCanvas(buffer: Uint8ClampedArray | Float32Array, width: number, height: number, mimeType: ImageMimeType): string {
  436. const imageCanvas = document.createElement('canvas');
  437. imageCanvas.width = width;
  438. imageCanvas.height = height;
  439. imageCanvas.id = "WriteCanvas";
  440. const ctx = imageCanvas.getContext('2d') as CanvasRenderingContext2D;
  441. const imgData = ctx.createImageData(width, height);
  442. imgData.data.set(buffer);
  443. ctx.putImageData(imgData, 0, 0);
  444. return imageCanvas.toDataURL(mimeType);
  445. }
  446. /**
  447. * Generates a white texture based on the specified width and height
  448. * @param width width of the texture in pixels
  449. * @param height height of the texture in pixels
  450. * @param scene babylonjs scene
  451. * @returns white texture
  452. */
  453. private static _CreateWhiteTexture(width: number, height: number, scene: Scene): Texture {
  454. const data = new Uint8Array(width * height * 4);
  455. for (let i = 0; i < data.length; i = i + 4) {
  456. data[i] = data[i + 1] = data[i + 2] = data[i + 3] = 0xFF;
  457. }
  458. const rawTexture = RawTexture.CreateRGBATexture(data, width, height, scene);
  459. return rawTexture;
  460. }
  461. /**
  462. * Resizes the two source textures to the same dimensions. If a texture is null, a default white texture is generated. If both textures are null, returns null
  463. * @param texture1 first texture to resize
  464. * @param texture2 second texture to resize
  465. * @param scene babylonjs scene
  466. * @returns resized textures or null
  467. */
  468. private static _ResizeTexturesToSameDimensions(texture1: BaseTexture, texture2: BaseTexture, scene: Scene): { "texture1": BaseTexture, "texture2": BaseTexture } {
  469. let texture1Size = texture1 ? texture1.getSize() : { width: 0, height: 0 };
  470. let texture2Size = texture2 ? texture2.getSize() : { width: 0, height: 0 };
  471. let resizedTexture1;
  472. let resizedTexture2;
  473. if (texture1Size.width < texture2Size.width) {
  474. if (texture1) {
  475. resizedTexture1 = TextureTools.CreateResizedCopy(texture1 as Texture, texture2Size.width, texture2Size.height, true);
  476. }
  477. else {
  478. resizedTexture1 = this._CreateWhiteTexture(texture2Size.width, texture2Size.height, scene);
  479. }
  480. resizedTexture2 = texture2;
  481. }
  482. else if (texture1Size.width > texture2Size.width) {
  483. if (texture2) {
  484. resizedTexture2 = TextureTools.CreateResizedCopy(texture2 as Texture, texture1Size.width, texture1Size.height, true);
  485. }
  486. else {
  487. resizedTexture2 = this._CreateWhiteTexture(texture1Size.width, texture1Size.height, scene);
  488. }
  489. resizedTexture1 = texture1;
  490. }
  491. else {
  492. resizedTexture1 = texture1;
  493. resizedTexture2 = texture2;
  494. }
  495. return {
  496. "texture1": resizedTexture1,
  497. "texture2": resizedTexture2
  498. }
  499. }
  500. /**
  501. * Convert Specular Glossiness Textures to Metallic Roughness
  502. * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness
  503. * @link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js
  504. * @param diffuseTexture texture used to store diffuse information
  505. * @param specularGlossinessTexture texture used to store specular and glossiness information
  506. * @param factors specular glossiness material factors
  507. * @param mimeType the mime type to use for the texture
  508. * @returns pbr metallic roughness interface or null
  509. */
  510. private static _ConvertSpecularGlossinessTexturesToMetallicRoughness(diffuseTexture: BaseTexture, specularGlossinessTexture: BaseTexture, factors: _IPBRSpecularGlossiness, mimeType: ImageMimeType): Nullable<_IPBRMetallicRoughness> {
  511. if (!(diffuseTexture || specularGlossinessTexture)) {
  512. return null;
  513. }
  514. const scene = diffuseTexture ? diffuseTexture.getScene() : specularGlossinessTexture.getScene();
  515. if (!scene) {
  516. Tools.Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Scene from textures is missing!");
  517. return null;
  518. }
  519. const resizedTextures = this._ResizeTexturesToSameDimensions(diffuseTexture, specularGlossinessTexture, scene);
  520. let diffuseSize = resizedTextures.texture1.getSize();
  521. let diffuseBuffer: Uint8Array;
  522. let specularGlossinessBuffer: Uint8Array;
  523. const width = diffuseSize.width;
  524. const height = diffuseSize.height;
  525. let pixels = (resizedTextures.texture1.readPixels());
  526. if (pixels instanceof Uint8Array) {
  527. diffuseBuffer = (resizedTextures.texture1.readPixels()) as Uint8Array;
  528. }
  529. else {
  530. Tools.Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture1.name);
  531. return null;
  532. }
  533. pixels = resizedTextures.texture2.readPixels();
  534. if (pixels instanceof Uint8Array) {
  535. specularGlossinessBuffer = (resizedTextures.texture2.readPixels()) as Uint8Array;
  536. }
  537. else {
  538. Tools.Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture2.name);
  539. return null;
  540. }
  541. const byteLength = specularGlossinessBuffer.byteLength;
  542. const metallicRoughnessBuffer = new Uint8Array(byteLength);
  543. const baseColorBuffer = new Uint8Array(byteLength);
  544. const strideSize = 4;
  545. const maxBaseColor = Color3.Black();
  546. let maxMetallic = 0;
  547. let maxRoughness = 0;
  548. for (let h = 0; h < height; ++h) {
  549. for (let w = 0; w < width; ++w) {
  550. const offset = (width * h + w) * strideSize;
  551. const diffuseColor = Color3.FromInts(diffuseBuffer[offset], diffuseBuffer[offset + 1], diffuseBuffer[offset + 2]).toLinearSpace().multiply(factors.diffuseColor);
  552. const specularColor = Color3.FromInts(specularGlossinessBuffer[offset], specularGlossinessBuffer[offset + 1], specularGlossinessBuffer[offset + 2]).toLinearSpace().multiply(factors.specularColor);
  553. const glossiness = (specularGlossinessBuffer[offset + 3] / 255) * factors.glossiness;
  554. const specularGlossiness: _IPBRSpecularGlossiness = {
  555. diffuseColor: diffuseColor,
  556. specularColor: specularColor,
  557. glossiness: glossiness
  558. };
  559. const metallicRoughness = this._ConvertSpecularGlossinessToMetallicRoughness(specularGlossiness);
  560. maxBaseColor.r = Math.max(maxBaseColor.r, metallicRoughness.baseColor.r);
  561. maxBaseColor.g = Math.max(maxBaseColor.g, metallicRoughness.baseColor.g);
  562. maxBaseColor.b = Math.max(maxBaseColor.b, metallicRoughness.baseColor.b);
  563. maxMetallic = Math.max(maxMetallic, metallicRoughness.metallic);
  564. maxRoughness = Math.max(maxRoughness, metallicRoughness.roughness);
  565. baseColorBuffer[offset] = metallicRoughness.baseColor.r * 255;
  566. baseColorBuffer[offset + 1] = metallicRoughness.baseColor.g * 255;
  567. baseColorBuffer[offset + 2] = metallicRoughness.baseColor.b * 255;
  568. baseColorBuffer[offset + 3] = resizedTextures.texture1.hasAlpha ? diffuseBuffer[offset + 3] : 255;
  569. metallicRoughnessBuffer[offset] = 0;
  570. metallicRoughnessBuffer[offset + 1] = metallicRoughness.roughness * 255;
  571. metallicRoughnessBuffer[offset + 2] = metallicRoughness.metallic * 255;
  572. metallicRoughnessBuffer[offset + 3] = 255;
  573. }
  574. }
  575. // Retrieves the metallic roughness factors from the maximum texture values.
  576. const metallicRoughnessFactors: _IPBRMetallicRoughness = {
  577. baseColor: maxBaseColor,
  578. metallic: maxMetallic,
  579. roughness: maxRoughness
  580. };
  581. let writeOutMetallicRoughnessTexture = false;
  582. let writeOutBaseColorTexture = false;
  583. for (let h = 0; h < height; ++h) {
  584. for (let w = 0; w < width; ++w) {
  585. const destinationOffset = (width * h + w) * strideSize;
  586. baseColorBuffer[destinationOffset] /= metallicRoughnessFactors.baseColor.r > this._epsilon ? metallicRoughnessFactors.baseColor.r : 1;
  587. baseColorBuffer[destinationOffset + 1] /= metallicRoughnessFactors.baseColor.g > this._epsilon ? metallicRoughnessFactors.baseColor.g : 1;
  588. baseColorBuffer[destinationOffset + 2] /= metallicRoughnessFactors.baseColor.b > this._epsilon ? metallicRoughnessFactors.baseColor.b : 1;
  589. const linearBaseColorPixel = Color3.FromInts(baseColorBuffer[destinationOffset], baseColorBuffer[destinationOffset + 1], baseColorBuffer[destinationOffset + 2]);
  590. const sRGBBaseColorPixel = linearBaseColorPixel.toGammaSpace();
  591. baseColorBuffer[destinationOffset] = sRGBBaseColorPixel.r * 255;
  592. baseColorBuffer[destinationOffset + 1] = sRGBBaseColorPixel.g * 255;
  593. baseColorBuffer[destinationOffset + 2] = sRGBBaseColorPixel.b * 255;
  594. if (!this.FuzzyEquals(sRGBBaseColorPixel, Color3.White(), this._epsilon)) {
  595. writeOutBaseColorTexture = true;
  596. }
  597. metallicRoughnessBuffer[destinationOffset + 1] /= metallicRoughnessFactors.roughness > this._epsilon ? metallicRoughnessFactors.roughness : 1;
  598. metallicRoughnessBuffer[destinationOffset + 2] /= metallicRoughnessFactors.metallic > this._epsilon ? metallicRoughnessFactors.metallic : 1;
  599. const metallicRoughnessPixel = Color3.FromInts(255, metallicRoughnessBuffer[destinationOffset + 1], metallicRoughnessBuffer[destinationOffset + 2]);
  600. if (!this.FuzzyEquals(metallicRoughnessPixel, Color3.White(), this._epsilon)) {
  601. writeOutMetallicRoughnessTexture = true;
  602. }
  603. }
  604. }
  605. if (writeOutMetallicRoughnessTexture) {
  606. const metallicRoughnessBase64 = this._CreateBase64FromCanvas(metallicRoughnessBuffer, width, height, mimeType);
  607. metallicRoughnessFactors.metallicRoughnessTextureBase64 = metallicRoughnessBase64;
  608. }
  609. if (writeOutBaseColorTexture) {
  610. const baseColorBase64 = this._CreateBase64FromCanvas(baseColorBuffer, width, height, mimeType);
  611. metallicRoughnessFactors.baseColorTextureBase64 = baseColorBase64;
  612. }
  613. return metallicRoughnessFactors;
  614. }
  615. /**
  616. * Converts specular glossiness material properties to metallic roughness
  617. * @param specularGlossiness interface with specular glossiness material properties
  618. * @returns interface with metallic roughness material properties
  619. */
  620. private static _ConvertSpecularGlossinessToMetallicRoughness(specularGlossiness: _IPBRSpecularGlossiness): _IPBRMetallicRoughness {
  621. const diffusePerceivedBrightness = _GLTFMaterial._GetPerceivedBrightness(specularGlossiness.diffuseColor);
  622. const specularPerceivedBrightness = _GLTFMaterial._GetPerceivedBrightness(specularGlossiness.specularColor);
  623. const oneMinusSpecularStrength = 1 - _GLTFMaterial._GetMaxComponent(specularGlossiness.specularColor);
  624. const metallic = _GLTFMaterial._SolveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength);
  625. const baseColorFromDiffuse = specularGlossiness.diffuseColor.scale(oneMinusSpecularStrength / (1.0 - this._dielectricSpecular.r) / Math.max(1 - metallic, this._epsilon));
  626. const baseColorFromSpecular = specularGlossiness.specularColor.subtract(this._dielectricSpecular.scale(1 - metallic)).scale(1 / Math.max(metallic, this._epsilon));
  627. let baseColor = Color3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic);
  628. baseColor = baseColor.clampToRef(0, 1, baseColor);
  629. const metallicRoughness: _IPBRMetallicRoughness = {
  630. baseColor: baseColor,
  631. metallic: metallic,
  632. roughness: 1 - specularGlossiness.glossiness
  633. }
  634. return metallicRoughness;
  635. }
  636. /**
  637. * Calculates the surface reflectance, independent of lighting conditions
  638. * @param color Color source to calculate brightness from
  639. * @returns number representing the perceived brightness, or zero if color is undefined
  640. */
  641. private static _GetPerceivedBrightness(color: Color3): number {
  642. if (color) {
  643. return Math.sqrt(0.299 * color.r * color.r + 0.587 * color.g * color.g + 0.114 * color.b * color.b);
  644. }
  645. return 0;
  646. }
  647. /**
  648. * Returns the maximum color component value
  649. * @param color
  650. * @returns maximum color component value, or zero if color is null or undefined
  651. */
  652. private static _GetMaxComponent(color: Color3): number {
  653. if (color) {
  654. return Math.max(color.r, Math.max(color.g, color.b));
  655. }
  656. return 0;
  657. }
  658. /**
  659. * Convert a PBRMaterial (Metallic/Roughness) to Metallic Roughness factors
  660. * @param babylonPBRMaterial BJS PBR Metallic Roughness Material
  661. * @param mimeType mime type to use for the textures
  662. * @param images array of glTF image interfaces
  663. * @param textures array of glTF texture interfaces
  664. * @param glTFPbrMetallicRoughness glTF PBR Metallic Roughness interface
  665. * @param imageData map of image file name to data
  666. * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
  667. * @returns glTF PBR Metallic Roughness factors
  668. */
  669. private static _ConvertMetalRoughFactorsToMetallicRoughness(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean): _IPBRMetallicRoughness {
  670. const metallicRoughness = {
  671. baseColor: babylonPBRMaterial.albedoColor,
  672. metallic: babylonPBRMaterial.metallic,
  673. roughness: babylonPBRMaterial.roughness
  674. };
  675. if (hasTextureCoords) {
  676. if (babylonPBRMaterial.albedoTexture) {
  677. const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.albedoTexture, mimeType, images, textures, samplers, imageData);
  678. if (glTFTexture) {
  679. glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
  680. }
  681. }
  682. if (babylonPBRMaterial.metallicTexture) {
  683. const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.metallicTexture, mimeType, images, textures, samplers, imageData);
  684. if (glTFTexture != null) {
  685. glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFTexture;
  686. }
  687. }
  688. }
  689. return metallicRoughness;
  690. }
  691. private static _GetGLTFTextureSampler(texture: BaseTexture): ISampler {
  692. const sampler = _GLTFMaterial._GetGLTFTextureWrapModesSampler(texture);
  693. let samplingMode = texture instanceof Texture ? (texture as Texture).samplingMode : null;
  694. if (samplingMode != null) {
  695. switch (samplingMode) {
  696. case Texture.LINEAR_LINEAR: {
  697. sampler.magFilter = TextureMagFilter.LINEAR;
  698. sampler.minFilter = TextureMinFilter.LINEAR;
  699. break;
  700. }
  701. case Texture.LINEAR_NEAREST: {
  702. sampler.magFilter = TextureMagFilter.LINEAR;
  703. sampler.minFilter = TextureMinFilter.NEAREST;
  704. break;
  705. }
  706. case Texture.NEAREST_LINEAR: {
  707. sampler.magFilter = TextureMagFilter.NEAREST;
  708. sampler.minFilter = TextureMinFilter.LINEAR;
  709. break;
  710. }
  711. case Texture.NEAREST_LINEAR_MIPLINEAR: {
  712. sampler.magFilter = TextureMagFilter.NEAREST;
  713. sampler.minFilter = TextureMinFilter.LINEAR_MIPMAP_LINEAR;
  714. break;
  715. }
  716. case Texture.NEAREST_NEAREST: {
  717. sampler.magFilter = TextureMagFilter.NEAREST;
  718. sampler.minFilter = TextureMinFilter.NEAREST;
  719. break;
  720. }
  721. case Texture.NEAREST_LINEAR_MIPNEAREST: {
  722. sampler.magFilter = TextureMagFilter.NEAREST;
  723. sampler.minFilter = TextureMinFilter.LINEAR_MIPMAP_NEAREST;
  724. break;
  725. }
  726. case Texture.LINEAR_NEAREST_MIPNEAREST: {
  727. sampler.magFilter = TextureMagFilter.LINEAR;
  728. sampler.minFilter = TextureMinFilter.NEAREST_MIPMAP_NEAREST;
  729. break;
  730. }
  731. case Texture.LINEAR_NEAREST_MIPLINEAR: {
  732. sampler.magFilter = TextureMagFilter.LINEAR;
  733. sampler.minFilter = TextureMinFilter.NEAREST_MIPMAP_LINEAR;
  734. break;
  735. }
  736. case Texture.NEAREST_NEAREST_MIPLINEAR: {
  737. sampler.magFilter = TextureMagFilter.NEAREST;
  738. sampler.minFilter = TextureMinFilter.NEAREST_MIPMAP_LINEAR;
  739. break;
  740. }
  741. case Texture.LINEAR_LINEAR_MIPLINEAR: {
  742. sampler.magFilter = TextureMagFilter.LINEAR;
  743. sampler.minFilter = TextureMinFilter.LINEAR_MIPMAP_LINEAR;
  744. break;
  745. }
  746. case Texture.LINEAR_LINEAR_MIPNEAREST: {
  747. sampler.magFilter = TextureMagFilter.LINEAR;
  748. sampler.minFilter = TextureMinFilter.LINEAR_MIPMAP_NEAREST;
  749. break;
  750. }
  751. case Texture.NEAREST_NEAREST_MIPNEAREST: {
  752. sampler.magFilter = TextureMagFilter.NEAREST;
  753. sampler.minFilter = TextureMinFilter.NEAREST_MIPMAP_NEAREST;
  754. break;
  755. }
  756. }
  757. }
  758. return sampler;
  759. }
  760. private static _GetGLTFTextureWrapMode(wrapMode: number): TextureWrapMode {
  761. switch (wrapMode) {
  762. case Texture.WRAP_ADDRESSMODE: {
  763. return TextureWrapMode.REPEAT;
  764. }
  765. case Texture.CLAMP_ADDRESSMODE: {
  766. return TextureWrapMode.CLAMP_TO_EDGE;
  767. }
  768. case Texture.MIRROR_ADDRESSMODE: {
  769. return TextureWrapMode.MIRRORED_REPEAT;
  770. }
  771. default: {
  772. Tools.Error(`Unsupported Texture Wrap Mode ${wrapMode}!`);
  773. return TextureWrapMode.REPEAT;
  774. }
  775. }
  776. }
  777. private static _GetGLTFTextureWrapModesSampler(texture: BaseTexture): ISampler {
  778. let wrapS = _GLTFMaterial._GetGLTFTextureWrapMode(texture instanceof Texture ? (texture as Texture).wrapU : Texture.WRAP_ADDRESSMODE);
  779. let wrapT = _GLTFMaterial._GetGLTFTextureWrapMode(texture instanceof Texture ? (texture as Texture).wrapV : Texture.WRAP_ADDRESSMODE);
  780. if (wrapS === TextureWrapMode.REPEAT && wrapT === TextureWrapMode.REPEAT) { // default wrapping mode in glTF, so omitting
  781. return {};
  782. }
  783. return { wrapS: wrapS, wrapT: wrapT };
  784. }
  785. /**
  786. * Convert a PBRMaterial (Specular/Glossiness) to Metallic Roughness factors
  787. * @param babylonPBRMaterial BJS PBR Metallic Roughness Material
  788. * @param mimeType mime type to use for the textures
  789. * @param images array of glTF image interfaces
  790. * @param textures array of glTF texture interfaces
  791. * @param glTFPbrMetallicRoughness glTF PBR Metallic Roughness interface
  792. * @param imageData map of image file name to data
  793. * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
  794. * @returns glTF PBR Metallic Roughness factors
  795. */
  796. private static _ConvertSpecGlossFactorsToMetallicRoughness(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean): Nullable<_IPBRMetallicRoughness> {
  797. const specGloss: _IPBRSpecularGlossiness = {
  798. diffuseColor: babylonPBRMaterial.albedoColor || Color3.White(),
  799. specularColor: babylonPBRMaterial.reflectivityColor || Color3.White(),
  800. glossiness: babylonPBRMaterial.microSurface || 1,
  801. };
  802. let samplerIndex: Nullable<number> = null;
  803. const sampler = this._GetGLTFTextureSampler(babylonPBRMaterial.albedoTexture);
  804. if (sampler.magFilter != null && sampler.minFilter != null && sampler.wrapS != null && sampler.wrapT != null) {
  805. samplers.push(sampler);
  806. samplerIndex = samplers.length - 1;
  807. }
  808. if (babylonPBRMaterial.reflectivityTexture && !babylonPBRMaterial.useMicroSurfaceFromReflectivityMapAlpha) {
  809. Tools.Error("_ConvertPBRMaterial: Glossiness values not included in the reflectivity texture currently not supported");
  810. return null;
  811. }
  812. let metallicRoughnessFactors = this._ConvertSpecularGlossinessTexturesToMetallicRoughness(babylonPBRMaterial.albedoTexture, babylonPBRMaterial.reflectivityTexture, specGloss, mimeType);
  813. if (!metallicRoughnessFactors) {
  814. metallicRoughnessFactors = this._ConvertSpecularGlossinessToMetallicRoughness(specGloss);
  815. }
  816. else {
  817. if (hasTextureCoords) {
  818. if (metallicRoughnessFactors.baseColorTextureBase64) {
  819. const glTFBaseColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughnessFactors.baseColorTextureBase64, "bjsBaseColorTexture_" + (textures.length) + ".png", mimeType, images, textures, babylonPBRMaterial.albedoTexture.coordinatesIndex, samplerIndex, imageData);
  820. if (glTFBaseColorTexture != null) {
  821. glTFPbrMetallicRoughness.baseColorTexture = glTFBaseColorTexture;
  822. }
  823. }
  824. if (metallicRoughnessFactors.metallicRoughnessTextureBase64) {
  825. const glTFMRColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughnessFactors.metallicRoughnessTextureBase64, "bjsMetallicRoughnessTexture_" + (textures.length) + ".png", mimeType, images, textures, babylonPBRMaterial.reflectivityTexture.coordinatesIndex, samplerIndex, imageData);
  826. if (glTFMRColorTexture != null) {
  827. glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFMRColorTexture;
  828. }
  829. }
  830. }
  831. }
  832. return metallicRoughnessFactors
  833. }
  834. /**
  835. * Converts a Babylon PBR Metallic Roughness Material to a glTF Material
  836. * @param babylonPBRMaterial BJS PBR Metallic Roughness Material
  837. * @param mimeType mime type to use for the textures
  838. * @param images array of glTF image interfaces
  839. * @param textures array of glTF texture interfaces
  840. * @param materials array of glTF material interfaces
  841. * @param imageData map of image file name to data
  842. * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
  843. */
  844. public static _ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
  845. const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
  846. let metallicRoughness: Nullable<_IPBRMetallicRoughness>;
  847. const glTFMaterial: IMaterial = {
  848. name: babylonPBRMaterial.name
  849. };
  850. const useMetallicRoughness = babylonPBRMaterial.isMetallicWorkflow();
  851. if (useMetallicRoughness) {
  852. metallicRoughness = this._ConvertMetalRoughFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
  853. }
  854. else {
  855. metallicRoughness = this._ConvertSpecGlossFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
  856. }
  857. if (metallicRoughness) {
  858. if (!(this.FuzzyEquals(metallicRoughness.baseColor, Color3.White(), this._epsilon) && babylonPBRMaterial.alpha >= this._epsilon)) {
  859. glTFPbrMetallicRoughness.baseColorFactor = [
  860. metallicRoughness.baseColor.r,
  861. metallicRoughness.baseColor.g,
  862. metallicRoughness.baseColor.b,
  863. babylonPBRMaterial.alpha
  864. ];
  865. }
  866. if (metallicRoughness.metallic != null && metallicRoughness.metallic !== 1) {
  867. glTFPbrMetallicRoughness.metallicFactor = metallicRoughness.metallic;
  868. }
  869. if (metallicRoughness.roughness != null && metallicRoughness.roughness !== 1) {
  870. glTFPbrMetallicRoughness.roughnessFactor = metallicRoughness.roughness;
  871. }
  872. if (babylonPBRMaterial.backFaceCulling != null && !babylonPBRMaterial.backFaceCulling) {
  873. if (!babylonPBRMaterial.twoSidedLighting) {
  874. Tools.Warn(babylonPBRMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
  875. }
  876. glTFMaterial.doubleSided = true;
  877. }
  878. if (hasTextureCoords) {
  879. if (babylonPBRMaterial.bumpTexture) {
  880. const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.bumpTexture, mimeType, images, textures, samplers, imageData);
  881. if (glTFTexture) {
  882. glTFMaterial.normalTexture = glTFTexture;
  883. if (babylonPBRMaterial.bumpTexture.level !== 1) {
  884. glTFMaterial.normalTexture.scale = babylonPBRMaterial.bumpTexture.level;
  885. }
  886. }
  887. }
  888. if (babylonPBRMaterial.ambientTexture) {
  889. const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.ambientTexture, mimeType, images, textures, samplers, imageData);
  890. if (glTFTexture) {
  891. let occlusionTexture: IMaterialOcclusionTextureInfo = {
  892. index: glTFTexture.index
  893. };
  894. glTFMaterial.occlusionTexture = occlusionTexture;
  895. if (babylonPBRMaterial.ambientTextureStrength) {
  896. occlusionTexture.strength = babylonPBRMaterial.ambientTextureStrength;
  897. }
  898. }
  899. }
  900. if (babylonPBRMaterial.emissiveTexture) {
  901. const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, samplers, imageData);
  902. if (glTFTexture != null) {
  903. glTFMaterial.emissiveTexture = glTFTexture;
  904. }
  905. }
  906. }
  907. if (!this.FuzzyEquals(babylonPBRMaterial.emissiveColor, Color3.Black(), this._epsilon)) {
  908. glTFMaterial.emissiveFactor = babylonPBRMaterial.emissiveColor.asArray();
  909. }
  910. if (babylonPBRMaterial.transparencyMode != null) {
  911. const alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMaterial);
  912. if (alphaMode) {
  913. if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
  914. glTFMaterial.alphaMode = alphaMode;
  915. if (alphaMode === MaterialAlphaMode.MASK) {
  916. glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
  917. }
  918. }
  919. }
  920. }
  921. glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
  922. materials.push(glTFMaterial);
  923. }
  924. }
  925. private static GetPixelsFromTexture(babylonTexture: Texture): Uint8Array | Float32Array {
  926. let pixels = babylonTexture.textureType === Engine.TEXTURETYPE_UNSIGNED_INT ? babylonTexture.readPixels() as Uint8Array : babylonTexture.readPixels() as Float32Array;
  927. return pixels;
  928. }
  929. /**
  930. * Extracts a texture from a Babylon texture into file data and glTF data
  931. * @param babylonTexture Babylon texture to extract
  932. * @param mimeType Mime Type of the babylonTexture
  933. * @param images Array of glTF images
  934. * @param textures Array of glTF textures
  935. * @param imageData map of image file name and data
  936. * @return glTF texture info, or null if the texture format is not supported
  937. */
  938. private static _ExportTexture(babylonTexture: BaseTexture, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
  939. const sampler = _GLTFMaterial._GetGLTFTextureSampler(babylonTexture);
  940. let samplerIndex: Nullable<number> = null;
  941. // if a pre-existing sampler with identical parameters exists, then reuse the previous sampler
  942. let foundSamplerIndex: Nullable<number> = null;
  943. for (let i = 0; i < samplers.length; ++i) {
  944. let s = samplers[i];
  945. if (s.minFilter === sampler.minFilter && s.magFilter === sampler.magFilter &&
  946. s.wrapS === sampler.wrapS && s.wrapT === sampler.wrapT) {
  947. foundSamplerIndex = i;
  948. break;
  949. }
  950. }
  951. if (foundSamplerIndex == null) {
  952. samplers.push(sampler);
  953. samplerIndex = samplers.length - 1;
  954. }
  955. else {
  956. samplerIndex = foundSamplerIndex;
  957. }
  958. let textureName = "texture_" + (textures.length - 1).toString();
  959. let textureData = babylonTexture.getInternalTexture();
  960. if (textureData != null) {
  961. textureName = textureData.url || textureName;
  962. }
  963. textureName = Tools.GetFilename(textureName);
  964. const baseFile = textureName.split('.')[0];
  965. let extension = "";
  966. if (mimeType === ImageMimeType.JPEG) {
  967. extension = ".jpg";
  968. }
  969. else if (mimeType === ImageMimeType.PNG) {
  970. extension = ".png";
  971. }
  972. else {
  973. Tools.Error("Unsupported mime type " + mimeType);
  974. return null;
  975. }
  976. textureName = baseFile + extension;
  977. const pixels = _GLTFMaterial.GetPixelsFromTexture(babylonTexture as Texture);
  978. const size = babylonTexture.getSize();
  979. const base64Data = this._CreateBase64FromCanvas(pixels, size.width, size.height, mimeType);
  980. return this._GetTextureInfoFromBase64(base64Data, textureName, mimeType, images, textures, babylonTexture.coordinatesIndex, samplerIndex, imageData);
  981. }
  982. /**
  983. * Builds a texture from base64 string
  984. * @param base64Texture base64 texture string
  985. * @param textureName Name to use for the texture
  986. * @param mimeType image mime type for the texture
  987. * @param images array of images
  988. * @param textures array of textures
  989. * @param imageData map of image data
  990. * @returns glTF texture info, or null if the texture format is not supported
  991. */
  992. private static _GetTextureInfoFromBase64(base64Texture: string, textureName: string, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], texCoordIndex: number, samplerIndex: Nullable<number>, imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
  993. let textureInfo: Nullable<ITextureInfo> = null;
  994. const glTFTexture: ITexture = {
  995. source: images.length,
  996. name: textureName
  997. };
  998. if (samplerIndex != null) {
  999. glTFTexture.sampler = samplerIndex;
  1000. }
  1001. const binStr = atob(base64Texture.split(',')[1]);
  1002. let arrBuff = new ArrayBuffer(binStr.length);
  1003. const arr = new Uint8Array(arrBuff);
  1004. for (let i = 0, length = binStr.length; i < length; ++i) {
  1005. arr[i] = binStr.charCodeAt(i);
  1006. }
  1007. const imageValues = { data: arr, mimeType: mimeType };
  1008. imageData[textureName] = imageValues;
  1009. if (mimeType === ImageMimeType.JPEG || mimeType === ImageMimeType.PNG) {
  1010. const glTFImage: IImage = {
  1011. uri: textureName
  1012. }
  1013. let foundIndex: Nullable<number> = null;
  1014. for (let i = 0; i < images.length; ++i) {
  1015. if (images[i].uri === textureName) {
  1016. foundIndex = i;
  1017. break;
  1018. }
  1019. }
  1020. if (foundIndex == null) {
  1021. images.push(glTFImage);
  1022. glTFTexture.source = images.length - 1;
  1023. }
  1024. else {
  1025. glTFTexture.source = foundIndex;
  1026. }
  1027. textures.push(glTFTexture);
  1028. textureInfo = {
  1029. index: textures.length - 1
  1030. }
  1031. if (texCoordIndex) {
  1032. textureInfo.texCoord = texCoordIndex;
  1033. }
  1034. }
  1035. return textureInfo;
  1036. }
  1037. }
  1038. }