babylon.material.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. module BABYLON {
  2. export class MaterialDefines {
  3. private _keys: string[];
  4. private _isDirty = true;
  5. public _renderId: number;
  6. public _areLightsDirty = true;
  7. public _areAttributesDirty = true;
  8. public _areTexturesDirty = true;
  9. public _areFresnelDirty = true;
  10. public _areMiscDirty = true;
  11. public _areImageProcessingDirty = true;
  12. public _normals = false;
  13. public _uvs = false;
  14. public _needNormals = false;
  15. public _needUVs = false;
  16. public get isDirty(): boolean {
  17. return this._isDirty;
  18. }
  19. public markAsProcessed() {
  20. this._isDirty = false;
  21. this._areAttributesDirty = false;
  22. this._areTexturesDirty = false;
  23. this._areFresnelDirty = false;
  24. this._areLightsDirty = false;
  25. this._areMiscDirty = false;
  26. this._areImageProcessingDirty = false;
  27. }
  28. public markAsUnprocessed() {
  29. this._isDirty = true;
  30. }
  31. public markAllAsDirty() {
  32. this._areTexturesDirty = true;
  33. this._areAttributesDirty = true;
  34. this._areLightsDirty = true;
  35. this._areFresnelDirty = true;
  36. this._areMiscDirty = true;
  37. this._areImageProcessingDirty = true;
  38. this._isDirty = true;
  39. }
  40. public markAsImageProcessingDirty() {
  41. this._areImageProcessingDirty = true;
  42. this._isDirty = true;
  43. }
  44. public markAsLightDirty() {
  45. this._areLightsDirty = true;
  46. this._isDirty = true;
  47. }
  48. public markAsAttributesDirty() {
  49. this._areAttributesDirty = true;
  50. this._isDirty = true;
  51. }
  52. public markAsTexturesDirty() {
  53. this._areTexturesDirty = true;
  54. this._isDirty = true;
  55. }
  56. public markAsFresnelDirty() {
  57. this._areFresnelDirty = true;
  58. this._isDirty = true;
  59. }
  60. public markAsMiscDirty() {
  61. this._areMiscDirty = true;
  62. this._isDirty = true;
  63. }
  64. public rebuild() {
  65. if (this._keys) {
  66. delete this._keys;
  67. }
  68. this._keys = [];
  69. for (var key of Object.keys(this)) {
  70. if (key[0] === "_") {
  71. continue;
  72. }
  73. this._keys.push(key);
  74. }
  75. }
  76. public isEqual(other: MaterialDefines): boolean {
  77. if (this._keys.length !== other._keys.length) {
  78. return false;
  79. }
  80. for (var index = 0; index < this._keys.length; index++) {
  81. var prop = this._keys[index];
  82. if (this[prop] !== other[prop]) {
  83. return false;
  84. }
  85. }
  86. return true;
  87. }
  88. public cloneTo(other: MaterialDefines): void {
  89. if (this._keys.length !== other._keys.length) {
  90. other._keys = this._keys.slice(0);
  91. }
  92. for (var index = 0; index < this._keys.length; index++) {
  93. var prop = this._keys[index];
  94. other[prop] = this[prop];
  95. }
  96. }
  97. public reset(): void {
  98. for (var index = 0; index < this._keys.length; index++) {
  99. var prop = this._keys[index];
  100. if (typeof (this[prop]) === "number") {
  101. this[prop] = 0;
  102. } else {
  103. this[prop] = false;
  104. }
  105. }
  106. }
  107. public toString(): string {
  108. var result = "";
  109. for (var index = 0; index < this._keys.length; index++) {
  110. var prop = this._keys[index];
  111. var value = this[prop];
  112. if (typeof (value) === "number") {
  113. result += "#define " + prop + " " + this[prop] + "\n";
  114. } else if (value) {
  115. result += "#define " + prop + "\n";
  116. }
  117. }
  118. return result;
  119. }
  120. }
  121. export class Material {
  122. private static _TriangleFillMode = 0;
  123. private static _WireFrameFillMode = 1;
  124. private static _PointFillMode = 2;
  125. public static get TriangleFillMode(): number {
  126. return Material._TriangleFillMode;
  127. }
  128. public static get WireFrameFillMode(): number {
  129. return Material._WireFrameFillMode;
  130. }
  131. public static get PointFillMode(): number {
  132. return Material._PointFillMode;
  133. }
  134. private static _ClockWiseSideOrientation = 0;
  135. private static _CounterClockWiseSideOrientation = 1;
  136. public static get ClockWiseSideOrientation(): number {
  137. return Material._ClockWiseSideOrientation;
  138. }
  139. public static get CounterClockWiseSideOrientation(): number {
  140. return Material._CounterClockWiseSideOrientation;
  141. }
  142. private static _TextureDirtyFlag = 1;
  143. private static _LightDirtyFlag = 2;
  144. private static _FresnelDirtyFlag = 4;
  145. private static _AttributesDirtyFlag = 8;
  146. private static _MiscDirtyFlag = 16;
  147. public static get TextureDirtyFlag(): number {
  148. return Material._TextureDirtyFlag;
  149. }
  150. public static get LightDirtyFlag(): number {
  151. return Material._LightDirtyFlag;
  152. }
  153. public static get FresnelDirtyFlag(): number {
  154. return Material._FresnelDirtyFlag;
  155. }
  156. public static get AttributesDirtyFlag(): number {
  157. return Material._AttributesDirtyFlag;
  158. }
  159. public static get MiscDirtyFlag(): number {
  160. return Material._MiscDirtyFlag;
  161. }
  162. @serialize()
  163. public id: string;
  164. @serialize()
  165. public name: string;
  166. @serialize()
  167. public checkReadyOnEveryCall = false;
  168. @serialize()
  169. public checkReadyOnlyOnce = false;
  170. @serialize()
  171. public state = "";
  172. @serialize()
  173. public alpha = 1.0;
  174. @serialize("backFaceCulling")
  175. protected _backFaceCulling = true;
  176. public set backFaceCulling(value : boolean) {
  177. if (this._backFaceCulling === value) {
  178. return;
  179. }
  180. this._backFaceCulling = value;
  181. this.markAsDirty(Material.TextureDirtyFlag);
  182. }
  183. public get backFaceCulling(): boolean {
  184. return this._backFaceCulling;
  185. }
  186. @serialize()
  187. public sideOrientation: number;
  188. public onCompiled: (effect: Effect) => void;
  189. public onError: (effect: Effect, errors: string) => void;
  190. public getRenderTargetTextures: () => SmartArray<RenderTargetTexture>;
  191. public doNotSerialize = false;
  192. public storeEffectOnSubMeshes = false;
  193. /**
  194. * An event triggered when the material is disposed.
  195. * @type {BABYLON.Observable}
  196. */
  197. public onDisposeObservable = new Observable<Material>();
  198. private _onDisposeObserver: Observer<Material>;
  199. public set onDispose(callback: () => void) {
  200. if (this._onDisposeObserver) {
  201. this.onDisposeObservable.remove(this._onDisposeObserver);
  202. }
  203. this._onDisposeObserver = this.onDisposeObservable.add(callback);
  204. }
  205. /**
  206. * An event triggered when the material is bound.
  207. * @type {BABYLON.Observable}
  208. */
  209. public onBindObservable = new Observable<AbstractMesh>();
  210. private _onBindObserver: Observer<AbstractMesh>;
  211. public set onBind(callback: (Mesh: AbstractMesh) => void) {
  212. if (this._onBindObserver) {
  213. this.onBindObservable.remove(this._onBindObserver);
  214. }
  215. this._onBindObserver = this.onBindObservable.add(callback);
  216. }
  217. /**
  218. * An event triggered when the material is unbound.
  219. * @type {BABYLON.Observable}
  220. */
  221. public onUnBindObservable = new Observable<Material>();
  222. @serialize()
  223. public alphaMode = Engine.ALPHA_COMBINE;
  224. @serialize()
  225. private _needDepthPrePass = false;
  226. public set needDepthPrePass(value : boolean) {
  227. if (this._needDepthPrePass === value) {
  228. return;
  229. }
  230. this._needDepthPrePass = value;
  231. if (this._needDepthPrePass) {
  232. this.checkReadyOnEveryCall = true;
  233. }
  234. }
  235. public get needDepthPrePass(): boolean {
  236. return this._needDepthPrePass;
  237. }
  238. @serialize()
  239. public disableDepthWrite = false;
  240. @serialize()
  241. public forceDepthWrite = false;
  242. @serialize()
  243. public separateCullingPass = false;
  244. @serialize("fogEnabled")
  245. private _fogEnabled = true;
  246. public set fogEnabled(value : boolean) {
  247. if (this._fogEnabled === value) {
  248. return;
  249. }
  250. this._fogEnabled = value;
  251. this.markAsDirty(Material.MiscDirtyFlag);
  252. }
  253. public get fogEnabled(): boolean {
  254. return this._fogEnabled;
  255. }
  256. @serialize()
  257. public pointSize = 1.0;
  258. @serialize()
  259. public zOffset = 0;
  260. @serialize()
  261. public get wireframe(): boolean {
  262. return this._fillMode === Material.WireFrameFillMode;
  263. }
  264. public set wireframe(value: boolean) {
  265. this._fillMode = (value ? Material.WireFrameFillMode : Material.TriangleFillMode);
  266. }
  267. @serialize()
  268. public get pointsCloud(): boolean {
  269. return this._fillMode === Material.PointFillMode;
  270. }
  271. public set pointsCloud(value: boolean) {
  272. this._fillMode = (value ? Material.PointFillMode : Material.TriangleFillMode);
  273. }
  274. @serialize()
  275. public get fillMode(): number {
  276. return this._fillMode;
  277. }
  278. public set fillMode(value: number) {
  279. if (this._fillMode === value) {
  280. return;
  281. }
  282. this._fillMode = value;
  283. this.markAsDirty(Material.MiscDirtyFlag);
  284. }
  285. public _effect: Effect;
  286. public _wasPreviouslyReady = false;
  287. private _useUBO: boolean;
  288. private _scene: Scene;
  289. private _fillMode = Material.TriangleFillMode;
  290. private _cachedDepthWriteState: boolean;
  291. protected _uniformBuffer: UniformBuffer;
  292. constructor(name: string, scene: Scene, doNotAdd?: boolean) {
  293. this.name = name;
  294. this.id = name || Tools.RandomId();
  295. this._scene = scene || Engine.LastCreatedScene;
  296. if (this._scene.useRightHandedSystem) {
  297. this.sideOrientation = Material.ClockWiseSideOrientation;
  298. } else {
  299. this.sideOrientation = Material.CounterClockWiseSideOrientation;
  300. }
  301. this._uniformBuffer = new UniformBuffer(this._scene.getEngine());
  302. this._useUBO = this.getScene().getEngine().supportsUniformBuffers;
  303. if (!doNotAdd) {
  304. this._scene.materials.push(this);
  305. }
  306. }
  307. /**
  308. * @param {boolean} fullDetails - support for multiple levels of logging within scene loading
  309. * subclasses should override adding information pertainent to themselves
  310. */
  311. public toString(fullDetails? : boolean) : string {
  312. var ret = "Name: " + this.name;
  313. if (fullDetails){
  314. }
  315. return ret;
  316. }
  317. /**
  318. * Child classes can use it to update shaders
  319. */
  320. public getClassName(): string {
  321. return "Material";
  322. }
  323. public get isFrozen(): boolean {
  324. return this.checkReadyOnlyOnce;
  325. }
  326. public freeze(): void {
  327. this.checkReadyOnlyOnce = true;
  328. }
  329. public unfreeze(): void {
  330. this.checkReadyOnlyOnce = false;
  331. }
  332. public isReady(mesh?: AbstractMesh, useInstances?: boolean): boolean {
  333. return true;
  334. }
  335. public isReadyForSubMesh(mesh: AbstractMesh, subMesh: BaseSubMesh, useInstances?: boolean): boolean {
  336. return false;
  337. }
  338. public getEffect(): Effect {
  339. return this._effect;
  340. }
  341. public getScene(): Scene {
  342. return this._scene;
  343. }
  344. public needAlphaBlending(): boolean {
  345. return (this.alpha < 1.0);
  346. }
  347. public needAlphaTesting(): boolean {
  348. return false;
  349. }
  350. public getAlphaTestTexture(): BaseTexture {
  351. return null;
  352. }
  353. public markDirty(): void {
  354. this._wasPreviouslyReady = false;
  355. }
  356. public _preBind(effect?: Effect, overrideOrientation? : number): boolean {
  357. var engine = this._scene.getEngine();
  358. var orientation = (overrideOrientation == null) ? this.sideOrientation : overrideOrientation;
  359. var reverse = orientation === Material.ClockWiseSideOrientation;
  360. engine.enableEffect(effect ? effect : this._effect);
  361. engine.setState(this.backFaceCulling, this.zOffset, false, reverse);
  362. return reverse;
  363. }
  364. public bind(world: Matrix, mesh?: Mesh): void {
  365. }
  366. public bindForSubMesh(world: Matrix, mesh: Mesh, subMesh: SubMesh): void {
  367. }
  368. public bindOnlyWorldMatrix(world: Matrix): void {
  369. }
  370. public bindSceneUniformBuffer(effect: Effect, sceneUbo: UniformBuffer): void {
  371. sceneUbo.bindToEffect(effect, "Scene");
  372. }
  373. public bindView(effect: Effect): void {
  374. if (!this._useUBO) {
  375. effect.setMatrix("view", this.getScene().getViewMatrix());
  376. } else {
  377. this.bindSceneUniformBuffer(effect, this.getScene().getSceneUniformBuffer());
  378. }
  379. }
  380. public bindViewProjection(effect: Effect): void {
  381. if (!this._useUBO) {
  382. effect.setMatrix("viewProjection", this.getScene().getTransformMatrix());
  383. } else {
  384. this.bindSceneUniformBuffer(effect, this.getScene().getSceneUniformBuffer());
  385. }
  386. }
  387. protected _afterBind(mesh: Mesh): void {
  388. this._scene._cachedMaterial = this;
  389. if (mesh) {
  390. this._scene._cachedVisibility = mesh.visibility;
  391. } else {
  392. this._scene._cachedVisibility = 1;
  393. }
  394. this.onBindObservable.notifyObservers(mesh);
  395. if (this.disableDepthWrite) {
  396. var engine = this._scene.getEngine();
  397. this._cachedDepthWriteState = engine.getDepthWrite();
  398. engine.setDepthWrite(false);
  399. }
  400. }
  401. public unbind(): void {
  402. this.onUnBindObservable.notifyObservers(this);
  403. if (this.disableDepthWrite) {
  404. var engine = this._scene.getEngine();
  405. engine.setDepthWrite(this._cachedDepthWriteState);
  406. }
  407. }
  408. public getActiveTextures(): BaseTexture[] {
  409. return [];
  410. }
  411. public hasTexture(texture: BaseTexture): boolean {
  412. return false;
  413. }
  414. public clone(name: string): Material {
  415. return null;
  416. }
  417. public getBindedMeshes(): AbstractMesh[] {
  418. var result = new Array<AbstractMesh>();
  419. for (var index = 0; index < this._scene.meshes.length; index++) {
  420. var mesh = this._scene.meshes[index];
  421. if (mesh.material === this) {
  422. result.push(mesh);
  423. }
  424. }
  425. return result;
  426. }
  427. /**
  428. * Force shader compilation including textures ready check
  429. */
  430. public forceCompilation(mesh: AbstractMesh, onCompiled: (material: Material) => void, options?: { alphaTest: boolean, clipPlane: boolean }): void {
  431. var subMesh = new BaseSubMesh();
  432. var scene = this.getScene();
  433. var engine = scene.getEngine();
  434. var checkReady = () => {
  435. if (!this._scene || !this._scene.getEngine()) {
  436. return;
  437. }
  438. if (subMesh._materialDefines) {
  439. subMesh._materialDefines._renderId = -1;
  440. }
  441. var alphaTestState = engine.getAlphaTesting();
  442. var clipPlaneState = scene.clipPlane;
  443. engine.setAlphaTesting(options ? options.alphaTest : this.needAlphaTesting());
  444. if (options && options.clipPlane) {
  445. scene.clipPlane = new Plane(0, 0, 0, 1);
  446. }
  447. if (this.storeEffectOnSubMeshes) {
  448. if (this.isReadyForSubMesh(mesh, subMesh)) {
  449. if (onCompiled) {
  450. onCompiled(this);
  451. }
  452. }
  453. else {
  454. setTimeout(checkReady, 16);
  455. }
  456. } else {
  457. if (this.isReady(mesh)) {
  458. if (onCompiled) {
  459. onCompiled(this);
  460. }
  461. }
  462. else {
  463. setTimeout(checkReady, 16);
  464. }
  465. }
  466. engine.setAlphaTesting(alphaTestState);
  467. if (options && options.clipPlane) {
  468. scene.clipPlane = clipPlaneState;
  469. }
  470. };
  471. checkReady();
  472. }
  473. public markAsDirty(flag: number): void {
  474. if (flag & Material.TextureDirtyFlag) {
  475. this._markAllSubMeshesAsTexturesDirty();
  476. }
  477. if (flag & Material.LightDirtyFlag) {
  478. this._markAllSubMeshesAsLightsDirty();
  479. }
  480. if (flag & Material.FresnelDirtyFlag) {
  481. this._markAllSubMeshesAsFresnelDirty();
  482. }
  483. if (flag & Material.AttributesDirtyFlag) {
  484. this._markAllSubMeshesAsAttributesDirty();
  485. }
  486. if (flag & Material.MiscDirtyFlag) {
  487. this._markAllSubMeshesAsMiscDirty();
  488. }
  489. this.getScene().resetCachedMaterial();
  490. }
  491. protected _markAllSubMeshesAsDirty(func: (defines: MaterialDefines) => void) {
  492. for (var mesh of this.getScene().meshes) {
  493. if (!mesh.subMeshes) {
  494. continue;
  495. }
  496. for (var subMesh of mesh.subMeshes) {
  497. if (subMesh.getMaterial() !== this) {
  498. continue;
  499. }
  500. if (!subMesh._materialDefines) {
  501. continue;
  502. }
  503. func(subMesh._materialDefines);
  504. }
  505. }
  506. }
  507. protected _markAllSubMeshesAsImageProcessingDirty() {
  508. this._markAllSubMeshesAsDirty(defines => defines.markAsImageProcessingDirty());
  509. }
  510. protected _markAllSubMeshesAsTexturesDirty() {
  511. this._markAllSubMeshesAsDirty(defines => defines.markAsTexturesDirty());
  512. }
  513. protected _markAllSubMeshesAsFresnelDirty() {
  514. this._markAllSubMeshesAsDirty(defines => defines.markAsFresnelDirty());
  515. }
  516. protected _markAllSubMeshesAsLightsDirty() {
  517. this._markAllSubMeshesAsDirty(defines => defines.markAsLightDirty());
  518. }
  519. protected _markAllSubMeshesAsAttributesDirty() {
  520. this._markAllSubMeshesAsDirty(defines => defines.markAsAttributesDirty());
  521. }
  522. protected _markAllSubMeshesAsMiscDirty() {
  523. this._markAllSubMeshesAsDirty(defines => defines.markAsMiscDirty());
  524. }
  525. public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean): void {
  526. // Animations
  527. this.getScene().stopAnimation(this);
  528. // Remove from scene
  529. var index = this._scene.materials.indexOf(this);
  530. if (index >= 0) {
  531. this._scene.materials.splice(index, 1);
  532. }
  533. // Remove from meshes
  534. for (index = 0; index < this._scene.meshes.length; index++) {
  535. var mesh = this._scene.meshes[index];
  536. if (mesh.material === this) {
  537. mesh.material = null;
  538. if ((<Mesh>mesh).geometry) {
  539. var geometry = (<Mesh>mesh).geometry;
  540. if (this.storeEffectOnSubMeshes) {
  541. for (var subMesh of mesh.subMeshes) {
  542. geometry._releaseVertexArrayObject(subMesh._materialEffect);
  543. }
  544. } else {
  545. geometry._releaseVertexArrayObject(this._effect)
  546. }
  547. }
  548. }
  549. }
  550. this._uniformBuffer.dispose();
  551. // Shader are kept in cache for further use but we can get rid of this by using forceDisposeEffect
  552. if (forceDisposeEffect && this._effect) {
  553. if (this.storeEffectOnSubMeshes) {
  554. for (var subMesh of mesh.subMeshes) {
  555. this._scene.getEngine()._releaseEffect(subMesh._materialEffect);
  556. }
  557. } else {
  558. this._scene.getEngine()._releaseEffect(this._effect);
  559. }
  560. this._effect = null;
  561. }
  562. // Callback
  563. this.onDisposeObservable.notifyObservers(this);
  564. this.onDisposeObservable.clear();
  565. this.onBindObservable.clear();
  566. this.onUnBindObservable.clear();
  567. }
  568. public serialize(): any {
  569. return SerializationHelper.Serialize(this);
  570. }
  571. public static ParseMultiMaterial(parsedMultiMaterial: any, scene: Scene): MultiMaterial {
  572. var multiMaterial = new BABYLON.MultiMaterial(parsedMultiMaterial.name, scene);
  573. multiMaterial.id = parsedMultiMaterial.id;
  574. if (Tags) {
  575. Tags.AddTagsTo(multiMaterial, parsedMultiMaterial.tags);
  576. }
  577. for (var matIndex = 0; matIndex < parsedMultiMaterial.materials.length; matIndex++) {
  578. var subMatId = parsedMultiMaterial.materials[matIndex];
  579. if (subMatId) {
  580. multiMaterial.subMaterials.push(scene.getMaterialByID(subMatId));
  581. } else {
  582. multiMaterial.subMaterials.push(null);
  583. }
  584. }
  585. return multiMaterial;
  586. }
  587. public static Parse(parsedMaterial: any, scene: Scene, rootUrl: string) {
  588. if (!parsedMaterial.customType) {
  589. return StandardMaterial.Parse(parsedMaterial, scene, rootUrl);
  590. }
  591. if (parsedMaterial.customType === "BABYLON.PBRMaterial" && parsedMaterial.overloadedAlbedo) {
  592. parsedMaterial.customType = "BABYLON.LegacyPBRMaterial";
  593. if (!(<any>BABYLON).LegacyPBRMaterial) {
  594. BABYLON.Tools.Error("Your scene is trying to load a legacy version of the PBRMaterial, please, include it from the materials library.");
  595. return;
  596. }
  597. }
  598. var materialType = Tools.Instantiate(parsedMaterial.customType);
  599. return materialType.Parse(parsedMaterial, scene, rootUrl);;
  600. }
  601. }
  602. }