NodeMaterial.js 30 KB

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