nodeMaterial.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. import { NodeMaterialBlock } from './nodeMaterialBlock';
  2. import { PushMaterial } from '../pushMaterial';
  3. import { Scene } from '../../scene';
  4. import { AbstractMesh } from '../../Meshes/abstractMesh';
  5. import { Matrix } from '../../Maths/math';
  6. import { Mesh } from '../../Meshes/mesh';
  7. import { Engine } from '../../Engines/engine';
  8. import { NodeMaterialBuildState } from './nodeMaterialBuildState';
  9. import { EffectCreationOptions, EffectFallbacks } from '../effect';
  10. import { BaseTexture } from '../../Materials/Textures/baseTexture';
  11. import { NodeMaterialConnectionPoint } from './nodeMaterialBlockConnectionPoint';
  12. import { NodeMaterialBlockConnectionPointTypes } from './nodeMaterialBlockConnectionPointTypes';
  13. import { Observable } from '../../Misc/observable';
  14. import { NodeMaterialBlockTargets } from './nodeMaterialBlockTargets';
  15. import { NodeMaterialBuildStateSharedData } from './NodeMaterialBuildStateSharedData';
  16. import { SubMesh } from '../../Meshes/subMesh';
  17. import { MaterialDefines } from '../../Materials/materialDefines';
  18. import { MaterialHelper } from '../../Materials/materialHelper';
  19. /**
  20. * Class used to configure NodeMaterial
  21. */
  22. export interface INodeMaterialOptions {
  23. /**
  24. * Defines if blocks should emit comments
  25. */
  26. emitComments: boolean;
  27. }
  28. /**
  29. * Class used to create a node based material built by assembling shader blocks
  30. */
  31. export class NodeMaterial extends PushMaterial {
  32. private _options: INodeMaterialOptions;
  33. private _vertexCompilationState: NodeMaterialBuildState;
  34. private _fragmentCompilationState: NodeMaterialBuildState;
  35. private _sharedData: NodeMaterialBuildStateSharedData;
  36. private _buildId: number = 0;
  37. private _buildWasSuccessful = false;
  38. private _cachedWorldViewMatrix = new Matrix();
  39. private _cachedWorldViewProjectionMatrix = new Matrix();
  40. private _textureConnectionPoints = new Array<NodeMaterialConnectionPoint>();
  41. /**
  42. * Observable raised when the material is built
  43. */
  44. public onBuildObservable = new Observable<NodeMaterial>();
  45. /**
  46. * Gets or sets the root nodes of the material vertex shader
  47. */
  48. private _vertexRootNodes = new Array<NodeMaterialBlock>();
  49. /**
  50. * Gets or sets the root nodes of the material fragment (pixel) shader
  51. */
  52. private _fragmentRootNodes = new Array<NodeMaterialBlock>();
  53. /** Gets or sets options to control the node material overall behavior */
  54. public get options() {
  55. return this._options;
  56. }
  57. public set options(options: INodeMaterialOptions) {
  58. this._options = options;
  59. }
  60. /**
  61. * Create a new node based material
  62. * @param name defines the material name
  63. * @param scene defines the hosting scene
  64. * @param options defines creation option
  65. */
  66. constructor(name: string, scene?: Scene, options: Partial<INodeMaterialOptions> = {}) {
  67. super(name, scene || Engine.LastCreatedScene!);
  68. this._options = {
  69. emitComments: false,
  70. ...options
  71. };
  72. }
  73. /**
  74. * Gets the current class name of the material e.g. "NodeMaterial"
  75. * @returns the class name
  76. */
  77. public getClassName(): string {
  78. return "NodeMaterial";
  79. }
  80. /**
  81. * Add a new block to the list of root nodes
  82. * @param node defines the node to add
  83. * @returns the current material
  84. */
  85. public addRootNode(node: NodeMaterialBlock) {
  86. if (node.target === null) {
  87. throw "This node is not meant to be at root level. You may want to explicitly set its target value.";
  88. }
  89. if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0 && node._canAddAtVertexRoot) {
  90. this._addVertexRootNode(node);
  91. }
  92. if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0 && node._canAddAtFragmentRoot) {
  93. this._addFragmentRootNode(node);
  94. }
  95. return this;
  96. }
  97. /**
  98. * Remove a block from the list of root nodes
  99. * @param node defines the node to remove
  100. * @returns the current material
  101. */
  102. public removeRootNode(node: NodeMaterialBlock) {
  103. if (node.target === null) {
  104. return this;
  105. }
  106. if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) {
  107. this._removeVertexRootNode(node);
  108. }
  109. if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) {
  110. this._removeFragmentRootNode(node);
  111. }
  112. return this;
  113. }
  114. private _addVertexRootNode(node: NodeMaterialBlock) {
  115. if (this._vertexRootNodes.indexOf(node) !== -1) {
  116. return;
  117. }
  118. node.target = NodeMaterialBlockTargets.Vertex;
  119. this._vertexRootNodes.push(node);
  120. return this;
  121. }
  122. private _removeVertexRootNode(node: NodeMaterialBlock) {
  123. let index = this._vertexRootNodes.indexOf(node);
  124. if (index === -1) {
  125. return;
  126. }
  127. this._vertexRootNodes.splice(index, 1);
  128. return this;
  129. }
  130. private _addFragmentRootNode(node: NodeMaterialBlock) {
  131. if (this._fragmentRootNodes.indexOf(node) !== -1) {
  132. return;
  133. }
  134. node.target = NodeMaterialBlockTargets.Fragment;
  135. this._fragmentRootNodes.push(node);
  136. return this;
  137. }
  138. private _removeFragmentRootNode(node: NodeMaterialBlock) {
  139. let index = this._fragmentRootNodes.indexOf(node);
  140. if (index === -1) {
  141. return;
  142. }
  143. this._fragmentRootNodes.splice(index, 1);
  144. return this;
  145. }
  146. /**
  147. * Specifies if the material will require alpha blending
  148. * @returns a boolean specifying if alpha blending is needed
  149. */
  150. public needAlphaBlending(): boolean {
  151. return (this.alpha < 1.0) || this._sharedData.hints.needAlphaBlending;
  152. }
  153. /**
  154. * Specifies if this material should be rendered in alpha test mode
  155. * @returns a boolean specifying if an alpha test is needed.
  156. */
  157. public needAlphaTesting(): boolean {
  158. return this._sharedData.hints.needAlphaTesting;
  159. }
  160. private _propagateTarget(node: NodeMaterialBlock, target: NodeMaterialBlockTargets, state: NodeMaterialBuildState) {
  161. node.target = target;
  162. node.initialize(state);
  163. for (var exitPoint of node.outputs) {
  164. for (var block of exitPoint.connectedBlocks) {
  165. if (block) {
  166. this._propagateTarget(block, target, state);
  167. }
  168. }
  169. }
  170. }
  171. private _resetDualBlocks(node: NodeMaterialBlock, id: number) {
  172. if (node.target === NodeMaterialBlockTargets.VertexAndFragment) {
  173. node.buildId = id;
  174. }
  175. for (var exitPoint of node.outputs) {
  176. for (var block of exitPoint.connectedBlocks) {
  177. if (block) {
  178. this._resetDualBlocks(block, id);
  179. }
  180. }
  181. }
  182. }
  183. /**
  184. * Build the material and generates the inner effect
  185. * @param verbose defines if the build should log activity
  186. */
  187. public build(verbose: boolean = false) {
  188. this._buildWasSuccessful = false;
  189. if (this._vertexRootNodes.length === 0) {
  190. throw "You must define at least one vertexRootNode";
  191. }
  192. if (this._fragmentRootNodes.length === 0) {
  193. throw "You must define at least one fragmentRootNode";
  194. }
  195. // Compilation state
  196. this._vertexCompilationState = new NodeMaterialBuildState();
  197. this._vertexCompilationState.target = NodeMaterialBlockTargets.Vertex;
  198. this._fragmentCompilationState = new NodeMaterialBuildState();
  199. this._fragmentCompilationState.target = NodeMaterialBlockTargets.Fragment;
  200. // Shared data
  201. this._sharedData = new NodeMaterialBuildStateSharedData();
  202. this._vertexCompilationState.sharedData = this._sharedData;
  203. this._fragmentCompilationState.sharedData = this._sharedData;
  204. this._sharedData.buildId = this._buildId;
  205. this._sharedData.emitComments = this._options.emitComments;
  206. this._sharedData.verbose = verbose;
  207. // Propagate targets
  208. for (var vertexRootNode of this._vertexRootNodes) {
  209. this._propagateTarget(vertexRootNode, NodeMaterialBlockTargets.Vertex, this._vertexCompilationState);
  210. }
  211. for (var fragmentRootNode of this._fragmentRootNodes) {
  212. this._propagateTarget(fragmentRootNode, NodeMaterialBlockTargets.Fragment, this._fragmentCompilationState);
  213. }
  214. // Vertex
  215. for (var vertexRootNode of this._vertexRootNodes) {
  216. vertexRootNode.build(this._vertexCompilationState);
  217. }
  218. // Fragment
  219. this._fragmentCompilationState._vertexState = this._vertexCompilationState;
  220. for (var fragmentRootNode of this._fragmentRootNodes) {
  221. this._resetDualBlocks(fragmentRootNode, this._buildId - 1);
  222. }
  223. for (var fragmentRootNode of this._fragmentRootNodes) {
  224. fragmentRootNode.build(this._fragmentCompilationState);
  225. }
  226. // Finalize
  227. this._vertexCompilationState.finalize(this._vertexCompilationState);
  228. this._fragmentCompilationState.finalize(this._fragmentCompilationState);
  229. // Textures
  230. this._textureConnectionPoints = this._sharedData.uniformConnectionPoints.filter((u) => u.type === NodeMaterialBlockConnectionPointTypes.Texture);
  231. this._buildId++;
  232. // Errors
  233. this._sharedData.emitErrors();
  234. if (verbose) {
  235. console.log("Vertex shader:");
  236. console.log(this._vertexCompilationState.compilationString);
  237. console.log("Fragment shader:");
  238. console.log(this._fragmentCompilationState.compilationString);
  239. }
  240. this._buildWasSuccessful = true;
  241. this.onBuildObservable.notifyObservers(this);
  242. }
  243. /**
  244. * Get if the submesh is ready to be used and all its information available.
  245. * Child classes can use it to update shaders
  246. * @param mesh defines the mesh to check
  247. * @param subMesh defines which submesh to check
  248. * @param useInstances specifies that instances should be used
  249. * @returns a boolean indicating that the submesh is ready or not
  250. */
  251. public isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances: boolean = false): boolean {
  252. if (!this._buildWasSuccessful) {
  253. return false;
  254. }
  255. if (subMesh.effect && this.isFrozen) {
  256. if (this._wasPreviouslyReady) {
  257. return true;
  258. }
  259. }
  260. if (!subMesh._materialDefines) {
  261. subMesh._materialDefines = new MaterialDefines();
  262. }
  263. var scene = this.getScene();
  264. var defines = subMesh._materialDefines;
  265. if (!this.checkReadyOnEveryCall && subMesh.effect) {
  266. if (defines._renderId === scene.getRenderId()) {
  267. return true;
  268. }
  269. }
  270. var engine = scene.getEngine();
  271. // Textures
  272. for (var connectionPoint of this._textureConnectionPoints) {
  273. let texture = connectionPoint.value as BaseTexture;
  274. if (texture && !texture.isReady()) {
  275. return false;
  276. }
  277. }
  278. // Bones
  279. MaterialHelper.PrepareDefinesForAttributes(mesh, defines, false, true, false, false);
  280. if (defines.isDirty) {
  281. defines.markAsProcessed();
  282. // Uniforms
  283. let mergedUniforms = this._vertexCompilationState.uniforms;
  284. this._fragmentCompilationState.uniforms.forEach((u) => {
  285. let index = mergedUniforms.indexOf(u);
  286. if (index === -1) {
  287. mergedUniforms.push(u);
  288. }
  289. });
  290. // Samplers
  291. let mergedSamplers = this._vertexCompilationState.samplers;
  292. this._fragmentCompilationState.samplers.forEach((s) => {
  293. let index = mergedSamplers.indexOf(s);
  294. if (index === -1) {
  295. mergedSamplers.push(s);
  296. }
  297. });
  298. var fallbacks = new EffectFallbacks();
  299. this._sharedData.blocksWithFallbacks.forEach(b => {
  300. b.provideFallbacks(mesh, fallbacks);
  301. })
  302. let previousEffect = subMesh.effect;
  303. // Compilation
  304. var join = defines.toString();
  305. var effect = engine.createEffect({
  306. vertex: "nodeMaterial" + this._buildId,
  307. fragment: "nodeMaterial" + this._buildId,
  308. vertexSource: this._vertexCompilationState.compilationString,
  309. fragmentSource: this._fragmentCompilationState.compilationString
  310. }, <EffectCreationOptions>{
  311. attributes: this._vertexCompilationState.attributes,
  312. uniformsNames: mergedUniforms,
  313. samplers: mergedSamplers,
  314. defines: join,
  315. fallbacks: fallbacks,
  316. onCompiled: this.onCompiled,
  317. onError: this.onError
  318. }, engine);
  319. if (effect) {
  320. // Use previous effect while new one is compiling
  321. if (this.allowShaderHotSwapping && previousEffect && !effect.isReady()) {
  322. effect = previousEffect;
  323. defines.markAsUnprocessed();
  324. } else {
  325. scene.resetCachedMaterial();
  326. subMesh.setEffect(effect, defines);
  327. }
  328. }
  329. }
  330. if (!subMesh.effect || !subMesh.effect.isReady()) {
  331. return false;
  332. }
  333. defines._renderId = scene.getRenderId();
  334. this._wasPreviouslyReady = true;
  335. return true;
  336. }
  337. /**
  338. * Binds the world matrix to the material
  339. * @param world defines the world transformation matrix
  340. */
  341. public bindOnlyWorldMatrix(world: Matrix): void {
  342. var scene = this.getScene();
  343. if (!this._activeEffect) {
  344. return;
  345. }
  346. let hints = this._sharedData.hints;
  347. if (hints.needWorldViewMatrix) {
  348. world.multiplyToRef(scene.getViewMatrix(), this._cachedWorldViewMatrix);
  349. }
  350. if (hints.needWorldViewProjectionMatrix) {
  351. world.multiplyToRef(scene.getTransformMatrix(), this._cachedWorldViewProjectionMatrix);
  352. }
  353. // Connection points
  354. for (var connectionPoint of this._sharedData.uniformConnectionPoints) {
  355. connectionPoint.transmitWorld(this._activeEffect, world, this._cachedWorldViewMatrix, this._cachedWorldViewProjectionMatrix);
  356. }
  357. }
  358. /**
  359. * Binds the submesh to this material by preparing the effect and shader to draw
  360. * @param world defines the world transformation matrix
  361. * @param mesh defines the mesh containing the submesh
  362. * @param subMesh defines the submesh to bind the material to
  363. */
  364. public bindForSubMesh(world: Matrix, mesh: Mesh, subMesh: SubMesh): void {
  365. let scene = this.getScene();
  366. var effect = subMesh.effect;
  367. if (!effect) {
  368. return;
  369. }
  370. this._activeEffect = effect;
  371. // Matrices
  372. this.bindOnlyWorldMatrix(world);
  373. let mustRebind = this._mustRebind(scene, effect, mesh.visibility);
  374. if (mustRebind) {
  375. let sharedData = this._sharedData;
  376. if (effect && scene.getCachedMaterial() !== this) {
  377. // Connection points
  378. for (var connectionPoint of sharedData.uniformConnectionPoints) {
  379. connectionPoint.transmit(effect, scene);
  380. }
  381. // Bindable blocks
  382. for (var block of sharedData.bindableBlocks) {
  383. block.bind(effect, mesh);
  384. }
  385. }
  386. }
  387. this._afterBind(mesh, this._activeEffect);
  388. }
  389. /**
  390. * Gets the active textures from the material
  391. * @returns an array of textures
  392. */
  393. public getActiveTextures(): BaseTexture[] {
  394. var activeTextures = super.getActiveTextures();
  395. for (var connectionPoint of this._textureConnectionPoints) {
  396. if (connectionPoint.value) {
  397. activeTextures.push(connectionPoint.value);
  398. }
  399. }
  400. return activeTextures;
  401. }
  402. /**
  403. * Specifies if the material uses a texture
  404. * @param texture defines the texture to check against the material
  405. * @returns a boolean specifying if the material uses the texture
  406. */
  407. public hasTexture(texture: BaseTexture): boolean {
  408. if (super.hasTexture(texture)) {
  409. return true;
  410. }
  411. for (var connectionPoint of this._textureConnectionPoints) {
  412. if (connectionPoint.value === texture) {
  413. return true;
  414. }
  415. }
  416. return false;
  417. }
  418. /**
  419. * Disposes the material
  420. * @param forceDisposeEffect specifies if effects should be forcefully disposed
  421. * @param forceDisposeTextures specifies if textures should be forcefully disposed
  422. * @param notBoundToMesh specifies if the material that is being disposed is known to be not bound to any mesh
  423. */
  424. public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean, notBoundToMesh?: boolean): void {
  425. if (forceDisposeTextures) {
  426. for (var connectionPoint of this._textureConnectionPoints) {
  427. if (connectionPoint.value) {
  428. (connectionPoint.value as BaseTexture).dispose();
  429. }
  430. }
  431. }
  432. this._textureConnectionPoints = [];
  433. this.onBuildObservable.clear();
  434. super.dispose(forceDisposeEffect, forceDisposeTextures, notBoundToMesh);
  435. }
  436. }