ShadowNode.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. import ShadowBaseNode, { shadowWorldPosition } from './ShadowBaseNode.js';
  2. import { float, vec2, vec3, vec4, If, int, Fn, nodeObject } from '../tsl/TSLBase.js';
  3. import { reference } from '../accessors/ReferenceNode.js';
  4. import { texture } from '../accessors/TextureNode.js';
  5. import { positionWorld } from '../accessors/Position.js';
  6. import { transformedNormalWorld } from '../accessors/Normal.js';
  7. import { mix, fract, step, max, clamp, sqrt } from '../math/MathNode.js';
  8. import { add, sub } from '../math/OperatorNode.js';
  9. import { DepthTexture } from '../../textures/DepthTexture.js';
  10. import NodeMaterial from '../../materials/nodes/NodeMaterial.js';
  11. import QuadMesh from '../../renderers/common/QuadMesh.js';
  12. import { Loop } from '../utils/LoopNode.js';
  13. import { screenCoordinate } from '../display/ScreenNode.js';
  14. import { HalfFloatType, LessCompare, RGFormat, VSMShadowMap, WebGPUCoordinateSystem } from '../../constants.js';
  15. import { renderGroup } from '../core/UniformGroupNode.js';
  16. import { viewZToLogarithmicDepth } from '../display/ViewportDepthNode.js';
  17. import { objectPosition } from '../accessors/Object3DNode.js';
  18. import { lightShadowMatrix } from '../accessors/Lights.js';
  19. const shadowMaterialLib = /*@__PURE__*/ new WeakMap();
  20. const linearDistance = /*@__PURE__*/ Fn( ( [ position, cameraNear, cameraFar ] ) => {
  21. let dist = positionWorld.sub( position ).length();
  22. dist = dist.sub( cameraNear ).div( cameraFar.sub( cameraNear ) );
  23. dist = dist.saturate(); // clamp to [ 0, 1 ]
  24. return dist;
  25. } );
  26. const linearShadowDistance = ( light ) => {
  27. const camera = light.shadow.camera;
  28. const nearDistance = reference( 'near', 'float', camera ).setGroup( renderGroup );
  29. const farDistance = reference( 'far', 'float', camera ).setGroup( renderGroup );
  30. const referencePosition = objectPosition( light );
  31. return linearDistance( referencePosition, nearDistance, farDistance );
  32. };
  33. const getShadowMaterial = ( light ) => {
  34. let material = shadowMaterialLib.get( light );
  35. if ( material === undefined ) {
  36. const depthNode = light.isPointLight ? linearShadowDistance( light ) : null;
  37. material = new NodeMaterial();
  38. material.colorNode = vec4( 0, 0, 0, 1 );
  39. material.depthNode = depthNode;
  40. material.isShadowNodeMaterial = true; // Use to avoid other overrideMaterial override material.colorNode unintentionally when using material.shadowNode
  41. material.name = 'ShadowMaterial';
  42. shadowMaterialLib.set( light, material );
  43. }
  44. return material;
  45. };
  46. /** @module ShadowNode **/
  47. /**
  48. * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map
  49. * with a binary `[0,1]` result.
  50. *
  51. * @method
  52. * @param {Object} inputs - The input parameter object.
  53. * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
  54. * @param {Node<vec3>} inputs.shadowCoord - The shadow coordinates.
  55. * @return {Node<float>} The filtering result.
  56. */
  57. export const BasicShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord } ) => {
  58. return texture( depthTexture, shadowCoord.xy ).compare( shadowCoord.z );
  59. } );
  60. /**
  61. * A shadow filtering function performing PCF filtering.
  62. *
  63. * @method
  64. * @param {Object} inputs - The input parameter object.
  65. * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
  66. * @param {Node<vec3>} inputs.shadowCoord - The shadow coordinates.
  67. * @param {LightShadow} inputs.shadow - The light shadow.
  68. * @return {Node<float>} The filtering result.
  69. */
  70. export const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow } ) => {
  71. const depthCompare = ( uv, compare ) => texture( depthTexture, uv ).compare( compare );
  72. const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
  73. const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup );
  74. const texelSize = vec2( 1 ).div( mapSize );
  75. const dx0 = texelSize.x.negate().mul( radius );
  76. const dy0 = texelSize.y.negate().mul( radius );
  77. const dx1 = texelSize.x.mul( radius );
  78. const dy1 = texelSize.y.mul( radius );
  79. const dx2 = dx0.div( 2 );
  80. const dy2 = dy0.div( 2 );
  81. const dx3 = dx1.div( 2 );
  82. const dy3 = dy1.div( 2 );
  83. return add(
  84. depthCompare( shadowCoord.xy.add( vec2( dx0, dy0 ) ), shadowCoord.z ),
  85. depthCompare( shadowCoord.xy.add( vec2( 0, dy0 ) ), shadowCoord.z ),
  86. depthCompare( shadowCoord.xy.add( vec2( dx1, dy0 ) ), shadowCoord.z ),
  87. depthCompare( shadowCoord.xy.add( vec2( dx2, dy2 ) ), shadowCoord.z ),
  88. depthCompare( shadowCoord.xy.add( vec2( 0, dy2 ) ), shadowCoord.z ),
  89. depthCompare( shadowCoord.xy.add( vec2( dx3, dy2 ) ), shadowCoord.z ),
  90. depthCompare( shadowCoord.xy.add( vec2( dx0, 0 ) ), shadowCoord.z ),
  91. depthCompare( shadowCoord.xy.add( vec2( dx2, 0 ) ), shadowCoord.z ),
  92. depthCompare( shadowCoord.xy, shadowCoord.z ),
  93. depthCompare( shadowCoord.xy.add( vec2( dx3, 0 ) ), shadowCoord.z ),
  94. depthCompare( shadowCoord.xy.add( vec2( dx1, 0 ) ), shadowCoord.z ),
  95. depthCompare( shadowCoord.xy.add( vec2( dx2, dy3 ) ), shadowCoord.z ),
  96. depthCompare( shadowCoord.xy.add( vec2( 0, dy3 ) ), shadowCoord.z ),
  97. depthCompare( shadowCoord.xy.add( vec2( dx3, dy3 ) ), shadowCoord.z ),
  98. depthCompare( shadowCoord.xy.add( vec2( dx0, dy1 ) ), shadowCoord.z ),
  99. depthCompare( shadowCoord.xy.add( vec2( 0, dy1 ) ), shadowCoord.z ),
  100. depthCompare( shadowCoord.xy.add( vec2( dx1, dy1 ) ), shadowCoord.z )
  101. ).mul( 1 / 17 );
  102. } );
  103. /**
  104. * A shadow filtering function performing PCF soft filtering.
  105. *
  106. * @method
  107. * @param {Object} inputs - The input parameter object.
  108. * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
  109. * @param {Node<vec3>} inputs.shadowCoord - The shadow coordinates.
  110. * @param {LightShadow} inputs.shadow - The light shadow.
  111. * @return {Node<float>} The filtering result.
  112. */
  113. export const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow } ) => {
  114. const depthCompare = ( uv, compare ) => texture( depthTexture, uv ).compare( compare );
  115. const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
  116. const texelSize = vec2( 1 ).div( mapSize );
  117. const dx = texelSize.x;
  118. const dy = texelSize.y;
  119. const uv = shadowCoord.xy;
  120. const f = fract( uv.mul( mapSize ).add( 0.5 ) );
  121. uv.subAssign( f.mul( texelSize ) );
  122. return add(
  123. depthCompare( uv, shadowCoord.z ),
  124. depthCompare( uv.add( vec2( dx, 0 ) ), shadowCoord.z ),
  125. depthCompare( uv.add( vec2( 0, dy ) ), shadowCoord.z ),
  126. depthCompare( uv.add( texelSize ), shadowCoord.z ),
  127. mix(
  128. depthCompare( uv.add( vec2( dx.negate(), 0 ) ), shadowCoord.z ),
  129. depthCompare( uv.add( vec2( dx.mul( 2 ), 0 ) ), shadowCoord.z ),
  130. f.x
  131. ),
  132. mix(
  133. depthCompare( uv.add( vec2( dx.negate(), dy ) ), shadowCoord.z ),
  134. depthCompare( uv.add( vec2( dx.mul( 2 ), dy ) ), shadowCoord.z ),
  135. f.x
  136. ),
  137. mix(
  138. depthCompare( uv.add( vec2( 0, dy.negate() ) ), shadowCoord.z ),
  139. depthCompare( uv.add( vec2( 0, dy.mul( 2 ) ) ), shadowCoord.z ),
  140. f.y
  141. ),
  142. mix(
  143. depthCompare( uv.add( vec2( dx, dy.negate() ) ), shadowCoord.z ),
  144. depthCompare( uv.add( vec2( dx, dy.mul( 2 ) ) ), shadowCoord.z ),
  145. f.y
  146. ),
  147. mix(
  148. mix(
  149. depthCompare( uv.add( vec2( dx.negate(), dy.negate() ) ), shadowCoord.z ),
  150. depthCompare( uv.add( vec2( dx.mul( 2 ), dy.negate() ) ), shadowCoord.z ),
  151. f.x
  152. ),
  153. mix(
  154. depthCompare( uv.add( vec2( dx.negate(), dy.mul( 2 ) ) ), shadowCoord.z ),
  155. depthCompare( uv.add( vec2( dx.mul( 2 ), dy.mul( 2 ) ) ), shadowCoord.z ),
  156. f.x
  157. ),
  158. f.y
  159. )
  160. ).mul( 1 / 9 );
  161. } );
  162. /**
  163. * A shadow filtering function performing VSM filtering.
  164. *
  165. * @method
  166. * @param {Object} inputs - The input parameter object.
  167. * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
  168. * @param {Node<vec3>} inputs.shadowCoord - The shadow coordinates.
  169. * @return {Node<float>} The filtering result.
  170. */
  171. export const VSMShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord } ) => {
  172. const occlusion = float( 1 ).toVar();
  173. const distribution = texture( depthTexture ).sample( shadowCoord.xy ).rg;
  174. const hardShadow = step( shadowCoord.z, distribution.x );
  175. If( hardShadow.notEqual( float( 1.0 ) ), () => {
  176. const distance = shadowCoord.z.sub( distribution.x );
  177. const variance = max( 0, distribution.y.mul( distribution.y ) );
  178. let softnessProbability = variance.div( variance.add( distance.mul( distance ) ) ); // Chebeyshevs inequality
  179. softnessProbability = clamp( sub( softnessProbability, 0.3 ).div( 0.95 - 0.3 ) );
  180. occlusion.assign( clamp( max( hardShadow, softnessProbability ) ) );
  181. } );
  182. return occlusion;
  183. } );
  184. /**
  185. * Represents the shader code for the first VSM render pass.
  186. *
  187. * @method
  188. * @param {Object} inputs - The input parameter object.
  189. * @param {Node<float>} inputs.samples - The number of samples
  190. * @param {Node<float>} inputs.radius - The radius.
  191. * @param {Node<float>} inputs.size - The size.
  192. * @param {TextureNode} inputs.shadowPass - A reference to the render target's depth data.
  193. * @return {Node<vec2>} The VSM output.
  194. */
  195. const VSMPassVertical = /*@__PURE__*/ Fn( ( { samples, radius, size, shadowPass } ) => {
  196. const mean = float( 0 ).toVar();
  197. const squaredMean = float( 0 ).toVar();
  198. const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) );
  199. const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( - 1 ) );
  200. Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => {
  201. const uvOffset = uvStart.add( float( i ).mul( uvStride ) );
  202. const depth = shadowPass.sample( add( screenCoordinate.xy, vec2( 0, uvOffset ).mul( radius ) ).div( size ) ).x;
  203. mean.addAssign( depth );
  204. squaredMean.addAssign( depth.mul( depth ) );
  205. } );
  206. mean.divAssign( samples );
  207. squaredMean.divAssign( samples );
  208. const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ) );
  209. return vec2( mean, std_dev );
  210. } );
  211. /**
  212. * Represents the shader code for the second VSM render pass.
  213. *
  214. * @method
  215. * @param {Object} inputs - The input parameter object.
  216. * @param {Node<float>} inputs.samples - The number of samples
  217. * @param {Node<float>} inputs.radius - The radius.
  218. * @param {Node<float>} inputs.size - The size.
  219. * @param {TextureNode} inputs.shadowPass - The result of the first VSM render pass.
  220. * @return {Node<vec2>} The VSM output.
  221. */
  222. const VSMPassHorizontal = /*@__PURE__*/ Fn( ( { samples, radius, size, shadowPass } ) => {
  223. const mean = float( 0 ).toVar();
  224. const squaredMean = float( 0 ).toVar();
  225. const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) );
  226. const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( - 1 ) );
  227. Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => {
  228. const uvOffset = uvStart.add( float( i ).mul( uvStride ) );
  229. const distribution = shadowPass.sample( add( screenCoordinate.xy, vec2( uvOffset, 0 ).mul( radius ) ).div( size ) );
  230. mean.addAssign( distribution.x );
  231. squaredMean.addAssign( add( distribution.y.mul( distribution.y ), distribution.x.mul( distribution.x ) ) );
  232. } );
  233. mean.divAssign( samples );
  234. squaredMean.divAssign( samples );
  235. const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ) );
  236. return vec2( mean, std_dev );
  237. } );
  238. const _shadowFilterLib = [ BasicShadowFilter, PCFShadowFilter, PCFSoftShadowFilter, VSMShadowFilter ];
  239. //
  240. const _quadMesh = /*@__PURE__*/ new QuadMesh();
  241. /**
  242. * Represents the default shadow implementation for lighting nodes.
  243. *
  244. * @augments ShadowBaseNode
  245. */
  246. class ShadowNode extends ShadowBaseNode {
  247. static get type() {
  248. return 'ShadowNode';
  249. }
  250. /**
  251. * Constructs a new shadow node.
  252. *
  253. * @param {Light} light - The shadow casting light.
  254. * @param {LightShadow?} [shadow=null] - An optional light shadow.
  255. */
  256. constructor( light, shadow = null ) {
  257. super( light );
  258. /**
  259. * The light shadow which defines the properties light's
  260. * shadow.
  261. *
  262. * @type {LightShadow?}
  263. * @default null
  264. */
  265. this.shadow = shadow || light.shadow;
  266. /**
  267. * A reference to the shadow map which is a render target.
  268. *
  269. * @type {RenderTarget?}
  270. * @default null
  271. */
  272. this.shadowMap = null;
  273. /**
  274. * Only relevant for VSM shadows. Render target for the
  275. * first VSM render pass.
  276. *
  277. * @type {RenderTarget?}
  278. * @default null
  279. */
  280. this.vsmShadowMapVertical = null;
  281. /**
  282. * Only relevant for VSM shadows. Render target for the
  283. * second VSM render pass.
  284. *
  285. * @type {RenderTarget?}
  286. * @default null
  287. */
  288. this.vsmShadowMapHorizontal = null;
  289. /**
  290. * Only relevant for VSM shadows. Node material which
  291. * is used to render the first VSM pass.
  292. *
  293. * @type {NodeMaterial?}
  294. * @default null
  295. */
  296. this.vsmMaterialVertical = null;
  297. /**
  298. * Only relevant for VSM shadows. Node material which
  299. * is used to render the second VSM pass.
  300. *
  301. * @type {NodeMaterial?}
  302. * @default null
  303. */
  304. this.vsmMaterialHorizontal = null;
  305. /**
  306. * A reference to the output node which defines the
  307. * final result of this shadow node.
  308. *
  309. * @type {Node?}
  310. * @private
  311. * @default null
  312. */
  313. this._node = null;
  314. /**
  315. * This flag can be used for type testing.
  316. *
  317. * @type {Boolean}
  318. * @readonly
  319. * @default true
  320. */
  321. this.isShadowNode = true;
  322. }
  323. /**
  324. * Setups the shadow filtering.
  325. *
  326. * @param {NodeBuilder} builder - A reference to the current node builder.
  327. * @param {Object} inputs - A configuration object that defines the shadow filtering.
  328. * @param {Function} inputs.filterFn - This function defines the filtering type of the shadow map e.g. PCF.
  329. * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
  330. * @param {Node<vec3>} inputs.shadowCoord - Shadow coordinates which are used to sample from the shadow map.
  331. * @param {LightShadow} inputs.shadow - The light shadow.
  332. * @return {Node<float>} The result node of the shadow filtering.
  333. */
  334. setupShadowFilter( builder, { filterFn, depthTexture, shadowCoord, shadow } ) {
  335. const frustumTest = shadowCoord.x.greaterThanEqual( 0 )
  336. .and( shadowCoord.x.lessThanEqual( 1 ) )
  337. .and( shadowCoord.y.greaterThanEqual( 0 ) )
  338. .and( shadowCoord.y.lessThanEqual( 1 ) )
  339. .and( shadowCoord.z.lessThanEqual( 1 ) );
  340. const shadowNode = filterFn( { depthTexture, shadowCoord, shadow } );
  341. return frustumTest.select( shadowNode, float( 1 ) );
  342. }
  343. /**
  344. * Setups the shadow coordinates.
  345. *
  346. * @param {NodeBuilder} builder - A reference to the current node builder.
  347. * @param {Node<vec3>} shadowPosition - A node representing the shadow position.
  348. * @return {Node<vec3>} The shadow coordinates.
  349. */
  350. setupShadowCoord( builder, shadowPosition ) {
  351. const { shadow } = this;
  352. const { renderer } = builder;
  353. const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup );
  354. let shadowCoord = shadowPosition;
  355. let coordZ;
  356. if ( shadow.camera.isOrthographicCamera || renderer.logarithmicDepthBuffer !== true ) {
  357. shadowCoord = shadowCoord.xyz.div( shadowCoord.w );
  358. coordZ = shadowCoord.z;
  359. if ( renderer.coordinateSystem === WebGPUCoordinateSystem ) {
  360. coordZ = coordZ.mul( 2 ).sub( 1 ); // WebGPU: Conversion [ 0, 1 ] to [ - 1, 1 ]
  361. }
  362. } else {
  363. const w = shadowCoord.w;
  364. shadowCoord = shadowCoord.xy.div( w ); // <-- Only divide X/Y coords since we don't need Z
  365. // The normally available "cameraNear" and "cameraFar" nodes cannot be used here because they do not get
  366. // updated to use the shadow camera. So, we have to declare our own "local" ones here.
  367. // TODO: How do we get the cameraNear/cameraFar nodes to use the shadow camera so we don't have to declare local ones here?
  368. const cameraNearLocal = reference( 'near', 'float', shadow.camera ).setGroup( renderGroup );
  369. const cameraFarLocal = reference( 'far', 'float', shadow.camera ).setGroup( renderGroup );
  370. coordZ = viewZToLogarithmicDepth( w.negate(), cameraNearLocal, cameraFarLocal );
  371. }
  372. shadowCoord = vec3(
  373. shadowCoord.x,
  374. shadowCoord.y.oneMinus(), // follow webgpu standards
  375. coordZ.add( bias )
  376. );
  377. return shadowCoord;
  378. }
  379. /**
  380. * Returns the shadow filtering function for the given shadow type.
  381. *
  382. * @param {Number} type - The shadow type.
  383. * @return {Function} The filtering function.
  384. */
  385. getShadowFilterFn( type ) {
  386. return _shadowFilterLib[ type ];
  387. }
  388. /**
  389. * Setups the shadow output node.
  390. *
  391. * @param {NodeBuilder} builder - A reference to the current node builder.
  392. * @return {Node<vec3>} The shadow output node.
  393. */
  394. setupShadow( builder ) {
  395. const { renderer } = builder;
  396. const { light, shadow } = this;
  397. const shadowMapType = renderer.shadowMap.type;
  398. const depthTexture = new DepthTexture( shadow.mapSize.width, shadow.mapSize.height );
  399. depthTexture.compareFunction = LessCompare;
  400. const shadowMap = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height );
  401. shadowMap.depthTexture = depthTexture;
  402. shadow.camera.updateProjectionMatrix();
  403. // VSM
  404. if ( shadowMapType === VSMShadowMap ) {
  405. depthTexture.compareFunction = null; // VSM does not use textureSampleCompare()/texture2DCompare()
  406. this.vsmShadowMapVertical = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType } );
  407. this.vsmShadowMapHorizontal = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType } );
  408. const shadowPassVertical = texture( depthTexture );
  409. const shadowPassHorizontal = texture( this.vsmShadowMapVertical.texture );
  410. const samples = reference( 'blurSamples', 'float', shadow ).setGroup( renderGroup );
  411. const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup );
  412. const size = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
  413. let material = this.vsmMaterialVertical || ( this.vsmMaterialVertical = new NodeMaterial() );
  414. material.fragmentNode = VSMPassVertical( { samples, radius, size, shadowPass: shadowPassVertical } ).context( builder.getSharedContext() );
  415. material.name = 'VSMVertical';
  416. material = this.vsmMaterialHorizontal || ( this.vsmMaterialHorizontal = new NodeMaterial() );
  417. material.fragmentNode = VSMPassHorizontal( { samples, radius, size, shadowPass: shadowPassHorizontal } ).context( builder.getSharedContext() );
  418. material.name = 'VSMHorizontal';
  419. }
  420. //
  421. const shadowIntensity = reference( 'intensity', 'float', shadow ).setGroup( renderGroup );
  422. const normalBias = reference( 'normalBias', 'float', shadow ).setGroup( renderGroup );
  423. const shadowPosition = lightShadowMatrix( light ).mul( shadowWorldPosition.add( transformedNormalWorld.mul( normalBias ) ) );
  424. const shadowCoord = this.setupShadowCoord( builder, shadowPosition );
  425. //
  426. const filterFn = shadow.filterNode || this.getShadowFilterFn( renderer.shadowMap.type ) || null;
  427. if ( filterFn === null ) {
  428. throw new Error( 'THREE.WebGPURenderer: Shadow map type not supported yet.' );
  429. }
  430. const shadowDepthTexture = ( shadowMapType === VSMShadowMap ) ? this.vsmShadowMapHorizontal.texture : depthTexture;
  431. const shadowNode = this.setupShadowFilter( builder, { filterFn, shadowTexture: shadowMap.texture, depthTexture: shadowDepthTexture, shadowCoord, shadow } );
  432. const shadowColor = texture( shadowMap.texture, shadowCoord );
  433. const shadowOutput = mix( 1, shadowNode.rgb.mix( shadowColor, 1 ), shadowIntensity.mul( shadowColor.a ) ).toVar();
  434. this.shadowMap = shadowMap;
  435. this.shadow.map = shadowMap;
  436. return shadowOutput;
  437. }
  438. /**
  439. * The implementation performs the setup of the output node. An output is only
  440. * produces if shadow mapping is globally enabled in the renderer.
  441. *
  442. * @param {NodeBuilder} builder - A reference to the current node builder.
  443. * @return {ShaderCallNodeInternal} The output node.
  444. */
  445. setup( builder ) {
  446. if ( builder.renderer.shadowMap.enabled === false ) return;
  447. return Fn( () => {
  448. let node = this._node;
  449. this.setupShadowPosition( builder );
  450. if ( node === null ) {
  451. this._node = node = this.setupShadow( builder );
  452. }
  453. if ( builder.material.shadowNode ) { // @deprecated, r171
  454. console.warn( 'THREE.NodeMaterial: ".shadowNode" is deprecated. Use ".castShadowNode" instead.' );
  455. }
  456. if ( builder.material.receivedShadowNode ) {
  457. node = builder.material.receivedShadowNode( node );
  458. }
  459. return node;
  460. } )();
  461. }
  462. /**
  463. * Renders the shadow. The logic of this function could be included
  464. * into {@link ShadowNode#updateShadow} however more specialized shadow
  465. * nodes might require a custom shadow map rendering. By having a
  466. * dedicated method, it's easier to overwrite the default beavior.
  467. *
  468. * @param {NodeFrme} frame - A reference to the current node frame.
  469. */
  470. renderShadow( frame ) {
  471. const { shadow, shadowMap, light } = this;
  472. const { renderer, scene } = frame;
  473. shadow.updateMatrices( light );
  474. shadowMap.setSize( shadow.mapSize.width, shadow.mapSize.height );
  475. renderer.render( scene, shadow.camera );
  476. }
  477. /**
  478. * Updates the shadow.
  479. *
  480. * @param {NodeFrme} frame - A reference to the current node frame.
  481. */
  482. updateShadow( frame ) {
  483. const { shadowMap, light, shadow } = this;
  484. const { renderer, scene, camera } = frame;
  485. const shadowType = renderer.shadowMap.type;
  486. const depthVersion = shadowMap.depthTexture.version;
  487. this._depthVersionCached = depthVersion;
  488. const currentOverrideMaterial = scene.overrideMaterial;
  489. scene.overrideMaterial = getShadowMaterial( light );
  490. shadow.camera.layers.mask = camera.layers.mask;
  491. const currentRenderTarget = renderer.getRenderTarget();
  492. const currentRenderObjectFunction = renderer.getRenderObjectFunction();
  493. const currentMRT = renderer.getMRT();
  494. renderer.setMRT( null );
  495. renderer.setRenderObjectFunction( ( object, scene, _camera, geometry, material, group, ...params ) => {
  496. if ( object.castShadow === true || ( object.receiveShadow && shadowType === VSMShadowMap ) ) {
  497. object.onBeforeShadow( renderer, object, camera, shadow.camera, geometry, scene.overrideMaterial, group );
  498. renderer.renderObject( object, scene, _camera, geometry, material, group, ...params );
  499. object.onAfterShadow( renderer, object, camera, shadow.camera, geometry, scene.overrideMaterial, group );
  500. }
  501. } );
  502. renderer.setRenderTarget( shadowMap );
  503. this.renderShadow( frame );
  504. renderer.setRenderObjectFunction( currentRenderObjectFunction );
  505. // vsm blur pass
  506. if ( light.isPointLight !== true && shadowType === VSMShadowMap ) {
  507. this.vsmPass( renderer );
  508. }
  509. renderer.setRenderTarget( currentRenderTarget );
  510. renderer.setMRT( currentMRT );
  511. scene.overrideMaterial = currentOverrideMaterial;
  512. }
  513. /**
  514. * For VSM additional render passes are required.
  515. *
  516. * @param {Renderer} renderer - A reference to the current renderer.
  517. */
  518. vsmPass( renderer ) {
  519. const { shadow } = this;
  520. this.vsmShadowMapVertical.setSize( shadow.mapSize.width, shadow.mapSize.height );
  521. this.vsmShadowMapHorizontal.setSize( shadow.mapSize.width, shadow.mapSize.height );
  522. renderer.setRenderTarget( this.vsmShadowMapVertical );
  523. _quadMesh.material = this.vsmMaterialVertical;
  524. _quadMesh.render( renderer );
  525. renderer.setRenderTarget( this.vsmShadowMapHorizontal );
  526. _quadMesh.material = this.vsmMaterialHorizontal;
  527. _quadMesh.render( renderer );
  528. }
  529. /**
  530. * Frees the internal resources of this shadow node.
  531. */
  532. dispose() {
  533. this.shadowMap.dispose();
  534. this.shadowMap = null;
  535. if ( this.vsmShadowMapVertical !== null ) {
  536. this.vsmShadowMapVertical.dispose();
  537. this.vsmShadowMapVertical = null;
  538. this.vsmMaterialVertical.dispose();
  539. this.vsmMaterialVertical = null;
  540. }
  541. if ( this.vsmShadowMapHorizontal !== null ) {
  542. this.vsmShadowMapHorizontal.dispose();
  543. this.vsmShadowMapHorizontal = null;
  544. this.vsmMaterialHorizontal.dispose();
  545. this.vsmMaterialHorizontal = null;
  546. }
  547. super.dispose();
  548. }
  549. /**
  550. * The implementation performs the update of the shadow map if necessary.
  551. *
  552. * @param {NodeFrme} frame - A reference to the current node frame.
  553. */
  554. updateBefore( frame ) {
  555. const { shadow } = this;
  556. const needsUpdate = shadow.needsUpdate || shadow.autoUpdate;
  557. if ( needsUpdate ) {
  558. this.updateShadow( frame );
  559. if ( this.shadowMap.depthTexture.version === this._depthVersionCached ) {
  560. shadow.needsUpdate = false;
  561. }
  562. }
  563. }
  564. }
  565. export default ShadowNode;
  566. /**
  567. * Factory method for creating an instance of `ShadowNode`.
  568. *
  569. * @method
  570. * @param {Light} light - The shadow casting light.
  571. * @param {LightShadow} shadow - The light shadow.
  572. * @return {ShadowNode} The created shadow node.
  573. */
  574. export const shadow = ( light, shadow ) => nodeObject( new ShadowNode( light, shadow ) );
粤ICP备19079148号