NodeMaterial.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245
  1. import { Material } from '../Material.js';
  2. import { NormalBlending } from '../../constants.js';
  3. import { getNodeChildren, getCacheKey } from '../../nodes/core/NodeUtils.js';
  4. import { output, diffuseColor, emissive, varyingProperty } from '../../nodes/core/PropertyNode.js';
  5. import { materialAlphaTest, materialColor, materialOpacity, materialEmissive, materialNormal, materialLightMap, materialAO } from '../../nodes/accessors/MaterialNode.js';
  6. import { modelViewProjection } from '../../nodes/accessors/ModelViewProjectionNode.js';
  7. import { normalLocal } from '../../nodes/accessors/Normal.js';
  8. import { instancedMesh } from '../../nodes/accessors/InstancedMeshNode.js';
  9. import { batch } from '../../nodes/accessors/BatchNode.js';
  10. import { materialReference } from '../../nodes/accessors/MaterialReferenceNode.js';
  11. import { positionLocal, positionView } from '../../nodes/accessors/Position.js';
  12. import { skinning } from '../../nodes/accessors/SkinningNode.js';
  13. import { morphReference } from '../../nodes/accessors/MorphNode.js';
  14. import { mix } from '../../nodes/math/MathNode.js';
  15. import { float, vec3, vec4, bool } from '../../nodes/tsl/TSLBase.js';
  16. import AONode from '../../nodes/lighting/AONode.js';
  17. import { lightingContext } from '../../nodes/lighting/LightingContextNode.js';
  18. import IrradianceNode from '../../nodes/lighting/IrradianceNode.js';
  19. import { depth, viewZToLogarithmicDepth, viewZToOrthographicDepth } from '../../nodes/display/ViewportDepthNode.js';
  20. import { cameraFar, cameraNear, cameraProjectionMatrix } from '../../nodes/accessors/Camera.js';
  21. import { clipping, clippingAlpha, hardwareClipping } from '../../nodes/accessors/ClippingNode.js';
  22. import NodeMaterialObserver from './manager/NodeMaterialObserver.js';
  23. import getAlphaHashThreshold from '../../nodes/functions/material/getAlphaHashThreshold.js';
  24. import { modelViewMatrix } from '../../nodes/accessors/ModelNode.js';
  25. import { vertexColor } from '../../nodes/accessors/VertexColorNode.js';
  26. /**
  27. * Base class for all node materials.
  28. *
  29. * @augments Material
  30. */
  31. class NodeMaterial extends Material {
  32. static get type() {
  33. return 'NodeMaterial';
  34. }
  35. /**
  36. * Represents the type of the node material.
  37. *
  38. * @type {string}
  39. */
  40. get type() {
  41. return this.constructor.type;
  42. }
  43. set type( _value ) { /* */ }
  44. /**
  45. * Constructs a new node material.
  46. */
  47. constructor() {
  48. super();
  49. /**
  50. * This flag can be used for type testing.
  51. *
  52. * @type {boolean}
  53. * @readonly
  54. * @default true
  55. */
  56. this.isNodeMaterial = true;
  57. /**
  58. * Whether this material is affected by fog or not.
  59. *
  60. * @type {boolean}
  61. * @default true
  62. */
  63. this.fog = true;
  64. /**
  65. * Whether this material is affected by lights or not.
  66. *
  67. * @type {boolean}
  68. * @default false
  69. */
  70. this.lights = false;
  71. /**
  72. * Whether this material uses hardware clipping or not.
  73. * This property is managed by the engine and should not be
  74. * modified by apps.
  75. *
  76. * @type {boolean}
  77. * @default false
  78. */
  79. this.hardwareClipping = false;
  80. /**
  81. * Node materials which set their `lights` property to `true`
  82. * are affected by all lights of the scene. Sometimes selective
  83. * lighting is wanted which means only _some_ lights in the scene
  84. * affect a material. This can be achieved by creating an instance
  85. * of {@link LightsNode} with a list of selective
  86. * lights and assign the node to this property.
  87. *
  88. * ```js
  89. * const customLightsNode = lights( [ light1, light2 ] );
  90. * material.lightsNode = customLightsNode;
  91. * ```
  92. *
  93. * @type {?LightsNode}
  94. * @default null
  95. */
  96. this.lightsNode = null;
  97. /**
  98. * The environment of node materials can be defined by an environment
  99. * map assigned to the `envMap` property or by `Scene.environment`
  100. * if the node material is a PBR material. This node property allows to overwrite
  101. * the default behavior and define the environment with a custom node.
  102. *
  103. * ```js
  104. * material.envNode = pmremTexture( renderTarget.texture );
  105. * ```
  106. *
  107. * @type {?Node<vec3>}
  108. * @default null
  109. */
  110. this.envNode = null;
  111. /**
  112. * The lighting of node materials might be influenced by ambient occlusion.
  113. * The default AO is inferred from an ambient occlusion map assigned to `aoMap`
  114. * and the respective `aoMapIntensity`. This node property allows to overwrite
  115. * the default and define the ambient occlusion with a custom node instead.
  116. *
  117. * If you don't want to overwrite the diffuse color but modify the existing
  118. * values instead, use {@link materialAO}.
  119. *
  120. * @type {?Node<float>}
  121. * @default null
  122. */
  123. this.aoNode = null;
  124. /**
  125. * The diffuse color of node materials is by default inferred from the
  126. * `color` and `map` properties. This node property allows to overwrite the default
  127. * and define the diffuse color with a node instead.
  128. *
  129. * ```js
  130. * material.colorNode = color( 0xff0000 ); // define red color
  131. * ```
  132. *
  133. * If you don't want to overwrite the diffuse color but modify the existing
  134. * values instead, use {@link materialColor}.
  135. *
  136. * ```js
  137. * material.colorNode = materialColor.mul( color( 0xff0000 ) ); // give diffuse colors a red tint
  138. * ```
  139. *
  140. * @type {?Node<vec3>}
  141. * @default null
  142. */
  143. this.colorNode = null;
  144. /**
  145. * The normals of node materials are by default inferred from the `normalMap`/`normalScale`
  146. * or `bumpMap`/`bumpScale` properties. This node property allows to overwrite the default
  147. * and define the normals with a node instead.
  148. *
  149. * If you don't want to overwrite the normals but modify the existing values instead,
  150. * use {@link materialNormal}.
  151. *
  152. * @type {?Node<vec3>}
  153. * @default null
  154. */
  155. this.normalNode = null;
  156. /**
  157. * The opacity of node materials is by default inferred from the `opacity`
  158. * and `alphaMap` properties. This node property allows to overwrite the default
  159. * and define the opacity with a node instead.
  160. *
  161. * If you don't want to overwrite the normals but modify the existing
  162. * value instead, use {@link materialOpacity}.
  163. *
  164. * @type {?Node<float>}
  165. * @default null
  166. */
  167. this.opacityNode = null;
  168. /**
  169. * This node can be used to implement a variety of filter-like effects. The idea is
  170. * to store the current rendering into a texture e.g. via `viewportSharedTexture()`, use it
  171. * to create an arbitrary effect and then assign the node composition to this property.
  172. * Everything behind the object using this material will now be affected by a filter.
  173. *
  174. * ```js
  175. * const material = new NodeMaterial()
  176. * material.transparent = true;
  177. *
  178. * // everything behind the object will be monochromatic
  179. * material.backdropNode = saturation( viewportSharedTexture().rgb, 0 );
  180. * ```
  181. *
  182. * Backdrop computations are part of the lighting so only lit materials can use this property.
  183. *
  184. * @type {?Node<vec3>}
  185. * @default null
  186. */
  187. this.backdropNode = null;
  188. /**
  189. * This node allows to modulate the influence of `backdropNode` to the outgoing light.
  190. *
  191. * @type {?Node<float>}
  192. * @default null
  193. */
  194. this.backdropAlphaNode = null;
  195. /**
  196. * The alpha test of node materials is by default inferred from the `alphaTest`
  197. * property. This node property allows to overwrite the default and define the
  198. * alpha test with a node instead.
  199. *
  200. * If you don't want to overwrite the alpha test but modify the existing
  201. * value instead, use {@link materialAlphaTest}.
  202. *
  203. * @type {?Node<float>}
  204. * @default null
  205. */
  206. this.alphaTestNode = null;
  207. /**
  208. * Discards the fragment if the mask value is `false`.
  209. *
  210. * @type {?Node<bool>}
  211. * @default null
  212. */
  213. this.maskNode = null;
  214. /**
  215. * The local vertex positions are computed based on multiple factors like the
  216. * attribute data, morphing or skinning. This node property allows to overwrite
  217. * the default and define local vertex positions with nodes instead.
  218. *
  219. * If you don't want to overwrite the vertex positions but modify the existing
  220. * values instead, use {@link positionLocal}.
  221. *
  222. *```js
  223. * material.positionNode = positionLocal.add( displace );
  224. * ```
  225. *
  226. * @type {?Node<vec3>}
  227. * @default null
  228. */
  229. this.positionNode = null;
  230. /**
  231. * This node property is intended for logic which modifies geometry data once or per animation step.
  232. * Apps usually place such logic randomly in initialization routines or in the animation loop.
  233. * `geometryNode` is intended as a dedicated API so there is an intended spot where geometry modifications
  234. * can be implemented.
  235. *
  236. * The idea is to assign a `Fn` definition that holds the geometry modification logic. A typical example
  237. * would be a GPU based particle system that provides a node material for usage on app level. The particle
  238. * simulation would be implemented as compute shaders and managed inside a `Fn` function. This function is
  239. * eventually assigned to `geometryNode`.
  240. *
  241. * @type {?Function}
  242. * @default null
  243. */
  244. this.geometryNode = null;
  245. /**
  246. * Allows to overwrite depth values in the fragment shader.
  247. *
  248. * @type {?Node<float>}
  249. * @default null
  250. */
  251. this.depthNode = null;
  252. /**
  253. * Allows to overwrite the position used for shadow map rendering which
  254. * is by default {@link positionWorld}, the vertex position
  255. * in world space.
  256. *
  257. * @type {?Node<float>}
  258. * @default null
  259. */
  260. this.receivedShadowPositionNode = null;
  261. /**
  262. * Allows to overwrite the geometry position used for shadow map projection which
  263. * is by default {@link positionLocal}, the vertex position in local space.
  264. *
  265. * @type {?Node<float>}
  266. * @default null
  267. */
  268. this.castShadowPositionNode = null;
  269. /**
  270. * This node can be used to influence how an object using this node material
  271. * receive shadows.
  272. *
  273. * ```js
  274. * const totalShadows = float( 1 ).toVar();
  275. * material.receivedShadowNode = Fn( ( [ shadow ] ) => {
  276. * totalShadows.mulAssign( shadow );
  277. * //return float( 1 ); // bypass received shadows
  278. * return shadow.mix( color( 0xff0000 ), 1 ); // modify shadow color
  279. * } );
  280. *
  281. * @type {?(Function|FunctionNode<vec4>)}
  282. * @default null
  283. */
  284. this.receivedShadowNode = null;
  285. /**
  286. * This node can be used to influence how an object using this node material
  287. * casts shadows. To apply a color to shadows, you can simply do:
  288. *
  289. * ```js
  290. * material.castShadowNode = vec4( 1, 0, 0, 1 );
  291. * ```
  292. *
  293. * Which can be nice to fake colored shadows of semi-transparent objects. It
  294. * is also common to use the property with `Fn` function so checks are performed
  295. * per fragment.
  296. *
  297. * ```js
  298. * materialCustomShadow.castShadowNode = Fn( () => {
  299. * hash( vertexIndex ).greaterThan( 0.5 ).discard();
  300. * return materialColor;
  301. * } )();
  302. * ```
  303. *
  304. * @type {?Node<vec4>}
  305. * @default null
  306. */
  307. this.castShadowNode = null;
  308. /**
  309. * This node can be used to define the final output of the material.
  310. *
  311. * TODO: Explain the differences to `fragmentNode`.
  312. *
  313. * @type {?Node<vec4>}
  314. * @default null
  315. */
  316. this.outputNode = null;
  317. /**
  318. * MRT configuration is done on renderer or pass level. This node allows to
  319. * overwrite what values are written into MRT targets on material level. This
  320. * can be useful for implementing selective FX features that should only affect
  321. * specific objects.
  322. *
  323. * @type {?MRTNode}
  324. * @default null
  325. */
  326. this.mrtNode = null;
  327. /**
  328. * This node property can be used if you need complete freedom in implementing
  329. * the fragment shader. Assigning a node will replace the built-in material
  330. * logic used in the fragment stage.
  331. *
  332. * @type {?Node<vec4>}
  333. * @default null
  334. */
  335. this.fragmentNode = null;
  336. /**
  337. * This node property can be used if you need complete freedom in implementing
  338. * the vertex shader. Assigning a node will replace the built-in material logic
  339. * used in the vertex stage.
  340. *
  341. * @type {?Node<vec4>}
  342. * @default null
  343. */
  344. this.vertexNode = null;
  345. // Deprecated properties
  346. Object.defineProperty( this, 'shadowPositionNode', { // @deprecated, r176
  347. get: () => {
  348. return this.receivedShadowPositionNode;
  349. },
  350. set: ( value ) => {
  351. console.warn( 'THREE.NodeMaterial: ".shadowPositionNode" was renamed to ".receivedShadowPositionNode".' );
  352. this.receivedShadowPositionNode = value;
  353. }
  354. } );
  355. }
  356. /**
  357. * Allows to define a custom cache key that influence the material key computation
  358. * for render objects.
  359. *
  360. * @return {string} The custom cache key.
  361. */
  362. customProgramCacheKey() {
  363. return this.type + getCacheKey( this );
  364. }
  365. /**
  366. * Builds this material with the given node builder.
  367. *
  368. * @param {NodeBuilder} builder - The current node builder.
  369. */
  370. build( builder ) {
  371. this.setup( builder );
  372. }
  373. /**
  374. * Setups a node material observer with the given builder.
  375. *
  376. * @param {NodeBuilder} builder - The current node builder.
  377. * @return {NodeMaterialObserver} The node material observer.
  378. */
  379. setupObserver( builder ) {
  380. return new NodeMaterialObserver( builder );
  381. }
  382. /**
  383. * Setups the vertex and fragment stage of this node material.
  384. *
  385. * @param {NodeBuilder} builder - The current node builder.
  386. */
  387. setup( builder ) {
  388. builder.context.setupNormal = () => this.setupNormal( builder );
  389. builder.context.setupPositionView = () => this.setupPositionView( builder );
  390. builder.context.setupModelViewProjection = () => this.setupModelViewProjection( builder );
  391. const renderer = builder.renderer;
  392. const renderTarget = renderer.getRenderTarget();
  393. // < VERTEX STAGE >
  394. builder.addStack();
  395. const mvp = this.setupVertex( builder );
  396. const vertexNode = this.vertexNode || mvp;
  397. builder.stack.outputNode = vertexNode;
  398. this.setupHardwareClipping( builder );
  399. if ( this.geometryNode !== null ) {
  400. builder.stack.outputNode = builder.stack.outputNode.bypass( this.geometryNode );
  401. }
  402. builder.addFlow( 'vertex', builder.removeStack() );
  403. // < FRAGMENT STAGE >
  404. builder.addStack();
  405. let resultNode;
  406. const clippingNode = this.setupClipping( builder );
  407. if ( this.depthWrite === true || this.depthTest === true ) {
  408. // only write depth if depth buffer is configured
  409. if ( renderTarget !== null ) {
  410. if ( renderTarget.depthBuffer === true ) this.setupDepth( builder );
  411. } else {
  412. if ( renderer.depth === true ) this.setupDepth( builder );
  413. }
  414. }
  415. if ( this.fragmentNode === null ) {
  416. this.setupDiffuseColor( builder );
  417. this.setupVariants( builder );
  418. const outgoingLightNode = this.setupLighting( builder );
  419. if ( clippingNode !== null ) builder.stack.add( clippingNode );
  420. // force unsigned floats - useful for RenderTargets
  421. const basicOutput = vec4( outgoingLightNode, diffuseColor.a ).max( 0 );
  422. resultNode = this.setupOutput( builder, basicOutput );
  423. // OUTPUT NODE
  424. output.assign( resultNode );
  425. //
  426. const isCustomOutput = this.outputNode !== null;
  427. if ( isCustomOutput ) resultNode = this.outputNode;
  428. // MRT
  429. if ( renderTarget !== null ) {
  430. const mrt = renderer.getMRT();
  431. const materialMRT = this.mrtNode;
  432. if ( mrt !== null ) {
  433. if ( isCustomOutput ) output.assign( resultNode );
  434. resultNode = mrt;
  435. if ( materialMRT !== null ) {
  436. resultNode = mrt.merge( materialMRT );
  437. }
  438. } else if ( materialMRT !== null ) {
  439. resultNode = materialMRT;
  440. }
  441. }
  442. } else {
  443. let fragmentNode = this.fragmentNode;
  444. if ( fragmentNode.isOutputStructNode !== true ) {
  445. fragmentNode = vec4( fragmentNode );
  446. }
  447. resultNode = this.setupOutput( builder, fragmentNode );
  448. }
  449. builder.stack.outputNode = resultNode;
  450. builder.addFlow( 'fragment', builder.removeStack() );
  451. // < OBSERVER >
  452. builder.observer = this.setupObserver( builder );
  453. }
  454. /**
  455. * Setups the clipping node.
  456. *
  457. * @param {NodeBuilder} builder - The current node builder.
  458. * @return {ClippingNode} The clipping node.
  459. */
  460. setupClipping( builder ) {
  461. if ( builder.clippingContext === null ) return null;
  462. const { unionPlanes, intersectionPlanes } = builder.clippingContext;
  463. let result = null;
  464. if ( unionPlanes.length > 0 || intersectionPlanes.length > 0 ) {
  465. const samples = builder.renderer.samples;
  466. if ( this.alphaToCoverage && samples > 1 ) {
  467. // to be added to flow when the color/alpha value has been determined
  468. result = clippingAlpha();
  469. } else {
  470. builder.stack.add( clipping() );
  471. }
  472. }
  473. return result;
  474. }
  475. /**
  476. * Setups the hardware clipping if available on the current device.
  477. *
  478. * @param {NodeBuilder} builder - The current node builder.
  479. */
  480. setupHardwareClipping( builder ) {
  481. this.hardwareClipping = false;
  482. if ( builder.clippingContext === null ) return;
  483. const candidateCount = builder.clippingContext.unionPlanes.length;
  484. // 8 planes supported by WebGL ANGLE_clip_cull_distance and WebGPU clip-distances
  485. if ( candidateCount > 0 && candidateCount <= 8 && builder.isAvailable( 'clipDistance' ) ) {
  486. builder.stack.add( hardwareClipping() );
  487. this.hardwareClipping = true;
  488. }
  489. return;
  490. }
  491. /**
  492. * Setups the depth of this material.
  493. *
  494. * @param {NodeBuilder} builder - The current node builder.
  495. */
  496. setupDepth( builder ) {
  497. const { renderer, camera } = builder;
  498. // Depth
  499. let depthNode = this.depthNode;
  500. if ( depthNode === null ) {
  501. const mrt = renderer.getMRT();
  502. if ( mrt && mrt.has( 'depth' ) ) {
  503. depthNode = mrt.get( 'depth' );
  504. } else if ( renderer.logarithmicDepthBuffer === true ) {
  505. if ( camera.isPerspectiveCamera ) {
  506. depthNode = viewZToLogarithmicDepth( positionView.z, cameraNear, cameraFar );
  507. } else {
  508. depthNode = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar );
  509. }
  510. }
  511. }
  512. if ( depthNode !== null ) {
  513. depth.assign( depthNode ).toStack();
  514. }
  515. }
  516. /**
  517. * Setups the position node in view space. This method exists
  518. * so derived node materials can modify the implementation e.g. sprite materials.
  519. *
  520. * @param {NodeBuilder} builder - The current node builder.
  521. * @return {Node<vec3>} The position in view space.
  522. */
  523. setupPositionView( /*builder*/ ) {
  524. return modelViewMatrix.mul( positionLocal ).xyz;
  525. }
  526. /**
  527. * Setups the position in clip space.
  528. *
  529. * @param {NodeBuilder} builder - The current node builder.
  530. * @return {Node<vec4>} The position in view space.
  531. */
  532. setupModelViewProjection( /*builder*/ ) {
  533. return cameraProjectionMatrix.mul( positionView );
  534. }
  535. /**
  536. * Setups the logic for the vertex stage.
  537. *
  538. * @param {NodeBuilder} builder - The current node builder.
  539. * @return {Node<vec4>} The position in clip space.
  540. */
  541. setupVertex( builder ) {
  542. builder.addStack();
  543. this.setupPosition( builder );
  544. builder.context.vertex = builder.removeStack();
  545. return modelViewProjection;
  546. }
  547. /**
  548. * Setups the computation of the position in local space.
  549. *
  550. * @param {NodeBuilder} builder - The current node builder.
  551. * @return {Node<vec3>} The position in local space.
  552. */
  553. setupPosition( builder ) {
  554. const { object, geometry } = builder;
  555. if ( geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color ) {
  556. morphReference( object ).toStack();
  557. }
  558. if ( object.isSkinnedMesh === true ) {
  559. skinning( object ).toStack();
  560. }
  561. if ( this.displacementMap ) {
  562. const displacementMap = materialReference( 'displacementMap', 'texture' );
  563. const displacementScale = materialReference( 'displacementScale', 'float' );
  564. const displacementBias = materialReference( 'displacementBias', 'float' );
  565. positionLocal.addAssign( normalLocal.normalize().mul( ( displacementMap.x.mul( displacementScale ).add( displacementBias ) ) ) );
  566. }
  567. if ( object.isBatchedMesh ) {
  568. batch( object ).toStack();
  569. }
  570. if ( ( object.isInstancedMesh && object.instanceMatrix && object.instanceMatrix.isInstancedBufferAttribute === true ) ) {
  571. instancedMesh( object ).toStack();
  572. }
  573. if ( this.positionNode !== null ) {
  574. positionLocal.assign( this.positionNode.context( { isPositionNodeInput: true } ) );
  575. }
  576. return positionLocal;
  577. }
  578. /**
  579. * Setups the computation of the material's diffuse color.
  580. *
  581. * @param {NodeBuilder} builder - The current node builder.
  582. * @param {BufferGeometry} geometry - The geometry.
  583. */
  584. setupDiffuseColor( { object, geometry } ) {
  585. // MASK
  586. if ( this.maskNode !== null ) {
  587. bool( this.maskNode ).discard();
  588. }
  589. // COLOR
  590. let colorNode = this.colorNode ? vec4( this.colorNode ) : materialColor;
  591. // VERTEX COLORS
  592. if ( this.vertexColors === true && geometry.hasAttribute( 'color' ) ) {
  593. colorNode = colorNode.mul( vertexColor() );
  594. }
  595. // INSTANCED COLORS
  596. if ( object.instanceColor ) {
  597. const instanceColor = varyingProperty( 'vec3', 'vInstanceColor' );
  598. colorNode = instanceColor.mul( colorNode );
  599. }
  600. if ( object.isBatchedMesh && object._colorsTexture ) {
  601. const batchColor = varyingProperty( 'vec3', 'vBatchColor' );
  602. colorNode = batchColor.mul( colorNode );
  603. }
  604. // DIFFUSE COLOR
  605. diffuseColor.assign( colorNode );
  606. // OPACITY
  607. const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;
  608. diffuseColor.a.assign( diffuseColor.a.mul( opacityNode ) );
  609. // ALPHA TEST
  610. let alphaTestNode = null;
  611. if ( this.alphaTestNode !== null || this.alphaTest > 0 ) {
  612. alphaTestNode = this.alphaTestNode !== null ? float( this.alphaTestNode ) : materialAlphaTest;
  613. diffuseColor.a.lessThanEqual( alphaTestNode ).discard();
  614. }
  615. // ALPHA HASH
  616. if ( this.alphaHash === true ) {
  617. diffuseColor.a.lessThan( getAlphaHashThreshold( positionLocal ) ).discard();
  618. }
  619. // OPAQUE
  620. const isOpaque = this.transparent === false && this.blending === NormalBlending && this.alphaToCoverage === false;
  621. if ( isOpaque ) {
  622. diffuseColor.a.assign( 1.0 );
  623. } else if ( alphaTestNode === null ) {
  624. diffuseColor.a.lessThanEqual( 0 ).discard();
  625. }
  626. }
  627. /**
  628. * Abstract interface method that can be implemented by derived materials
  629. * to setup material-specific node variables.
  630. *
  631. * @abstract
  632. * @param {NodeBuilder} builder - The current node builder.
  633. */
  634. setupVariants( /*builder*/ ) {
  635. // Interface function.
  636. }
  637. /**
  638. * Setups the outgoing light node variable
  639. *
  640. * @return {Node<vec3>} The outgoing light node.
  641. */
  642. setupOutgoingLight() {
  643. return ( this.lights === true ) ? vec3( 0 ) : diffuseColor.rgb;
  644. }
  645. /**
  646. * Setups the normal node from the material.
  647. *
  648. * @return {Node<vec3>} The normal node.
  649. */
  650. setupNormal() {
  651. return this.normalNode ? vec3( this.normalNode ) : materialNormal;
  652. }
  653. /**
  654. * Setups the environment node from the material.
  655. *
  656. * @param {NodeBuilder} builder - The current node builder.
  657. * @return {Node<vec4>} The environment node.
  658. */
  659. setupEnvironment( /*builder*/ ) {
  660. let node = null;
  661. if ( this.envNode ) {
  662. node = this.envNode;
  663. } else if ( this.envMap ) {
  664. node = this.envMap.isCubeTexture ? materialReference( 'envMap', 'cubeTexture' ) : materialReference( 'envMap', 'texture' );
  665. }
  666. return node;
  667. }
  668. /**
  669. * Setups the light map node from the material.
  670. *
  671. * @param {NodeBuilder} builder - The current node builder.
  672. * @return {Node<vec3>} The light map node.
  673. */
  674. setupLightMap( builder ) {
  675. let node = null;
  676. if ( builder.material.lightMap ) {
  677. node = new IrradianceNode( materialLightMap );
  678. }
  679. return node;
  680. }
  681. /**
  682. * Setups the lights node based on the scene, environment and material.
  683. *
  684. * @param {NodeBuilder} builder - The current node builder.
  685. * @return {LightsNode} The lights node.
  686. */
  687. setupLights( builder ) {
  688. const materialLightsNode = [];
  689. //
  690. const envNode = this.setupEnvironment( builder );
  691. if ( envNode && envNode.isLightingNode ) {
  692. materialLightsNode.push( envNode );
  693. }
  694. const lightMapNode = this.setupLightMap( builder );
  695. if ( lightMapNode && lightMapNode.isLightingNode ) {
  696. materialLightsNode.push( lightMapNode );
  697. }
  698. if ( this.aoNode !== null || builder.material.aoMap ) {
  699. const aoNode = this.aoNode !== null ? this.aoNode : materialAO;
  700. materialLightsNode.push( new AONode( aoNode ) );
  701. }
  702. let lightsN = this.lightsNode || builder.lightsNode;
  703. if ( materialLightsNode.length > 0 ) {
  704. lightsN = builder.renderer.lighting.createNode( [ ...lightsN.getLights(), ...materialLightsNode ] );
  705. }
  706. return lightsN;
  707. }
  708. /**
  709. * This method should be implemented by most derived materials
  710. * since it defines the material's lighting model.
  711. *
  712. * @abstract
  713. * @param {NodeBuilder} builder - The current node builder.
  714. * @return {LightingModel} The lighting model.
  715. */
  716. setupLightingModel( /*builder*/ ) {
  717. // Interface function.
  718. }
  719. /**
  720. * Setups the outgoing light node.
  721. *
  722. * @param {NodeBuilder} builder - The current node builder.
  723. * @return {Node<vec3>} The outgoing light node.
  724. */
  725. setupLighting( builder ) {
  726. const { material } = builder;
  727. const { backdropNode, backdropAlphaNode, emissiveNode } = this;
  728. // OUTGOING LIGHT
  729. const lights = this.lights === true || this.lightsNode !== null;
  730. const lightsNode = lights ? this.setupLights( builder ) : null;
  731. let outgoingLightNode = this.setupOutgoingLight( builder );
  732. if ( lightsNode && lightsNode.getScope().hasLights ) {
  733. const lightingModel = this.setupLightingModel( builder ) || null;
  734. outgoingLightNode = lightingContext( lightsNode, lightingModel, backdropNode, backdropAlphaNode );
  735. } else if ( backdropNode !== null ) {
  736. outgoingLightNode = vec3( backdropAlphaNode !== null ? mix( outgoingLightNode, backdropNode, backdropAlphaNode ) : backdropNode );
  737. }
  738. // EMISSIVE
  739. if ( ( emissiveNode && emissiveNode.isNode === true ) || ( material.emissive && material.emissive.isColor === true ) ) {
  740. emissive.assign( vec3( emissiveNode ? emissiveNode : materialEmissive ) );
  741. outgoingLightNode = outgoingLightNode.add( emissive );
  742. }
  743. return outgoingLightNode;
  744. }
  745. /**
  746. * Setup the fog.
  747. *
  748. * @param {NodeBuilder} builder - The current node builder.
  749. * @param {Node<vec4>} outputNode - The existing output node.
  750. * @return {Node<vec4>} The output node.
  751. */
  752. setupFog( builder, outputNode ) {
  753. const fogNode = builder.fogNode;
  754. if ( fogNode ) {
  755. output.assign( outputNode );
  756. outputNode = vec4( fogNode );
  757. }
  758. return outputNode;
  759. }
  760. /**
  761. * Setups the output node.
  762. *
  763. * @param {NodeBuilder} builder - The current node builder.
  764. * @param {Node<vec4>} outputNode - The existing output node.
  765. * @return {Node<vec4>} The output node.
  766. */
  767. setupOutput( builder, outputNode ) {
  768. // FOG
  769. if ( this.fog === true ) {
  770. outputNode = this.setupFog( builder, outputNode );
  771. }
  772. return outputNode;
  773. }
  774. /**
  775. * Most classic material types have a node pendant e.g. for `MeshBasicMaterial`
  776. * there is `MeshBasicNodeMaterial`. This utility method is intended for
  777. * defining all material properties of the classic type in the node type.
  778. *
  779. * @param {Material} material - The material to copy properties with their values to this node material.
  780. */
  781. setDefaultValues( material ) {
  782. // This approach is to reuse the native refreshUniforms*
  783. // and turn available the use of features like transmission and environment in core
  784. for ( const property in material ) {
  785. const value = material[ property ];
  786. if ( this[ property ] === undefined ) {
  787. this[ property ] = value;
  788. if ( value && value.clone ) this[ property ] = value.clone();
  789. }
  790. }
  791. const descriptors = Object.getOwnPropertyDescriptors( material.constructor.prototype );
  792. for ( const key in descriptors ) {
  793. if ( Object.getOwnPropertyDescriptor( this.constructor.prototype, key ) === undefined &&
  794. descriptors[ key ].get !== undefined ) {
  795. Object.defineProperty( this.constructor.prototype, key, descriptors[ key ] );
  796. }
  797. }
  798. }
  799. /**
  800. * Serializes this material to JSON.
  801. *
  802. * @param {?(Object|string)} meta - The meta information for serialization.
  803. * @return {Object} The serialized node.
  804. */
  805. toJSON( meta ) {
  806. const isRoot = ( meta === undefined || typeof meta === 'string' );
  807. if ( isRoot ) {
  808. meta = {
  809. textures: {},
  810. images: {},
  811. nodes: {}
  812. };
  813. }
  814. const data = Material.prototype.toJSON.call( this, meta );
  815. const nodeChildren = getNodeChildren( this );
  816. data.inputNodes = {};
  817. for ( const { property, childNode } of nodeChildren ) {
  818. data.inputNodes[ property ] = childNode.toJSON( meta ).uuid;
  819. }
  820. // TODO: Copied from Object3D.toJSON
  821. function extractFromCache( cache ) {
  822. const values = [];
  823. for ( const key in cache ) {
  824. const data = cache[ key ];
  825. delete data.metadata;
  826. values.push( data );
  827. }
  828. return values;
  829. }
  830. if ( isRoot ) {
  831. const textures = extractFromCache( meta.textures );
  832. const images = extractFromCache( meta.images );
  833. const nodes = extractFromCache( meta.nodes );
  834. if ( textures.length > 0 ) data.textures = textures;
  835. if ( images.length > 0 ) data.images = images;
  836. if ( nodes.length > 0 ) data.nodes = nodes;
  837. }
  838. return data;
  839. }
  840. /**
  841. * Copies the properties of the given node material to this instance.
  842. *
  843. * @param {NodeMaterial} source - The material to copy.
  844. * @return {NodeMaterial} A reference to this node material.
  845. */
  846. copy( source ) {
  847. this.lightsNode = source.lightsNode;
  848. this.envNode = source.envNode;
  849. this.colorNode = source.colorNode;
  850. this.normalNode = source.normalNode;
  851. this.opacityNode = source.opacityNode;
  852. this.backdropNode = source.backdropNode;
  853. this.backdropAlphaNode = source.backdropAlphaNode;
  854. this.alphaTestNode = source.alphaTestNode;
  855. this.maskNode = source.maskNode;
  856. this.positionNode = source.positionNode;
  857. this.geometryNode = source.geometryNode;
  858. this.depthNode = source.depthNode;
  859. this.receivedShadowPositionNode = source.receivedShadowPositionNode;
  860. this.castShadowPositionNode = source.castShadowPositionNode;
  861. this.receivedShadowNode = source.receivedShadowNode;
  862. this.castShadowNode = source.castShadowNode;
  863. this.outputNode = source.outputNode;
  864. this.mrtNode = source.mrtNode;
  865. this.fragmentNode = source.fragmentNode;
  866. this.vertexNode = source.vertexNode;
  867. return super.copy( source );
  868. }
  869. }
  870. export default NodeMaterial;
粤ICP备19079148号