nodeMaterial.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. import { NodeMaterialBlock, NodeMaterialBlockTargets } from './nodeMaterialBlock';
  2. import { Material } from '../material';
  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 { NodeMaterialCompilationState, NodeMaterialCompilationStateSharedData } from './nodeMaterialCompilationState';
  9. import { EffectCreationOptions } 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. /**
  15. * Class used to configure NodeMaterial
  16. */
  17. export interface INodeMaterialOptions {
  18. /**
  19. * Defines if the material needs alpha blending
  20. */
  21. needAlphaBlending: boolean;
  22. /**
  23. * Defines if the material needs alpha testing
  24. */
  25. needAlphaTesting: boolean;
  26. /**
  27. * Defines if blocks should emit comments
  28. */
  29. emitComments: boolean;
  30. }
  31. /**
  32. * Class used to create a node based material built by assembling shader blocks
  33. */
  34. export class NodeMaterial extends Material {
  35. private _options: INodeMaterialOptions;
  36. private _vertexCompilationState: NodeMaterialCompilationState;
  37. private _fragmentCompilationState: NodeMaterialCompilationState;
  38. private _buildId: number = 0;
  39. private _renderId: number;
  40. private _effectCompileId: number = 0;
  41. private _cachedWorldViewMatrix = new Matrix();
  42. private _cachedWorldViewProjectionMatrix = new Matrix();
  43. private _textureConnectionPoints = new Array<NodeMaterialConnectionPoint>();
  44. /**
  45. * Observable raised when the material is built
  46. */
  47. public onBuildObservable = new Observable<NodeMaterial>();
  48. /**
  49. * Gets or sets the root nodes of the material vertex shader
  50. */
  51. private _vertexRootNodes = new Array<NodeMaterialBlock>();
  52. /**
  53. * Gets or sets the root nodes of the material fragment (pixel) shader
  54. */
  55. private _fragmentRootNodes = new Array<NodeMaterialBlock>();
  56. /** Gets or sets options to control the node material overall behavior */
  57. public get options() {
  58. return this._options;
  59. }
  60. public set options(options: INodeMaterialOptions) {
  61. this._options = options;
  62. }
  63. /**
  64. * Create a new node based material
  65. * @param name defines the material name
  66. * @param scene defines the hosting scene
  67. * @param options defines creation option
  68. */
  69. constructor(name: string, scene?: Scene, options: Partial<INodeMaterialOptions> = {}) {
  70. super(name, scene || Engine.LastCreatedScene!);
  71. this._options = {
  72. needAlphaBlending: false,
  73. needAlphaTesting: false,
  74. emitComments: false,
  75. ...options
  76. };
  77. }
  78. /**
  79. * Gets the current class name of the material e.g. "NodeMaterial"
  80. * @returns the class name
  81. */
  82. public getClassName(): string {
  83. return "NodeMaterial";
  84. }
  85. /**
  86. * Add a new block to the list of root nodes
  87. * @param node defines the node to add
  88. * @returns the current material
  89. */
  90. public addRootNode(node: NodeMaterialBlock) {
  91. if (node.target === null) {
  92. throw "This node is not meant to be at root level. You may want to explicitly set its target value.";
  93. }
  94. if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0 && node._canAddAtVertexRoot) {
  95. this._addVertexRootNode(node);
  96. }
  97. if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0 && node._canAddAtFragmentRoot) {
  98. this._addFragmentRootNode(node);
  99. }
  100. return this;
  101. }
  102. /**
  103. * Remove a block from the list of root nodes
  104. * @param node defines the node to remove
  105. * @returns the current material
  106. */
  107. public removeRootNode(node: NodeMaterialBlock) {
  108. if (node.target === null) {
  109. return this;
  110. }
  111. if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) {
  112. this._removeVertexRootNode(node);
  113. }
  114. if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) {
  115. this._removeFragmentRootNode(node);
  116. }
  117. return this;
  118. }
  119. private _addVertexRootNode(node: NodeMaterialBlock) {
  120. if (this._vertexRootNodes.indexOf(node) !== -1) {
  121. return;
  122. }
  123. node.target = NodeMaterialBlockTargets.Vertex;
  124. this._vertexRootNodes.push(node);
  125. return this;
  126. }
  127. private _removeVertexRootNode(node: NodeMaterialBlock) {
  128. let index = this._vertexRootNodes.indexOf(node);
  129. if (index === -1) {
  130. return;
  131. }
  132. this._vertexRootNodes.splice(index, 1);
  133. return this;
  134. }
  135. private _addFragmentRootNode(node: NodeMaterialBlock) {
  136. if (this._fragmentRootNodes.indexOf(node) !== -1) {
  137. return;
  138. }
  139. node.target = NodeMaterialBlockTargets.Fragment;
  140. this._fragmentRootNodes.push(node);
  141. return this;
  142. }
  143. private _removeFragmentRootNode(node: NodeMaterialBlock) {
  144. let index = this._fragmentRootNodes.indexOf(node);
  145. if (index === -1) {
  146. return;
  147. }
  148. this._fragmentRootNodes.splice(index, 1);
  149. return this;
  150. }
  151. /**
  152. * Specifies if the material will require alpha blending
  153. * @returns a boolean specifying if alpha blending is needed
  154. */
  155. public needAlphaBlending(): boolean {
  156. return (this.alpha < 1.0) || this._options.needAlphaBlending;
  157. }
  158. /**
  159. * Specifies if this material should be rendered in alpha test mode
  160. * @returns a boolean specifying if an alpha test is needed.
  161. */
  162. public needAlphaTesting(): boolean {
  163. return this._options.needAlphaTesting;
  164. }
  165. private _propagateTarget(node: NodeMaterialBlock, target: NodeMaterialBlockTargets) {
  166. node.target = target;
  167. for (var exitPoint of node.outputs) {
  168. for (var block of exitPoint.connectedBlocks) {
  169. if (block) {
  170. this._propagateTarget(block, target);
  171. }
  172. }
  173. }
  174. }
  175. private _resetDualBlocks(node: NodeMaterialBlock, id: number) {
  176. if (node.target === NodeMaterialBlockTargets.VertexAndFragment) {
  177. node.buildId = id;
  178. }
  179. for (var exitPoint of node.outputs) {
  180. for (var block of exitPoint.connectedBlocks) {
  181. if (block) {
  182. this._resetDualBlocks(block, id);
  183. }
  184. }
  185. }
  186. }
  187. /**
  188. * Build the material and generates the inner effect
  189. */
  190. public build() {
  191. if (this._vertexRootNodes.length === 0) {
  192. throw "You must define at least one vertexRootNode";
  193. }
  194. if (this._fragmentRootNodes.length === 0) {
  195. throw "You must define at least one fragmentRootNode";
  196. }
  197. // Go through the nodes and do some magic :)
  198. // Needs to create the code and deduce samplers and uniforms in order to populate some lists used during bindings
  199. // Propagate targets
  200. for (var vertexRootNode of this._vertexRootNodes) {
  201. this._propagateTarget(vertexRootNode, NodeMaterialBlockTargets.Vertex);
  202. }
  203. for (var fragmentRootNode of this._fragmentRootNodes) {
  204. this._propagateTarget(fragmentRootNode, NodeMaterialBlockTargets.Fragment);
  205. }
  206. // Vertex
  207. this._vertexCompilationState = new NodeMaterialCompilationState();
  208. this._vertexCompilationState.target = NodeMaterialBlockTargets.Vertex;
  209. this._fragmentCompilationState = new NodeMaterialCompilationState();
  210. let sharedData = new NodeMaterialCompilationStateSharedData();
  211. this._vertexCompilationState.sharedData = sharedData;
  212. this._fragmentCompilationState.sharedData = sharedData;
  213. sharedData.buildId = this._buildId;
  214. sharedData.emitComments = this._options.emitComments;
  215. for (var vertexRootNode of this._vertexRootNodes) {
  216. vertexRootNode.build(this._vertexCompilationState);
  217. }
  218. // Fragment
  219. this._fragmentCompilationState.target = NodeMaterialBlockTargets.Fragment;
  220. this._fragmentCompilationState._vertexState = this._vertexCompilationState;
  221. this._fragmentCompilationState.hints = this._vertexCompilationState.hints;
  222. this._fragmentCompilationState._uniformConnectionPoints = this._vertexCompilationState._uniformConnectionPoints;
  223. for (var fragmentRootNode of this._fragmentRootNodes) {
  224. this._resetDualBlocks(fragmentRootNode, this._buildId - 1);
  225. }
  226. for (var fragmentRootNode of this._fragmentRootNodes) {
  227. fragmentRootNode.build(this._fragmentCompilationState);
  228. }
  229. // Finalize
  230. this._vertexCompilationState.finalize(this._vertexCompilationState);
  231. this._fragmentCompilationState.finalize(this._fragmentCompilationState);
  232. // Textures
  233. this._textureConnectionPoints = this._fragmentCompilationState._uniformConnectionPoints.filter((u) => u.type === NodeMaterialBlockConnectionPointTypes.Texture);
  234. this._buildId++;
  235. this.onBuildObservable.notifyObservers(this);
  236. }
  237. /**
  238. * Checks if the material is ready to render the requested mesh
  239. * @param mesh defines the mesh to render
  240. * @param useInstances defines whether or not the material is used with instances
  241. * @returns true if ready, otherwise false
  242. */
  243. public isReady(mesh?: AbstractMesh, useInstances?: boolean): boolean {
  244. var scene = this.getScene();
  245. var engine = scene.getEngine();
  246. if (!this.checkReadyOnEveryCall) {
  247. if (this._renderId === scene.getRenderId()) {
  248. return true;
  249. }
  250. }
  251. // Textures
  252. for (var connectionPoint of this._textureConnectionPoints) {
  253. let texture = connectionPoint.value as BaseTexture;
  254. if (texture && !texture.isReady()) {
  255. return false;
  256. }
  257. }
  258. this._renderId = scene.getRenderId();
  259. if (this._effectCompileId === this._buildId) {
  260. return true;
  261. }
  262. var previousEffect = this._effect;
  263. // Uniforms
  264. let mergedUniforms = this._vertexCompilationState.uniforms;
  265. this._fragmentCompilationState.uniforms.forEach((u) => {
  266. let index = mergedUniforms.indexOf(u);
  267. if (index === -1) {
  268. mergedUniforms.push(u);
  269. }
  270. });
  271. // Samplers
  272. let mergedSamplers = this._vertexCompilationState.samplers;
  273. this._fragmentCompilationState.samplers.forEach((s) => {
  274. let index = mergedSamplers.indexOf(s);
  275. if (index === -1) {
  276. mergedSamplers.push(s);
  277. }
  278. });
  279. // Compilation
  280. this._effect = engine.createEffect({
  281. vertex: "nodeMaterial" + this._buildId,
  282. fragment: "nodeMaterial" + this._buildId,
  283. vertexSource: this._vertexCompilationState.compilationString,
  284. fragmentSource: this._fragmentCompilationState.compilationString
  285. }, <EffectCreationOptions>{
  286. attributes: this._vertexCompilationState.attributes,
  287. uniformsNames: mergedUniforms,
  288. samplers: this._fragmentCompilationState.samplers,
  289. defines: "",
  290. onCompiled: this.onCompiled,
  291. onError: this.onError
  292. }, engine);
  293. if (!this._effect.isReady()) {
  294. return false;
  295. }
  296. if (previousEffect !== this._effect) {
  297. scene.resetCachedMaterial();
  298. }
  299. this._effectCompileId = this._buildId;
  300. return true;
  301. }
  302. /**
  303. * Binds the world matrix to the material
  304. * @param world defines the world transformation matrix
  305. */
  306. public bindOnlyWorldMatrix(world: Matrix): void {
  307. var scene = this.getScene();
  308. if (!this._effect) {
  309. return;
  310. }
  311. let hints = this._fragmentCompilationState.hints;
  312. if (hints.needWorldMatrix) {
  313. this._effect.setMatrix("world", world);
  314. }
  315. if (hints.needWorldViewMatrix) {
  316. world.multiplyToRef(scene.getViewMatrix(), this._cachedWorldViewMatrix);
  317. this._effect.setMatrix("worldView", this._cachedWorldViewMatrix);
  318. }
  319. if (hints.needWorldViewProjectionMatrix) {
  320. world.multiplyToRef(scene.getTransformMatrix(), this._cachedWorldViewProjectionMatrix);
  321. this._effect.setMatrix("worldViewProjection", this._cachedWorldViewProjectionMatrix);
  322. }
  323. }
  324. /**
  325. * Binds the material to the mesh
  326. * @param world defines the world transformation matrix
  327. * @param mesh defines the mesh to bind the material to
  328. */
  329. public bind(world: Matrix, mesh?: Mesh): void {
  330. let scene = this.getScene();
  331. // Std values
  332. this.bindOnlyWorldMatrix(world);
  333. if (this._effect && scene.getCachedMaterial() !== this) {
  334. let hints = this._fragmentCompilationState.hints;
  335. if (hints.needViewMatrix) {
  336. this._effect.setMatrix("view", scene.getViewMatrix());
  337. }
  338. if (hints.needProjectionMatrix) {
  339. this._effect.setMatrix("projection", scene.getProjectionMatrix());
  340. }
  341. if (hints.needViewProjectionMatrix) {
  342. this._effect.setMatrix("viewProjection", scene.getTransformMatrix());
  343. }
  344. if (hints.needFogColor) {
  345. this._effect.setColor3("fogColor", scene.fogColor);
  346. }
  347. if (hints.needFogParameters) {
  348. this._effect.setFloat4("fogParameters", scene.fogMode, scene.fogStart, scene.fogEnd, scene.fogDensity);
  349. }
  350. for (var connectionPoint of this._fragmentCompilationState._uniformConnectionPoints) {
  351. connectionPoint.transmit(this._effect);
  352. }
  353. }
  354. this._afterBind(mesh);
  355. }
  356. /**
  357. * Gets the active textures from the material
  358. * @returns an array of textures
  359. */
  360. public getActiveTextures(): BaseTexture[] {
  361. var activeTextures = super.getActiveTextures();
  362. for (var connectionPoint of this._textureConnectionPoints) {
  363. if (connectionPoint.value) {
  364. activeTextures.push(connectionPoint.value);
  365. }
  366. }
  367. return activeTextures;
  368. }
  369. /**
  370. * Specifies if the material uses a texture
  371. * @param texture defines the texture to check against the material
  372. * @returns a boolean specifying if the material uses the texture
  373. */
  374. public hasTexture(texture: BaseTexture): boolean {
  375. if (super.hasTexture(texture)) {
  376. return true;
  377. }
  378. for (var connectionPoint of this._textureConnectionPoints) {
  379. if (connectionPoint.value === texture) {
  380. return true;
  381. }
  382. }
  383. return false;
  384. }
  385. /**
  386. * Disposes the material
  387. * @param forceDisposeEffect specifies if effects should be forcefully disposed
  388. * @param forceDisposeTextures specifies if textures should be forcefully disposed
  389. * @param notBoundToMesh specifies if the material that is being disposed is known to be not bound to any mesh
  390. */
  391. public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean, notBoundToMesh?: boolean): void {
  392. if (forceDisposeTextures) {
  393. for (var connectionPoint of this._textureConnectionPoints) {
  394. if (connectionPoint.value) {
  395. (connectionPoint.value as BaseTexture).dispose();
  396. }
  397. }
  398. }
  399. this._textureConnectionPoints = [];
  400. this.onBuildObservable.clear();
  401. super.dispose(forceDisposeEffect, forceDisposeTextures, notBoundToMesh);
  402. }
  403. }