nodeMaterial.ts 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763
  1. import { NodeMaterialBlock } from './nodeMaterialBlock';
  2. import { PushMaterial } from '../pushMaterial';
  3. import { Scene } from '../../scene';
  4. import { AbstractMesh } from '../../Meshes/abstractMesh';
  5. import { Matrix, Vector2 } from '../../Maths/math.vector';
  6. import { Color4 } from '../../Maths/math.color';
  7. import { Mesh } from '../../Meshes/mesh';
  8. import { Engine } from '../../Engines/engine';
  9. import { NodeMaterialBuildState } from './nodeMaterialBuildState';
  10. import { IEffectCreationOptions } from '../effect';
  11. import { BaseTexture } from '../../Materials/Textures/baseTexture';
  12. import { Observable, Observer } from '../../Misc/observable';
  13. import { NodeMaterialBlockTargets } from './Enums/nodeMaterialBlockTargets';
  14. import { NodeMaterialBuildStateSharedData } from './nodeMaterialBuildStateSharedData';
  15. import { SubMesh } from '../../Meshes/subMesh';
  16. import { MaterialDefines } from '../../Materials/materialDefines';
  17. import { NodeMaterialOptimizer } from './Optimizers/nodeMaterialOptimizer';
  18. import { ImageProcessingConfiguration, IImageProcessingConfigurationDefines } from '../imageProcessingConfiguration';
  19. import { Nullable } from '../../types';
  20. import { VertexBuffer } from '../../Meshes/buffer';
  21. import { Tools } from '../../Misc/tools';
  22. import { TransformBlock } from './Blocks/transformBlock';
  23. import { VertexOutputBlock } from './Blocks/Vertex/vertexOutputBlock';
  24. import { FragmentOutputBlock } from './Blocks/Fragment/fragmentOutputBlock';
  25. import { InputBlock } from './Blocks/Input/inputBlock';
  26. import { _TypeStore } from '../../Misc/typeStore';
  27. import { serialize, SerializationHelper } from '../../Misc/decorators';
  28. import { TextureBlock } from './Blocks/Dual/textureBlock';
  29. import { ReflectionTextureBaseBlock } from './Blocks/Dual/reflectionTextureBaseBlock';
  30. import { RefractionBlock } from './Blocks/PBR/refractionBlock';
  31. import { CurrentScreenBlock } from './Blocks/Dual/currentScreenBlock';
  32. import { ParticleTextureBlock } from './Blocks/Particle/particleTextureBlock';
  33. import { ParticleRampGradientBlock } from './Blocks/Particle/particleRampGradientBlock';
  34. import { ParticleBlendMultiplyBlock } from './Blocks/Particle/particleBlendMultiplyBlock';
  35. import { EffectFallbacks } from '../effectFallbacks';
  36. import { WebRequest } from '../../Misc/webRequest';
  37. import { Effect } from '../effect';
  38. import { PostProcess, PostProcessOptions } from '../../PostProcesses/postProcess';
  39. import { Constants } from '../../Engines/constants';
  40. import { Camera } from '../../Cameras/camera';
  41. import { VectorMergerBlock } from './Blocks/vectorMergerBlock';
  42. import { RemapBlock } from './Blocks/remapBlock';
  43. import { MultiplyBlock } from './Blocks/multiplyBlock';
  44. import { NodeMaterialModes } from './Enums/nodeMaterialModes';
  45. import { Texture } from '../Textures/texture';
  46. import { ParticleSystem } from '../../Particles/particleSystem';
  47. import { BaseParticleSystem } from '../../Particles/baseParticleSystem';
  48. import { ColorSplitterBlock } from './Blocks/colorSplitterBlock';
  49. const onCreatedEffectParameters = { effect: null as unknown as Effect, subMesh: null as unknown as Nullable<SubMesh> };
  50. // declare NODEEDITOR namespace for compilation issue
  51. declare var NODEEDITOR: any;
  52. declare var BABYLON: any;
  53. /**
  54. * Interface used to configure the node material editor
  55. */
  56. export interface INodeMaterialEditorOptions {
  57. /** Define the URl to load node editor script */
  58. editorURL?: string;
  59. }
  60. /** @hidden */
  61. export class NodeMaterialDefines extends MaterialDefines implements IImageProcessingConfigurationDefines {
  62. public NORMAL = false;
  63. public TANGENT = false;
  64. public UV1 = false;
  65. /** BONES */
  66. public NUM_BONE_INFLUENCERS = 0;
  67. public BonesPerMesh = 0;
  68. public BONETEXTURE = false;
  69. /** MORPH TARGETS */
  70. public MORPHTARGETS = false;
  71. public MORPHTARGETS_NORMAL = false;
  72. public MORPHTARGETS_TANGENT = false;
  73. public MORPHTARGETS_UV = false;
  74. public NUM_MORPH_INFLUENCERS = 0;
  75. /** IMAGE PROCESSING */
  76. public IMAGEPROCESSING = false;
  77. public VIGNETTE = false;
  78. public VIGNETTEBLENDMODEMULTIPLY = false;
  79. public VIGNETTEBLENDMODEOPAQUE = false;
  80. public TONEMAPPING = false;
  81. public TONEMAPPING_ACES = false;
  82. public CONTRAST = false;
  83. public EXPOSURE = false;
  84. public COLORCURVES = false;
  85. public COLORGRADING = false;
  86. public COLORGRADING3D = false;
  87. public SAMPLER3DGREENDEPTH = false;
  88. public SAMPLER3DBGRMAP = false;
  89. public IMAGEPROCESSINGPOSTPROCESS = false;
  90. /** MISC. */
  91. public BUMPDIRECTUV = 0;
  92. constructor() {
  93. super();
  94. this.rebuild();
  95. }
  96. public setValue(name: string, value: any, markAsUnprocessedIfDirty = false) {
  97. if (this[name] === undefined) {
  98. this._keys.push(name);
  99. }
  100. if (markAsUnprocessedIfDirty && this[name] !== value) {
  101. this.markAsUnprocessed();
  102. }
  103. this[name] = value;
  104. }
  105. }
  106. /**
  107. * Class used to configure NodeMaterial
  108. */
  109. export interface INodeMaterialOptions {
  110. /**
  111. * Defines if blocks should emit comments
  112. */
  113. emitComments: boolean;
  114. }
  115. /**
  116. * Class used to create a node based material built by assembling shader blocks
  117. */
  118. export class NodeMaterial extends PushMaterial {
  119. private static _BuildIdGenerator: number = 0;
  120. private _options: INodeMaterialOptions;
  121. private _vertexCompilationState: NodeMaterialBuildState;
  122. private _fragmentCompilationState: NodeMaterialBuildState;
  123. private _sharedData: NodeMaterialBuildStateSharedData;
  124. private _buildId: number = NodeMaterial._BuildIdGenerator++;
  125. private _buildWasSuccessful = false;
  126. private _cachedWorldViewMatrix = new Matrix();
  127. private _cachedWorldViewProjectionMatrix = new Matrix();
  128. private _optimizers = new Array<NodeMaterialOptimizer>();
  129. private _animationFrame = -1;
  130. /** Define the Url to load node editor script */
  131. public static EditorURL = `https://unpkg.com/babylonjs-node-editor@${Engine.Version}/babylon.nodeEditor.js`;
  132. /** Define the Url to load snippets */
  133. public static SnippetUrl = "https://snippet.babylonjs.com";
  134. /** Gets or sets a boolean indicating that node materials should not deserialize textures from json / snippet content */
  135. public static IgnoreTexturesAtLoadTime = false;
  136. private BJSNODEMATERIALEDITOR = this._getGlobalNodeMaterialEditor();
  137. /** Get the inspector from bundle or global */
  138. private _getGlobalNodeMaterialEditor(): any {
  139. // UMD Global name detection from Webpack Bundle UMD Name.
  140. if (typeof NODEEDITOR !== 'undefined') {
  141. return NODEEDITOR;
  142. }
  143. // In case of module let's check the global emitted from the editor entry point.
  144. if (typeof BABYLON !== 'undefined' && typeof BABYLON.NodeEditor !== 'undefined') {
  145. return BABYLON;
  146. }
  147. return undefined;
  148. }
  149. /**
  150. * Snippet ID if the material was created from the snippet server
  151. */
  152. public snippetId: string;
  153. /**
  154. * Gets or sets data used by visual editor
  155. * @see https://nme.babylonjs.com
  156. */
  157. public editorData: any = null;
  158. /**
  159. * Gets or sets a boolean indicating that alpha value must be ignored (This will turn alpha blending off even if an alpha value is produced by the material)
  160. */
  161. public ignoreAlpha = false;
  162. /**
  163. * Defines the maximum number of lights that can be used in the material
  164. */
  165. public maxSimultaneousLights = 4;
  166. /**
  167. * Observable raised when the material is built
  168. */
  169. public onBuildObservable = new Observable<NodeMaterial>();
  170. /**
  171. * Gets or sets the root nodes of the material vertex shader
  172. */
  173. public _vertexOutputNodes = new Array<NodeMaterialBlock>();
  174. /**
  175. * Gets or sets the root nodes of the material fragment (pixel) shader
  176. */
  177. public _fragmentOutputNodes = new Array<NodeMaterialBlock>();
  178. /** Gets or sets options to control the node material overall behavior */
  179. public get options() {
  180. return this._options;
  181. }
  182. public set options(options: INodeMaterialOptions) {
  183. this._options = options;
  184. }
  185. /**
  186. * Default configuration related to image processing available in the standard Material.
  187. */
  188. protected _imageProcessingConfiguration: ImageProcessingConfiguration;
  189. /**
  190. * Gets the image processing configuration used either in this material.
  191. */
  192. public get imageProcessingConfiguration(): ImageProcessingConfiguration {
  193. return this._imageProcessingConfiguration;
  194. }
  195. /**
  196. * Sets the Default image processing configuration used either in the this material.
  197. *
  198. * If sets to null, the scene one is in use.
  199. */
  200. public set imageProcessingConfiguration(value: ImageProcessingConfiguration) {
  201. this._attachImageProcessingConfiguration(value);
  202. // Ensure the effect will be rebuilt.
  203. this._markAllSubMeshesAsTexturesDirty();
  204. }
  205. /**
  206. * Gets an array of blocks that needs to be serialized even if they are not yet connected
  207. */
  208. public attachedBlocks = new Array<NodeMaterialBlock>();
  209. /**
  210. * Specifies the mode of the node material
  211. * @hidden
  212. */
  213. @serialize("mode")
  214. public _mode: NodeMaterialModes = NodeMaterialModes.Material;
  215. /**
  216. * Gets the mode property
  217. */
  218. public get mode(): NodeMaterialModes {
  219. return this._mode;
  220. }
  221. /**
  222. * Create a new node based material
  223. * @param name defines the material name
  224. * @param scene defines the hosting scene
  225. * @param options defines creation option
  226. */
  227. constructor(name: string, scene?: Scene, options: Partial<INodeMaterialOptions> = {}) {
  228. super(name, scene || Engine.LastCreatedScene!);
  229. this._options = {
  230. emitComments: false,
  231. ...options
  232. };
  233. // Setup the default processing configuration to the scene.
  234. this._attachImageProcessingConfiguration(null);
  235. }
  236. /**
  237. * Gets the current class name of the material e.g. "NodeMaterial"
  238. * @returns the class name
  239. */
  240. public getClassName(): string {
  241. return "NodeMaterial";
  242. }
  243. /**
  244. * Keep track of the image processing observer to allow dispose and replace.
  245. */
  246. private _imageProcessingObserver: Nullable<Observer<ImageProcessingConfiguration>>;
  247. /**
  248. * Attaches a new image processing configuration to the Standard Material.
  249. * @param configuration
  250. */
  251. protected _attachImageProcessingConfiguration(configuration: Nullable<ImageProcessingConfiguration>): void {
  252. if (configuration === this._imageProcessingConfiguration) {
  253. return;
  254. }
  255. // Detaches observer.
  256. if (this._imageProcessingConfiguration && this._imageProcessingObserver) {
  257. this._imageProcessingConfiguration.onUpdateParameters.remove(this._imageProcessingObserver);
  258. }
  259. // Pick the scene configuration if needed.
  260. if (!configuration) {
  261. this._imageProcessingConfiguration = this.getScene().imageProcessingConfiguration;
  262. }
  263. else {
  264. this._imageProcessingConfiguration = configuration;
  265. }
  266. // Attaches observer.
  267. if (this._imageProcessingConfiguration) {
  268. this._imageProcessingObserver = this._imageProcessingConfiguration.onUpdateParameters.add(() => {
  269. this._markAllSubMeshesAsImageProcessingDirty();
  270. });
  271. }
  272. }
  273. /**
  274. * Get a block by its name
  275. * @param name defines the name of the block to retrieve
  276. * @returns the required block or null if not found
  277. */
  278. public getBlockByName(name: string) {
  279. let result = null;
  280. for (var block of this.attachedBlocks) {
  281. if (block.name === name) {
  282. if (!result) {
  283. result = block;
  284. } else {
  285. Tools.Warn("More than one block was found with the name `" + name + "`");
  286. return result;
  287. }
  288. }
  289. }
  290. return result;
  291. }
  292. /**
  293. * Get a block by its name
  294. * @param predicate defines the predicate used to find the good candidate
  295. * @returns the required block or null if not found
  296. */
  297. public getBlockByPredicate(predicate: (block: NodeMaterialBlock) => boolean) {
  298. for (var block of this.attachedBlocks) {
  299. if (predicate(block)) {
  300. return block;
  301. }
  302. }
  303. return null;
  304. }
  305. /**
  306. * Get an input block by its name
  307. * @param predicate defines the predicate used to find the good candidate
  308. * @returns the required input block or null if not found
  309. */
  310. public getInputBlockByPredicate(predicate: (block: InputBlock) => boolean): Nullable<InputBlock> {
  311. for (var block of this.attachedBlocks) {
  312. if (block.isInput && predicate(block as InputBlock)) {
  313. return block as InputBlock;
  314. }
  315. }
  316. return null;
  317. }
  318. /**
  319. * Gets the list of input blocks attached to this material
  320. * @returns an array of InputBlocks
  321. */
  322. public getInputBlocks() {
  323. let blocks: InputBlock[] = [];
  324. for (var block of this.attachedBlocks) {
  325. if (block.isInput) {
  326. blocks.push(block as InputBlock);
  327. }
  328. }
  329. return blocks;
  330. }
  331. /**
  332. * Adds a new optimizer to the list of optimizers
  333. * @param optimizer defines the optimizers to add
  334. * @returns the current material
  335. */
  336. public registerOptimizer(optimizer: NodeMaterialOptimizer) {
  337. let index = this._optimizers.indexOf(optimizer);
  338. if (index > -1) {
  339. return;
  340. }
  341. this._optimizers.push(optimizer);
  342. return this;
  343. }
  344. /**
  345. * Remove an optimizer from the list of optimizers
  346. * @param optimizer defines the optimizers to remove
  347. * @returns the current material
  348. */
  349. public unregisterOptimizer(optimizer: NodeMaterialOptimizer) {
  350. let index = this._optimizers.indexOf(optimizer);
  351. if (index === -1) {
  352. return;
  353. }
  354. this._optimizers.splice(index, 1);
  355. return this;
  356. }
  357. /**
  358. * Add a new block to the list of output nodes
  359. * @param node defines the node to add
  360. * @returns the current material
  361. */
  362. public addOutputNode(node: NodeMaterialBlock) {
  363. if (node.target === null) {
  364. throw "This node is not meant to be an output node. You may want to explicitly set its target value.";
  365. }
  366. if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) {
  367. this._addVertexOutputNode(node);
  368. }
  369. if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) {
  370. this._addFragmentOutputNode(node);
  371. }
  372. return this;
  373. }
  374. /**
  375. * Remove a block from the list of root nodes
  376. * @param node defines the node to remove
  377. * @returns the current material
  378. */
  379. public removeOutputNode(node: NodeMaterialBlock) {
  380. if (node.target === null) {
  381. return this;
  382. }
  383. if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) {
  384. this._removeVertexOutputNode(node);
  385. }
  386. if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) {
  387. this._removeFragmentOutputNode(node);
  388. }
  389. return this;
  390. }
  391. private _addVertexOutputNode(node: NodeMaterialBlock) {
  392. if (this._vertexOutputNodes.indexOf(node) !== -1) {
  393. return;
  394. }
  395. node.target = NodeMaterialBlockTargets.Vertex;
  396. this._vertexOutputNodes.push(node);
  397. return this;
  398. }
  399. private _removeVertexOutputNode(node: NodeMaterialBlock) {
  400. let index = this._vertexOutputNodes.indexOf(node);
  401. if (index === -1) {
  402. return;
  403. }
  404. this._vertexOutputNodes.splice(index, 1);
  405. return this;
  406. }
  407. private _addFragmentOutputNode(node: NodeMaterialBlock) {
  408. if (this._fragmentOutputNodes.indexOf(node) !== -1) {
  409. return;
  410. }
  411. node.target = NodeMaterialBlockTargets.Fragment;
  412. this._fragmentOutputNodes.push(node);
  413. return this;
  414. }
  415. private _removeFragmentOutputNode(node: NodeMaterialBlock) {
  416. let index = this._fragmentOutputNodes.indexOf(node);
  417. if (index === -1) {
  418. return;
  419. }
  420. this._fragmentOutputNodes.splice(index, 1);
  421. return this;
  422. }
  423. /**
  424. * Specifies if the material will require alpha blending
  425. * @returns a boolean specifying if alpha blending is needed
  426. */
  427. public needAlphaBlending(): boolean {
  428. if (this.ignoreAlpha) {
  429. return false;
  430. }
  431. return (this.alpha < 1.0) || (this._sharedData && this._sharedData.hints.needAlphaBlending);
  432. }
  433. /**
  434. * Specifies if this material should be rendered in alpha test mode
  435. * @returns a boolean specifying if an alpha test is needed.
  436. */
  437. public needAlphaTesting(): boolean {
  438. return this._sharedData && this._sharedData.hints.needAlphaTesting;
  439. }
  440. private _initializeBlock(node: NodeMaterialBlock, state: NodeMaterialBuildState, nodesToProcessForOtherBuildState: NodeMaterialBlock[]) {
  441. node.initialize(state);
  442. node.autoConfigure(this);
  443. node._preparationId = this._buildId;
  444. if (this.attachedBlocks.indexOf(node) === -1) {
  445. if (node.isUnique) {
  446. const className = node.getClassName();
  447. for (var other of this.attachedBlocks) {
  448. if (other.getClassName() === className) {
  449. throw `Cannot have multiple blocks of type ${className} in the same NodeMaterial`;
  450. }
  451. }
  452. }
  453. this.attachedBlocks.push(node);
  454. }
  455. for (var input of node.inputs) {
  456. input.associatedVariableName = "";
  457. let connectedPoint = input.connectedPoint;
  458. if (connectedPoint) {
  459. let block = connectedPoint.ownerBlock;
  460. if (block !== node) {
  461. if (block.target === NodeMaterialBlockTargets.VertexAndFragment) {
  462. nodesToProcessForOtherBuildState.push(block);
  463. } else if (state.target === NodeMaterialBlockTargets.Fragment
  464. && block.target === NodeMaterialBlockTargets.Vertex
  465. && block._preparationId !== this._buildId) {
  466. nodesToProcessForOtherBuildState.push(block);
  467. }
  468. this._initializeBlock(block, state, nodesToProcessForOtherBuildState);
  469. }
  470. }
  471. }
  472. for (var output of node.outputs) {
  473. output.associatedVariableName = "";
  474. }
  475. }
  476. private _resetDualBlocks(node: NodeMaterialBlock, id: number) {
  477. if (node.target === NodeMaterialBlockTargets.VertexAndFragment) {
  478. node.buildId = id;
  479. }
  480. for (var inputs of node.inputs) {
  481. let connectedPoint = inputs.connectedPoint;
  482. if (connectedPoint) {
  483. let block = connectedPoint.ownerBlock;
  484. if (block !== node) {
  485. this._resetDualBlocks(block, id);
  486. }
  487. }
  488. }
  489. }
  490. /**
  491. * Remove a block from the current node material
  492. * @param block defines the block to remove
  493. */
  494. public removeBlock(block: NodeMaterialBlock) {
  495. let attachedBlockIndex = this.attachedBlocks.indexOf(block);
  496. if (attachedBlockIndex > -1) {
  497. this.attachedBlocks.splice(attachedBlockIndex, 1);
  498. }
  499. if (block.isFinalMerger) {
  500. this.removeOutputNode(block);
  501. }
  502. }
  503. /**
  504. * Build the material and generates the inner effect
  505. * @param verbose defines if the build should log activity
  506. */
  507. public build(verbose: boolean = false) {
  508. this._buildWasSuccessful = false;
  509. var engine = this.getScene().getEngine();
  510. const allowEmptyVertexProgram = this._mode === NodeMaterialModes.Particle;
  511. if (this._vertexOutputNodes.length === 0 && !allowEmptyVertexProgram) {
  512. throw "You must define at least one vertexOutputNode";
  513. }
  514. if (this._fragmentOutputNodes.length === 0) {
  515. throw "You must define at least one fragmentOutputNode";
  516. }
  517. // Compilation state
  518. this._vertexCompilationState = new NodeMaterialBuildState();
  519. this._vertexCompilationState.supportUniformBuffers = engine.supportsUniformBuffers;
  520. this._vertexCompilationState.target = NodeMaterialBlockTargets.Vertex;
  521. this._fragmentCompilationState = new NodeMaterialBuildState();
  522. this._fragmentCompilationState.supportUniformBuffers = engine.supportsUniformBuffers;
  523. this._fragmentCompilationState.target = NodeMaterialBlockTargets.Fragment;
  524. // Shared data
  525. this._sharedData = new NodeMaterialBuildStateSharedData();
  526. this._vertexCompilationState.sharedData = this._sharedData;
  527. this._fragmentCompilationState.sharedData = this._sharedData;
  528. this._sharedData.buildId = this._buildId;
  529. this._sharedData.emitComments = this._options.emitComments;
  530. this._sharedData.verbose = verbose;
  531. this._sharedData.scene = this.getScene();
  532. this._sharedData.allowEmptyVertexProgram = allowEmptyVertexProgram;
  533. // Initialize blocks
  534. let vertexNodes: NodeMaterialBlock[] = [];
  535. let fragmentNodes: NodeMaterialBlock[] = [];
  536. for (var vertexOutputNode of this._vertexOutputNodes) {
  537. vertexNodes.push(vertexOutputNode);
  538. this._initializeBlock(vertexOutputNode, this._vertexCompilationState, fragmentNodes);
  539. }
  540. for (var fragmentOutputNode of this._fragmentOutputNodes) {
  541. fragmentNodes.push(fragmentOutputNode);
  542. this._initializeBlock(fragmentOutputNode, this._fragmentCompilationState, vertexNodes);
  543. }
  544. // Optimize
  545. this.optimize();
  546. // Vertex
  547. for (var vertexOutputNode of vertexNodes) {
  548. vertexOutputNode.build(this._vertexCompilationState, vertexNodes);
  549. }
  550. // Fragment
  551. this._fragmentCompilationState.uniforms = this._vertexCompilationState.uniforms.slice(0);
  552. this._fragmentCompilationState._uniformDeclaration = this._vertexCompilationState._uniformDeclaration;
  553. this._fragmentCompilationState._constantDeclaration = this._vertexCompilationState._constantDeclaration;
  554. this._fragmentCompilationState._vertexState = this._vertexCompilationState;
  555. for (var fragmentOutputNode of fragmentNodes) {
  556. this._resetDualBlocks(fragmentOutputNode, this._buildId - 1);
  557. }
  558. for (var fragmentOutputNode of fragmentNodes) {
  559. fragmentOutputNode.build(this._fragmentCompilationState, fragmentNodes);
  560. }
  561. // Finalize
  562. this._vertexCompilationState.finalize(this._vertexCompilationState);
  563. this._fragmentCompilationState.finalize(this._fragmentCompilationState);
  564. this._buildId = NodeMaterial._BuildIdGenerator++;
  565. // Errors
  566. this._sharedData.emitErrors();
  567. if (verbose) {
  568. console.log("Vertex shader:");
  569. console.log(this._vertexCompilationState.compilationString);
  570. console.log("Fragment shader:");
  571. console.log(this._fragmentCompilationState.compilationString);
  572. }
  573. this._buildWasSuccessful = true;
  574. this.onBuildObservable.notifyObservers(this);
  575. // Wipe defines
  576. const meshes = this.getScene().meshes;
  577. for (var mesh of meshes) {
  578. if (!mesh.subMeshes) {
  579. continue;
  580. }
  581. for (var subMesh of mesh.subMeshes) {
  582. if (subMesh.getMaterial() !== this) {
  583. continue;
  584. }
  585. if (!subMesh._materialDefines) {
  586. continue;
  587. }
  588. let defines = subMesh._materialDefines;
  589. defines.markAllAsDirty();
  590. defines.reset();
  591. }
  592. }
  593. }
  594. /**
  595. * Runs an otpimization phase to try to improve the shader code
  596. */
  597. public optimize() {
  598. for (var optimizer of this._optimizers) {
  599. optimizer.optimize(this._vertexOutputNodes, this._fragmentOutputNodes);
  600. }
  601. }
  602. private _prepareDefinesForAttributes(mesh: AbstractMesh, defines: NodeMaterialDefines) {
  603. let oldNormal = defines["NORMAL"];
  604. let oldTangent = defines["TANGENT"];
  605. let oldUV1 = defines["UV1"];
  606. defines["NORMAL"] = mesh.isVerticesDataPresent(VertexBuffer.NormalKind);
  607. defines["TANGENT"] = mesh.isVerticesDataPresent(VertexBuffer.TangentKind);
  608. defines["UV1"] = mesh.isVerticesDataPresent(VertexBuffer.UVKind);
  609. if (oldNormal !== defines["NORMAL"] || oldTangent !== defines["TANGENT"] || oldUV1 !== defines["UV1"]) {
  610. defines.markAsAttributesDirty();
  611. }
  612. }
  613. /**
  614. * Create a post process from the material
  615. * @param camera The camera to apply the render pass to.
  616. * @param options The required width/height ratio to downsize to before computing the render pass. (Use 1.0 for full size)
  617. * @param samplingMode The sampling mode to be used when computing the pass. (default: 0)
  618. * @param engine The engine which the post process will be applied. (default: current engine)
  619. * @param reusable If the post process can be reused on the same frame. (default: false)
  620. * @param textureType Type of textures used when performing the post process. (default: 0)
  621. * @param textureFormat Format of textures used when performing the post process. (default: TEXTUREFORMAT_RGBA)
  622. * @returns the post process created
  623. */
  624. public createPostProcess(
  625. camera: Nullable<Camera>, options: number | PostProcessOptions = 1, samplingMode: number = Constants.TEXTURE_NEAREST_SAMPLINGMODE, engine?: Engine, reusable?: boolean,
  626. textureType: number = Constants.TEXTURETYPE_UNSIGNED_INT, textureFormat = Constants.TEXTUREFORMAT_RGBA): PostProcess {
  627. let tempName = this.name + this._buildId;
  628. const defines = new NodeMaterialDefines();
  629. const dummyMesh = new AbstractMesh(tempName + "PostProcess", this.getScene());
  630. let buildId = this._buildId;
  631. this._processDefines(dummyMesh, defines);
  632. Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString);
  633. const postProcess = new PostProcess(
  634. this.name + "PostProcess", tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers,
  635. options, camera, samplingMode, engine, reusable, defines.toString(), textureType, tempName, { maxSimultaneousLights: this.maxSimultaneousLights }, false, textureFormat
  636. );
  637. postProcess.onApplyObservable.add((effect) => {
  638. if (buildId !== this._buildId) {
  639. delete Effect.ShadersStore[tempName + "VertexShader"];
  640. delete Effect.ShadersStore[tempName + "PixelShader"];
  641. tempName = this.name + this._buildId;
  642. defines.markAsUnprocessed();
  643. buildId = this._buildId;
  644. }
  645. const result = this._processDefines(dummyMesh, defines);
  646. if (result) {
  647. Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString);
  648. postProcess.updateEffect(defines.toString(), this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, { maxSimultaneousLights: this.maxSimultaneousLights }, undefined, undefined, tempName, tempName);
  649. }
  650. // Animated blocks
  651. if (this._sharedData.animatedInputs) {
  652. const scene = this.getScene();
  653. let frameId = scene.getFrameId();
  654. if (this._animationFrame !== frameId) {
  655. for (var input of this._sharedData.animatedInputs) {
  656. input.animate(scene);
  657. }
  658. this._animationFrame = frameId;
  659. }
  660. }
  661. // Bindable blocks
  662. for (var block of this._sharedData.bindableBlocks) {
  663. block.bind(effect, this);
  664. }
  665. // Connection points
  666. for (var inputBlock of this._sharedData.inputBlocks) {
  667. inputBlock._transmit(effect, this.getScene());
  668. }
  669. });
  670. return postProcess;
  671. }
  672. private _createEffectForParticles(particleSystem: ParticleSystem, blendMode: number, onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void, effect?: Effect, defines?: NodeMaterialDefines, dummyMesh?: Nullable<AbstractMesh>) {
  673. let tempName = this.name + this._buildId + "_" + blendMode;
  674. if (!defines) {
  675. defines = new NodeMaterialDefines();
  676. }
  677. if (!dummyMesh) {
  678. dummyMesh = this.getScene().getMeshByName(this.name + "Particle");
  679. if (!dummyMesh) {
  680. dummyMesh = new AbstractMesh(this.name + "Particle", this.getScene());
  681. }
  682. }
  683. let buildId = this._buildId;
  684. let particleSystemDefines: Array<string> = [];
  685. let particleSystemDefinesJoined = "";
  686. if (!effect) {
  687. const result = this._processDefines(dummyMesh, defines);
  688. Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString);
  689. particleSystem.fillDefines(particleSystemDefines, blendMode);
  690. particleSystemDefinesJoined = particleSystemDefines.join("\n");
  691. effect = this.getScene().getEngine().createEffectForParticles(tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, defines.toString() + "\n" + particleSystemDefinesJoined, result?.fallbacks, onCompiled, onError, particleSystem);
  692. particleSystem.setCustomEffect(effect, blendMode);
  693. }
  694. effect.onBindObservable.add((effect) => {
  695. if (buildId !== this._buildId) {
  696. delete Effect.ShadersStore[tempName + "PixelShader"];
  697. tempName = this.name + this._buildId + "_" + blendMode;
  698. defines!.markAsUnprocessed();
  699. buildId = this._buildId;
  700. }
  701. particleSystemDefines.length = 0;
  702. particleSystem.fillDefines(particleSystemDefines, blendMode);
  703. const particleSystemDefinesJoinedCurrent = particleSystemDefines.join("\n");
  704. if (particleSystemDefinesJoinedCurrent !== particleSystemDefinesJoined) {
  705. defines!.markAsUnprocessed();
  706. particleSystemDefinesJoined = particleSystemDefinesJoinedCurrent;
  707. }
  708. const result = this._processDefines(dummyMesh!, defines!);
  709. if (result) {
  710. Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString);
  711. effect = this.getScene().getEngine().createEffectForParticles(tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, defines!.toString() + "\n" + particleSystemDefinesJoined, result?.fallbacks, onCompiled, onError, particleSystem);
  712. particleSystem.setCustomEffect(effect, blendMode);
  713. this._createEffectForParticles(particleSystem, blendMode, onCompiled, onError, effect, defines, dummyMesh); // add the effect.onBindObservable observer
  714. return;
  715. }
  716. // Animated blocks
  717. if (this._sharedData.animatedInputs) {
  718. const scene = this.getScene();
  719. let frameId = scene.getFrameId();
  720. if (this._animationFrame !== frameId) {
  721. for (var input of this._sharedData.animatedInputs) {
  722. input.animate(scene);
  723. }
  724. this._animationFrame = frameId;
  725. }
  726. }
  727. // Bindable blocks
  728. for (var block of this._sharedData.bindableBlocks) {
  729. block.bind(effect, this);
  730. }
  731. // Connection points
  732. for (var inputBlock of this._sharedData.inputBlocks) {
  733. inputBlock._transmit(effect, this.getScene());
  734. }
  735. });
  736. }
  737. /**
  738. * Create the effect to be used as the custom effect for a particle system
  739. * @param particleSystem Particle system to create the effect for
  740. * @param onCompiled defines a function to call when the effect creation is successful
  741. * @param onError defines a function to call when the effect creation has failed
  742. */
  743. public createEffectForParticles(particleSystem: ParticleSystem, onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void) {
  744. this._createEffectForParticles(particleSystem, BaseParticleSystem.BLENDMODE_ONEONE, onCompiled, onError);
  745. this._createEffectForParticles(particleSystem, BaseParticleSystem.BLENDMODE_MULTIPLY, onCompiled, onError);
  746. }
  747. private _processDefines(mesh: AbstractMesh, defines: NodeMaterialDefines, useInstances = false): Nullable<{
  748. lightDisposed: boolean,
  749. uniformBuffers: string[],
  750. mergedUniforms: string[],
  751. mergedSamplers: string[],
  752. fallbacks: EffectFallbacks,
  753. }> {
  754. let result = null;
  755. // Shared defines
  756. this._sharedData.blocksWithDefines.forEach((b) => {
  757. b.initializeDefines(mesh, this, defines, useInstances);
  758. });
  759. this._sharedData.blocksWithDefines.forEach((b) => {
  760. b.prepareDefines(mesh, this, defines, useInstances);
  761. });
  762. // Need to recompile?
  763. if (defines.isDirty) {
  764. const lightDisposed = defines._areLightsDisposed;
  765. defines.markAsProcessed();
  766. // Repeatable content generators
  767. this._vertexCompilationState.compilationString = this._vertexCompilationState._builtCompilationString;
  768. this._fragmentCompilationState.compilationString = this._fragmentCompilationState._builtCompilationString;
  769. this._sharedData.repeatableContentBlocks.forEach((b) => {
  770. b.replaceRepeatableContent(this._vertexCompilationState, this._fragmentCompilationState, mesh, defines);
  771. });
  772. // Uniforms
  773. let uniformBuffers: string[] = [];
  774. this._sharedData.dynamicUniformBlocks.forEach((b) => {
  775. b.updateUniformsAndSamples(this._vertexCompilationState, this, defines, uniformBuffers);
  776. });
  777. let mergedUniforms = this._vertexCompilationState.uniforms;
  778. this._fragmentCompilationState.uniforms.forEach((u) => {
  779. let index = mergedUniforms.indexOf(u);
  780. if (index === -1) {
  781. mergedUniforms.push(u);
  782. }
  783. });
  784. // Samplers
  785. let mergedSamplers = this._vertexCompilationState.samplers;
  786. this._fragmentCompilationState.samplers.forEach((s) => {
  787. let index = mergedSamplers.indexOf(s);
  788. if (index === -1) {
  789. mergedSamplers.push(s);
  790. }
  791. });
  792. var fallbacks = new EffectFallbacks();
  793. this._sharedData.blocksWithFallbacks.forEach((b) => {
  794. b.provideFallbacks(mesh, fallbacks);
  795. });
  796. result = {
  797. lightDisposed,
  798. uniformBuffers,
  799. mergedUniforms,
  800. mergedSamplers,
  801. fallbacks,
  802. };
  803. }
  804. return result;
  805. }
  806. /**
  807. * Get if the submesh is ready to be used and all its information available.
  808. * Child classes can use it to update shaders
  809. * @param mesh defines the mesh to check
  810. * @param subMesh defines which submesh to check
  811. * @param useInstances specifies that instances should be used
  812. * @returns a boolean indicating that the submesh is ready or not
  813. */
  814. public isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances: boolean = false): boolean {
  815. if (!this._buildWasSuccessful) {
  816. return false;
  817. }
  818. var scene = this.getScene();
  819. if (this._sharedData.animatedInputs) {
  820. let frameId = scene.getFrameId();
  821. if (this._animationFrame !== frameId) {
  822. for (var input of this._sharedData.animatedInputs) {
  823. input.animate(scene);
  824. }
  825. this._animationFrame = frameId;
  826. }
  827. }
  828. if (subMesh.effect && this.isFrozen) {
  829. if (subMesh.effect._wasPreviouslyReady) {
  830. return true;
  831. }
  832. }
  833. if (!subMesh._materialDefines) {
  834. subMesh._materialDefines = new NodeMaterialDefines();
  835. }
  836. var defines = <NodeMaterialDefines>subMesh._materialDefines;
  837. if (this._isReadyForSubMesh(subMesh)) {
  838. return true;
  839. }
  840. var engine = scene.getEngine();
  841. this._prepareDefinesForAttributes(mesh, defines);
  842. // Check if blocks are ready
  843. if (this._sharedData.blockingBlocks.some((b) => !b.isReady(mesh, this, defines, useInstances))) {
  844. return false;
  845. }
  846. const result = this._processDefines(mesh, defines, useInstances);
  847. if (result) {
  848. let previousEffect = subMesh.effect;
  849. // Compilation
  850. var join = defines.toString();
  851. var effect = engine.createEffect({
  852. vertex: "nodeMaterial" + this._buildId,
  853. fragment: "nodeMaterial" + this._buildId,
  854. vertexSource: this._vertexCompilationState.compilationString,
  855. fragmentSource: this._fragmentCompilationState.compilationString
  856. }, <IEffectCreationOptions>{
  857. attributes: this._vertexCompilationState.attributes,
  858. uniformsNames: result.mergedUniforms,
  859. uniformBuffersNames: result.uniformBuffers,
  860. samplers: result.mergedSamplers,
  861. defines: join,
  862. fallbacks: result.fallbacks,
  863. onCompiled: this.onCompiled,
  864. onError: this.onError,
  865. indexParameters: { maxSimultaneousLights: this.maxSimultaneousLights, maxSimultaneousMorphTargets: defines.NUM_MORPH_INFLUENCERS }
  866. }, engine);
  867. if (effect) {
  868. if (this._onEffectCreatedObservable) {
  869. onCreatedEffectParameters.effect = effect;
  870. onCreatedEffectParameters.subMesh = subMesh;
  871. this._onEffectCreatedObservable.notifyObservers(onCreatedEffectParameters);
  872. }
  873. // Use previous effect while new one is compiling
  874. if (this.allowShaderHotSwapping && previousEffect && !effect.isReady()) {
  875. effect = previousEffect;
  876. defines.markAsUnprocessed();
  877. if (result.lightDisposed) {
  878. // re register in case it takes more than one frame.
  879. defines._areLightsDisposed = true;
  880. return false;
  881. }
  882. } else {
  883. scene.resetCachedMaterial();
  884. subMesh.setEffect(effect, defines);
  885. }
  886. }
  887. }
  888. if (!subMesh.effect || !subMesh.effect.isReady()) {
  889. return false;
  890. }
  891. defines._renderId = scene.getRenderId();
  892. subMesh.effect._wasPreviouslyReady = true;
  893. return true;
  894. }
  895. /**
  896. * Get a string representing the shaders built by the current node graph
  897. */
  898. public get compiledShaders() {
  899. return `// Vertex shader\r\n${this._vertexCompilationState.compilationString}\r\n\r\n// Fragment shader\r\n${this._fragmentCompilationState.compilationString}`;
  900. }
  901. /**
  902. * Binds the world matrix to the material
  903. * @param world defines the world transformation matrix
  904. */
  905. public bindOnlyWorldMatrix(world: Matrix): void {
  906. var scene = this.getScene();
  907. if (!this._activeEffect) {
  908. return;
  909. }
  910. let hints = this._sharedData.hints;
  911. if (hints.needWorldViewMatrix) {
  912. world.multiplyToRef(scene.getViewMatrix(), this._cachedWorldViewMatrix);
  913. }
  914. if (hints.needWorldViewProjectionMatrix) {
  915. world.multiplyToRef(scene.getTransformMatrix(), this._cachedWorldViewProjectionMatrix);
  916. }
  917. // Connection points
  918. for (var inputBlock of this._sharedData.inputBlocks) {
  919. inputBlock._transmitWorld(this._activeEffect, world, this._cachedWorldViewMatrix, this._cachedWorldViewProjectionMatrix);
  920. }
  921. }
  922. /**
  923. * Binds the submesh to this material by preparing the effect and shader to draw
  924. * @param world defines the world transformation matrix
  925. * @param mesh defines the mesh containing the submesh
  926. * @param subMesh defines the submesh to bind the material to
  927. */
  928. public bindForSubMesh(world: Matrix, mesh: Mesh, subMesh: SubMesh): void {
  929. let scene = this.getScene();
  930. var effect = subMesh.effect;
  931. if (!effect) {
  932. return;
  933. }
  934. this._activeEffect = effect;
  935. // Matrices
  936. this.bindOnlyWorldMatrix(world);
  937. let mustRebind = this._mustRebind(scene, effect, mesh.visibility);
  938. if (mustRebind) {
  939. let sharedData = this._sharedData;
  940. if (effect && scene.getCachedEffect() !== effect) {
  941. // Bindable blocks
  942. for (var block of sharedData.bindableBlocks) {
  943. block.bind(effect, this, mesh, subMesh);
  944. }
  945. // Connection points
  946. for (var inputBlock of sharedData.inputBlocks) {
  947. inputBlock._transmit(effect, scene);
  948. }
  949. }
  950. }
  951. this._afterBind(mesh, this._activeEffect);
  952. }
  953. /**
  954. * Gets the active textures from the material
  955. * @returns an array of textures
  956. */
  957. public getActiveTextures(): BaseTexture[] {
  958. var activeTextures = super.getActiveTextures();
  959. if (this._sharedData) {
  960. activeTextures.push(...this._sharedData.textureBlocks.filter((tb) => tb.texture).map((tb) => tb.texture!));
  961. }
  962. return activeTextures;
  963. }
  964. /**
  965. * Gets the list of texture blocks
  966. * @returns an array of texture blocks
  967. */
  968. public getTextureBlocks(): (TextureBlock | ReflectionTextureBaseBlock | RefractionBlock | CurrentScreenBlock | ParticleTextureBlock)[] {
  969. if (!this._sharedData) {
  970. return [];
  971. }
  972. return this._sharedData.textureBlocks;
  973. }
  974. /**
  975. * Specifies if the material uses a texture
  976. * @param texture defines the texture to check against the material
  977. * @returns a boolean specifying if the material uses the texture
  978. */
  979. public hasTexture(texture: BaseTexture): boolean {
  980. if (super.hasTexture(texture)) {
  981. return true;
  982. }
  983. if (!this._sharedData) {
  984. return false;
  985. }
  986. for (var t of this._sharedData.textureBlocks) {
  987. if (t.texture === texture) {
  988. return true;
  989. }
  990. }
  991. return false;
  992. }
  993. /**
  994. * Disposes the material
  995. * @param forceDisposeEffect specifies if effects should be forcefully disposed
  996. * @param forceDisposeTextures specifies if textures should be forcefully disposed
  997. * @param notBoundToMesh specifies if the material that is being disposed is known to be not bound to any mesh
  998. */
  999. public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean, notBoundToMesh?: boolean): void {
  1000. if (forceDisposeTextures) {
  1001. for (var texture of this._sharedData.textureBlocks.filter((tb) => tb.texture).map((tb) => tb.texture!)) {
  1002. texture.dispose();
  1003. }
  1004. }
  1005. for (var block of this.attachedBlocks) {
  1006. block.dispose();
  1007. }
  1008. this.onBuildObservable.clear();
  1009. super.dispose(forceDisposeEffect, forceDisposeTextures, notBoundToMesh);
  1010. }
  1011. /** Creates the node editor window. */
  1012. private _createNodeEditor() {
  1013. this.BJSNODEMATERIALEDITOR = this.BJSNODEMATERIALEDITOR || this._getGlobalNodeMaterialEditor();
  1014. this.BJSNODEMATERIALEDITOR.NodeEditor.Show({
  1015. nodeMaterial: this
  1016. });
  1017. }
  1018. /**
  1019. * Launch the node material editor
  1020. * @param config Define the configuration of the editor
  1021. * @return a promise fulfilled when the node editor is visible
  1022. */
  1023. public edit(config?: INodeMaterialEditorOptions): Promise<void> {
  1024. return new Promise((resolve, reject) => {
  1025. if (typeof this.BJSNODEMATERIALEDITOR == 'undefined') {
  1026. const editorUrl = config && config.editorURL ? config.editorURL : NodeMaterial.EditorURL;
  1027. // Load editor and add it to the DOM
  1028. Tools.LoadScript(editorUrl, () => {
  1029. this._createNodeEditor();
  1030. resolve();
  1031. });
  1032. } else {
  1033. // Otherwise creates the editor
  1034. this._createNodeEditor();
  1035. resolve();
  1036. }
  1037. });
  1038. }
  1039. /**
  1040. * Clear the current material
  1041. */
  1042. public clear() {
  1043. this._vertexOutputNodes = [];
  1044. this._fragmentOutputNodes = [];
  1045. this.attachedBlocks = [];
  1046. }
  1047. /**
  1048. * Clear the current material and set it to a default state
  1049. */
  1050. public setToDefault() {
  1051. this.clear();
  1052. this.editorData = null;
  1053. var positionInput = new InputBlock("Position");
  1054. positionInput.setAsAttribute("position");
  1055. var worldInput = new InputBlock("World");
  1056. worldInput.setAsSystemValue(BABYLON.NodeMaterialSystemValues.World);
  1057. var worldPos = new TransformBlock("WorldPos");
  1058. positionInput.connectTo(worldPos);
  1059. worldInput.connectTo(worldPos);
  1060. var viewProjectionInput = new InputBlock("ViewProjection");
  1061. viewProjectionInput.setAsSystemValue(BABYLON.NodeMaterialSystemValues.ViewProjection);
  1062. var worldPosdMultipliedByViewProjection = new TransformBlock("WorldPos * ViewProjectionTransform");
  1063. worldPos.connectTo(worldPosdMultipliedByViewProjection);
  1064. viewProjectionInput.connectTo(worldPosdMultipliedByViewProjection);
  1065. var vertexOutput = new VertexOutputBlock("VertexOutput");
  1066. worldPosdMultipliedByViewProjection.connectTo(vertexOutput);
  1067. // Pixel
  1068. var pixelColor = new InputBlock("color");
  1069. pixelColor.value = new Color4(0.8, 0.8, 0.8, 1);
  1070. var fragmentOutput = new FragmentOutputBlock("FragmentOutput");
  1071. pixelColor.connectTo(fragmentOutput);
  1072. // Add to nodes
  1073. this.addOutputNode(vertexOutput);
  1074. this.addOutputNode(fragmentOutput);
  1075. this._mode = NodeMaterialModes.Material;
  1076. }
  1077. /**
  1078. * Clear the current material and set it to a default state for post process
  1079. */
  1080. public setToDefaultPostProcess() {
  1081. this.clear();
  1082. this.editorData = null;
  1083. const position = new InputBlock("Position");
  1084. position.setAsAttribute("position2d");
  1085. const const1 = new InputBlock("Constant1");
  1086. const1.isConstant = true;
  1087. const1.value = 1;
  1088. const vmerger = new VectorMergerBlock("Position3D");
  1089. position.connectTo(vmerger);
  1090. const1.connectTo(vmerger, { input: "w" });
  1091. const vertexOutput = new VertexOutputBlock("VertexOutput");
  1092. vmerger.connectTo(vertexOutput);
  1093. // Pixel
  1094. const scale = new InputBlock("scale");
  1095. scale.visibleInInspector = true;
  1096. scale.value = new Vector2(1, 1);
  1097. const uv0 = new RemapBlock("uv0");
  1098. position.connectTo(uv0);
  1099. const uv = new MultiplyBlock("uv");
  1100. uv0.connectTo(uv);
  1101. scale.connectTo(uv);
  1102. const currentScreen = new CurrentScreenBlock("CurrentScreen");
  1103. uv.connectTo(currentScreen);
  1104. currentScreen.texture = new Texture("https://assets.babylonjs.com/nme/currentScreenPostProcess.png", this.getScene());
  1105. var fragmentOutput = new FragmentOutputBlock("FragmentOutput");
  1106. currentScreen.connectTo(fragmentOutput, { output: "rgba" });
  1107. // Add to nodes
  1108. this.addOutputNode(vertexOutput);
  1109. this.addOutputNode(fragmentOutput);
  1110. this._mode = NodeMaterialModes.PostProcess;
  1111. }
  1112. /**
  1113. * Clear the current material and set it to a default state for particle
  1114. */
  1115. public setToDefaultParticle() {
  1116. this.clear();
  1117. this.editorData = null;
  1118. // Pixel
  1119. const uv = new InputBlock("uv");
  1120. uv.setAsAttribute("particle_uv");
  1121. const texture = new ParticleTextureBlock("ParticleTexture");
  1122. uv.connectTo(texture);
  1123. const color = new InputBlock("Color");
  1124. color.setAsAttribute("particle_color");
  1125. const multiply = new MultiplyBlock("texture * color");
  1126. texture.connectTo(multiply);
  1127. color.connectTo(multiply);
  1128. const rampGradient = new ParticleRampGradientBlock("ParticleRampGradient");
  1129. multiply.connectTo(rampGradient);
  1130. const cSplitter = new ColorSplitterBlock("ColorSplitter");
  1131. color.connectTo(cSplitter);
  1132. const blendMultiply = new ParticleBlendMultiplyBlock("ParticleBlendMultiply");
  1133. rampGradient.connectTo(blendMultiply);
  1134. texture.connectTo(blendMultiply, { "output": "a" });
  1135. cSplitter.connectTo(blendMultiply, { "output": "a" });
  1136. const fragmentOutput = new FragmentOutputBlock("FragmentOutput");
  1137. blendMultiply.connectTo(fragmentOutput);
  1138. // Add to nodes
  1139. this.addOutputNode(fragmentOutput);
  1140. this._mode = NodeMaterialModes.Particle;
  1141. }
  1142. /**
  1143. * Loads the current Node Material from a url pointing to a file save by the Node Material Editor
  1144. * @param url defines the url to load from
  1145. * @returns a promise that will fullfil when the material is fully loaded
  1146. */
  1147. public loadAsync(url: string) {
  1148. return this.getScene()._loadFileAsync(url).then((data) => {
  1149. const serializationObject = JSON.parse(data as string);
  1150. this.loadFromSerialization(serializationObject, "");
  1151. });
  1152. }
  1153. private _gatherBlocks(rootNode: NodeMaterialBlock, list: NodeMaterialBlock[]) {
  1154. if (list.indexOf(rootNode) !== -1) {
  1155. return;
  1156. }
  1157. list.push(rootNode);
  1158. for (var input of rootNode.inputs) {
  1159. let connectedPoint = input.connectedPoint;
  1160. if (connectedPoint) {
  1161. let block = connectedPoint.ownerBlock;
  1162. if (block !== rootNode) {
  1163. this._gatherBlocks(block, list);
  1164. }
  1165. }
  1166. }
  1167. }
  1168. /**
  1169. * Generate a string containing the code declaration required to create an equivalent of this material
  1170. * @returns a string
  1171. */
  1172. public generateCode() {
  1173. let alreadyDumped: NodeMaterialBlock[] = [];
  1174. let vertexBlocks: NodeMaterialBlock[] = [];
  1175. let uniqueNames: string[] = [];
  1176. // Gets active blocks
  1177. for (var outputNode of this._vertexOutputNodes) {
  1178. this._gatherBlocks(outputNode, vertexBlocks);
  1179. }
  1180. let fragmentBlocks: NodeMaterialBlock[] = [];
  1181. for (var outputNode of this._fragmentOutputNodes) {
  1182. this._gatherBlocks(outputNode, fragmentBlocks);
  1183. }
  1184. // Generate vertex shader
  1185. let codeString = `var nodeMaterial = new BABYLON.NodeMaterial("${this.name || "node material"}");\r\n`;
  1186. for (var node of vertexBlocks) {
  1187. if (node.isInput && alreadyDumped.indexOf(node) === -1) {
  1188. codeString += node._dumpCode(uniqueNames, alreadyDumped);
  1189. }
  1190. }
  1191. // Generate fragment shader
  1192. for (var node of fragmentBlocks) {
  1193. if (node.isInput && alreadyDumped.indexOf(node) === -1) {
  1194. codeString += node._dumpCode(uniqueNames, alreadyDumped);
  1195. }
  1196. }
  1197. // Connections
  1198. alreadyDumped = [];
  1199. codeString += "\r\n// Connections\r\n";
  1200. for (var node of this._vertexOutputNodes) {
  1201. codeString += node._dumpCodeForOutputConnections(alreadyDumped);
  1202. }
  1203. for (var node of this._fragmentOutputNodes) {
  1204. codeString += node._dumpCodeForOutputConnections(alreadyDumped);
  1205. }
  1206. // Output nodes
  1207. codeString += "\r\n// Output nodes\r\n";
  1208. for (var node of this._vertexOutputNodes) {
  1209. codeString += `nodeMaterial.addOutputNode(${node._codeVariableName});\r\n`;
  1210. }
  1211. for (var node of this._fragmentOutputNodes) {
  1212. codeString += `nodeMaterial.addOutputNode(${node._codeVariableName});\r\n`;
  1213. }
  1214. codeString += `nodeMaterial.build();\r\n`;
  1215. return codeString;
  1216. }
  1217. /**
  1218. * Serializes this material in a JSON representation
  1219. * @returns the serialized material object
  1220. */
  1221. public serialize(selectedBlocks?: NodeMaterialBlock[]): any {
  1222. var serializationObject = selectedBlocks ? {} : SerializationHelper.Serialize(this);
  1223. serializationObject.editorData = JSON.parse(JSON.stringify(this.editorData)); // Copy
  1224. let blocks: NodeMaterialBlock[] = [];
  1225. if (selectedBlocks) {
  1226. blocks = selectedBlocks;
  1227. } else {
  1228. serializationObject.customType = "BABYLON.NodeMaterial";
  1229. serializationObject.outputNodes = [];
  1230. // Outputs
  1231. for (var outputNode of this._vertexOutputNodes) {
  1232. this._gatherBlocks(outputNode, blocks);
  1233. serializationObject.outputNodes.push(outputNode.uniqueId);
  1234. }
  1235. for (var outputNode of this._fragmentOutputNodes) {
  1236. this._gatherBlocks(outputNode, blocks);
  1237. if (serializationObject.outputNodes.indexOf(outputNode.uniqueId) === -1) {
  1238. serializationObject.outputNodes.push(outputNode.uniqueId);
  1239. }
  1240. }
  1241. }
  1242. // Blocks
  1243. serializationObject.blocks = [];
  1244. for (var block of blocks) {
  1245. serializationObject.blocks.push(block.serialize());
  1246. }
  1247. if (!selectedBlocks) {
  1248. for (var block of this.attachedBlocks) {
  1249. if (blocks.indexOf(block) !== -1) {
  1250. continue;
  1251. }
  1252. serializationObject.blocks.push(block.serialize());
  1253. }
  1254. }
  1255. return serializationObject;
  1256. }
  1257. private _restoreConnections(block: NodeMaterialBlock, source: any, map: {[key: number]: NodeMaterialBlock}) {
  1258. for (var outputPoint of block.outputs) {
  1259. for (var candidate of source.blocks) {
  1260. let target = map[candidate.id];
  1261. for (var input of candidate.inputs) {
  1262. if (map[input.targetBlockId] === block && input.targetConnectionName === outputPoint.name) {
  1263. let inputPoint = target.getInputByName(input.inputName);
  1264. if (!inputPoint || inputPoint.isConnected) {
  1265. continue;
  1266. }
  1267. outputPoint.connectTo(inputPoint, true);
  1268. this._restoreConnections(target, source, map);
  1269. continue;
  1270. }
  1271. }
  1272. }
  1273. }
  1274. }
  1275. /**
  1276. * Clear the current graph and load a new one from a serialization object
  1277. * @param source defines the JSON representation of the material
  1278. * @param rootUrl defines the root URL to use to load textures and relative dependencies
  1279. */
  1280. public loadFromSerialization(source: any, rootUrl: string = "") {
  1281. this.clear();
  1282. let map: {[key: number]: NodeMaterialBlock} = {};
  1283. // Create blocks
  1284. for (var parsedBlock of source.blocks) {
  1285. let blockType = _TypeStore.GetClass(parsedBlock.customType);
  1286. if (blockType) {
  1287. let block: NodeMaterialBlock = new blockType();
  1288. block._deserialize(parsedBlock, this.getScene(), rootUrl);
  1289. map[parsedBlock.id] = block;
  1290. this.attachedBlocks.push(block);
  1291. }
  1292. }
  1293. // Connections
  1294. // Starts with input blocks only
  1295. for (var blockIndex = 0; blockIndex < source.blocks.length; blockIndex++) {
  1296. let parsedBlock = source.blocks[blockIndex];
  1297. let block = map[parsedBlock.id];
  1298. if (block.inputs.length) {
  1299. continue;
  1300. }
  1301. this._restoreConnections(block, source, map);
  1302. }
  1303. // Outputs
  1304. for (var outputNodeId of source.outputNodes) {
  1305. this.addOutputNode(map[outputNodeId]);
  1306. }
  1307. // UI related info
  1308. if (source.locations || source.editorData && source.editorData.locations) {
  1309. let locations: {
  1310. blockId: number;
  1311. x: number;
  1312. y: number;
  1313. }[] = source.locations || source.editorData.locations;
  1314. for (var location of locations) {
  1315. if (map[location.blockId]) {
  1316. location.blockId = map[location.blockId].uniqueId;
  1317. }
  1318. }
  1319. if (source.locations) {
  1320. this.editorData = {
  1321. locations: locations
  1322. };
  1323. } else {
  1324. this.editorData = source.editorData;
  1325. this.editorData.locations = locations;
  1326. }
  1327. let blockMap: number[] = [];
  1328. for (var key in map) {
  1329. blockMap[key] = map[key].uniqueId;
  1330. }
  1331. this.editorData.map = blockMap;
  1332. }
  1333. this._mode = source.mode ?? NodeMaterialModes.Material;
  1334. }
  1335. /**
  1336. * Makes a duplicate of the current material.
  1337. * @param name - name to use for the new material.
  1338. */
  1339. public clone(name: string): NodeMaterial {
  1340. const serializationObject = this.serialize();
  1341. const clone = SerializationHelper.Clone(() => new NodeMaterial(name, this.getScene(), this.options), this);
  1342. clone.id = name;
  1343. clone.name = name;
  1344. clone.loadFromSerialization(serializationObject);
  1345. clone.build();
  1346. return clone;
  1347. }
  1348. /**
  1349. * Creates a node material from parsed material data
  1350. * @param source defines the JSON representation of the material
  1351. * @param scene defines the hosting scene
  1352. * @param rootUrl defines the root URL to use to load textures and relative dependencies
  1353. * @returns a new node material
  1354. */
  1355. public static Parse(source: any, scene: Scene, rootUrl: string = ""): NodeMaterial {
  1356. let nodeMaterial = SerializationHelper.Parse(() => new NodeMaterial(source.name, scene), source, scene, rootUrl);
  1357. nodeMaterial.loadFromSerialization(source, rootUrl);
  1358. nodeMaterial.build();
  1359. return nodeMaterial;
  1360. }
  1361. /**
  1362. * Creates a node material from a snippet saved in a remote file
  1363. * @param name defines the name of the material to create
  1364. * @param url defines the url to load from
  1365. * @param scene defines the hosting scene
  1366. * @returns a promise that will resolve to the new node material
  1367. */
  1368. public static ParseFromFileAsync(name: string, url: string, scene: Scene): Promise<NodeMaterial> {
  1369. var material = new NodeMaterial(name, scene);
  1370. return new Promise((resolve, reject) => {
  1371. return material.loadAsync(url).then(() => resolve(material)).catch(reject);
  1372. });
  1373. }
  1374. /**
  1375. * Creates a node material from a snippet saved by the node material editor
  1376. * @param snippetId defines the snippet to load
  1377. * @param scene defines the hosting scene
  1378. * @param rootUrl defines the root URL to use to load textures and relative dependencies
  1379. * @param nodeMaterial defines a node material to update (instead of creating a new one)
  1380. * @returns a promise that will resolve to the new node material
  1381. */
  1382. public static ParseFromSnippetAsync(snippetId: string, scene: Scene, rootUrl: string = "", nodeMaterial?: NodeMaterial): Promise<NodeMaterial> {
  1383. return new Promise((resolve, reject) => {
  1384. var request = new WebRequest();
  1385. request.addEventListener("readystatechange", () => {
  1386. if (request.readyState == 4) {
  1387. if (request.status == 200) {
  1388. var snippet = JSON.parse(JSON.parse(request.responseText).jsonPayload);
  1389. let serializationObject = JSON.parse(snippet.nodeMaterial);
  1390. if (!nodeMaterial) {
  1391. nodeMaterial = SerializationHelper.Parse(() => new NodeMaterial(snippetId, scene), serializationObject, scene, rootUrl);
  1392. nodeMaterial.uniqueId = scene.getUniqueId();
  1393. }
  1394. nodeMaterial.loadFromSerialization(serializationObject);
  1395. nodeMaterial.snippetId = snippetId;
  1396. try {
  1397. nodeMaterial.build();
  1398. resolve(nodeMaterial);
  1399. } catch (err) {
  1400. reject(err);
  1401. }
  1402. } else {
  1403. reject("Unable to load the snippet " + snippetId);
  1404. }
  1405. }
  1406. });
  1407. request.open("GET", this.SnippetUrl + "/" + snippetId.replace("#", "/"));
  1408. request.send();
  1409. });
  1410. }
  1411. /**
  1412. * Creates a new node material set to default basic configuration
  1413. * @param name defines the name of the material
  1414. * @param scene defines the hosting scene
  1415. * @returns a new NodeMaterial
  1416. */
  1417. public static CreateDefault(name: string, scene?: Scene) {
  1418. let newMaterial = new NodeMaterial(name, scene);
  1419. newMaterial.setToDefault();
  1420. newMaterial.build();
  1421. return newMaterial;
  1422. }
  1423. }
  1424. _TypeStore.RegisteredTypes["BABYLON.NodeMaterial"] = NodeMaterial;