OutlineNode.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. import { DepthTexture, FloatType, RenderTarget, Vector2, TempNode, QuadMesh, NodeMaterial, RendererUtils, NodeUpdateType } from 'three/webgpu';
  2. import { Loop, int, exp, min, float, mul, uv, vec2, vec3, Fn, textureSize, orthographicDepthToViewZ, screenUV, nodeObject, uniform, vec4, passTexture, texture, perspectiveDepthToViewZ, positionView, reference } from 'three/tsl';
  3. const _quadMesh = /*@__PURE__*/ new QuadMesh();
  4. const _size = /*@__PURE__*/ new Vector2();
  5. const _BLUR_DIRECTION_X = /*@__PURE__*/ new Vector2( 1.0, 0.0 );
  6. const _BLUR_DIRECTION_Y = /*@__PURE__*/ new Vector2( 0.0, 1.0 );
  7. let _rendererState;
  8. /**
  9. * Post processing node for rendering outlines around selected objects. The node
  10. * gives you great flexibility in composing the final outline look depending on
  11. * your requirements.
  12. * ```js
  13. * const postProcessing = new THREE.PostProcessing( renderer );
  14. *
  15. * const scenePass = pass( scene, camera );
  16. *
  17. * // outline parameter
  18. *
  19. * const edgeStrength = uniform( 3.0 );
  20. * const edgeGlow = uniform( 0.0 );
  21. * const edgeThickness = uniform( 1.0 );
  22. * const visibleEdgeColor = uniform( new THREE.Color( 0xffffff ) );
  23. * const hiddenEdgeColor = uniform( new THREE.Color( 0x4e3636 ) );
  24. *
  25. * outlinePass = outline( scene, camera, {
  26. * selectedObjects,
  27. * edgeGlow,
  28. * edgeThickness
  29. * } );
  30. *
  31. * // compose custom outline
  32. *
  33. * const { visibleEdge, hiddenEdge } = outlinePass;
  34. * const outlineColor = visibleEdge.mul( visibleEdgeColor ).add( hiddenEdge.mul( hiddenEdgeColor ) ).mul( edgeStrength );
  35. *
  36. * postProcessing.outputNode = outlineColor.add( scenePass );
  37. * ```
  38. *
  39. * @augments TempNode
  40. * @three_import import { outline } from 'three/addons/tsl/display/OutlineNode.js';
  41. */
  42. class OutlineNode extends TempNode {
  43. static get type() {
  44. return 'OutlineNode';
  45. }
  46. /**
  47. * Constructs a new outline node.
  48. *
  49. * @param {Scene} scene - A reference to the scene.
  50. * @param {Camera} camera - The camera the scene is rendered with.
  51. * @param {Object} params - The configuration parameters.
  52. * @param {Array<Object3D>} params.selectedObjects - An array of selected objects.
  53. * @param {Node<float>} [params.edgeThickness=float(1)] - The thickness of the edges.
  54. * @param {Node<float>} [params.edgeGlow=float(0)] - Can be used for an animated glow/pulse effects.
  55. * @param {number} [params.downSampleRatio=2] - The downsample ratio.
  56. */
  57. constructor( scene, camera, params = {} ) {
  58. super( 'vec4' );
  59. const {
  60. selectedObjects = [],
  61. edgeThickness = float( 1 ),
  62. edgeGlow = float( 0 ),
  63. downSampleRatio = 2
  64. } = params;
  65. /**
  66. * A reference to the scene.
  67. *
  68. * @type {Scene}
  69. */
  70. this.scene = scene;
  71. /**
  72. * The camera the scene is rendered with.
  73. *
  74. * @type {Camera}
  75. */
  76. this.camera = camera;
  77. /**
  78. * An array of selected objects.
  79. *
  80. * @type {Array<Object3D>}
  81. */
  82. this.selectedObjects = selectedObjects;
  83. /**
  84. * The thickness of the edges.
  85. *
  86. * @type {Node<float>}
  87. */
  88. this.edgeThicknessNode = nodeObject( edgeThickness );
  89. /**
  90. * Can be used for an animated glow/pulse effect.
  91. *
  92. * @type {Node<float>}
  93. */
  94. this.edgeGlowNode = nodeObject( edgeGlow );
  95. /**
  96. * The downsample ratio.
  97. *
  98. * @type {number}
  99. * @default 2
  100. */
  101. this.downSampleRatio = downSampleRatio;
  102. /**
  103. * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders
  104. * its effect once per frame in `updateBefore()`.
  105. *
  106. * @type {string}
  107. * @default 'frame'
  108. */
  109. this.updateBeforeType = NodeUpdateType.FRAME;
  110. // render targets
  111. /**
  112. * The render target for the depth pre-pass.
  113. *
  114. * @private
  115. * @type {RenderTarget}
  116. */
  117. this._renderTargetDepthBuffer = new RenderTarget();
  118. this._renderTargetDepthBuffer.depthTexture = new DepthTexture();
  119. this._renderTargetDepthBuffer.depthTexture.type = FloatType;
  120. /**
  121. * The render target for the mask pass.
  122. *
  123. * @private
  124. * @type {RenderTarget}
  125. */
  126. this._renderTargetMaskBuffer = new RenderTarget();
  127. /**
  128. * The render target for the mask downsample.
  129. *
  130. * @private
  131. * @type {RenderTarget}
  132. */
  133. this._renderTargetMaskDownSampleBuffer = new RenderTarget( 1, 1, { depthBuffer: false } );
  134. /**
  135. * The first render target for the edge detection.
  136. *
  137. * @private
  138. * @type {RenderTarget}
  139. */
  140. this._renderTargetEdgeBuffer1 = new RenderTarget( 1, 1, { depthBuffer: false } );
  141. /**
  142. * The second render target for the edge detection.
  143. *
  144. * @private
  145. * @type {RenderTarget}
  146. */
  147. this._renderTargetEdgeBuffer2 = new RenderTarget( 1, 1, { depthBuffer: false } );
  148. /**
  149. * The first render target for the blur pass.
  150. *
  151. * @private
  152. * @type {RenderTarget}
  153. */
  154. this._renderTargetBlurBuffer1 = new RenderTarget( 1, 1, { depthBuffer: false } );
  155. /**
  156. * The second render target for the blur pass.
  157. *
  158. * @private
  159. * @type {RenderTarget}
  160. */
  161. this._renderTargetBlurBuffer2 = new RenderTarget( 1, 1, { depthBuffer: false } );
  162. /**
  163. * The render target for the final composite.
  164. *
  165. * @private
  166. * @type {RenderTarget}
  167. */
  168. this._renderTargetComposite = new RenderTarget( 1, 1, { depthBuffer: false } );
  169. // uniforms
  170. /**
  171. * Represents the near value of the scene's camera.
  172. *
  173. * @private
  174. * @type {ReferenceNode<float>}
  175. */
  176. this._cameraNear = reference( 'near', 'float', camera );
  177. /**
  178. * Represents the far value of the scene's camera.
  179. *
  180. * @private
  181. * @type {ReferenceNode<float>}
  182. */
  183. this._cameraFar = reference( 'far', 'float', camera );
  184. /**
  185. * Uniform that represents the blur direction of the pass.
  186. *
  187. * @private
  188. * @type {UniformNode<vec2>}
  189. */
  190. this._blurDirection = uniform( new Vector2() );
  191. /**
  192. * Texture node that holds the data from the depth pre-pass.
  193. *
  194. * @private
  195. * @type {TextureNode}
  196. */
  197. this._depthTextureUniform = texture( this._renderTargetDepthBuffer.depthTexture );
  198. /**
  199. * Texture node that holds the data from the mask pass.
  200. *
  201. * @private
  202. * @type {TextureNode}
  203. */
  204. this._maskTextureUniform = texture( this._renderTargetMaskBuffer.texture );
  205. /**
  206. * Texture node that holds the data from the mask downsample pass.
  207. *
  208. * @private
  209. * @type {TextureNode}
  210. */
  211. this._maskTextureDownsSampleUniform = texture( this._renderTargetMaskDownSampleBuffer.texture );
  212. /**
  213. * Texture node that holds the data from the first edge detection pass.
  214. *
  215. * @private
  216. * @type {TextureNode}
  217. */
  218. this._edge1TextureUniform = texture( this._renderTargetEdgeBuffer1.texture );
  219. /**
  220. * Texture node that holds the data from the second edge detection pass.
  221. *
  222. * @private
  223. * @type {TextureNode}
  224. */
  225. this._edge2TextureUniform = texture( this._renderTargetEdgeBuffer2.texture );
  226. /**
  227. * Texture node that holds the current blurred color data.
  228. *
  229. * @private
  230. * @type {TextureNode}
  231. */
  232. this._blurColorTextureUniform = texture( this._renderTargetEdgeBuffer1.texture );
  233. // constants
  234. /**
  235. * Visible edge color.
  236. *
  237. * @private
  238. * @type {Node<vec3>}
  239. */
  240. this._visibleEdgeColor = vec3( 1, 0, 0 );
  241. /**
  242. * Hidden edge color.
  243. *
  244. * @private
  245. * @type {Node<vec3>}
  246. */
  247. this._hiddenEdgeColor = vec3( 0, 1, 0 );
  248. // materials
  249. /**
  250. * The material for the depth pre-pass.
  251. *
  252. * @private
  253. * @type {NodeMaterial}
  254. */
  255. this._depthMaterial = new NodeMaterial();
  256. this._depthMaterial.fragmentNode = vec4( 0, 0, 0, 1 );
  257. this._depthMaterial.name = 'OutlineNode.depth';
  258. /**
  259. * The material for preparing the mask.
  260. *
  261. * @private
  262. * @type {NodeMaterial}
  263. */
  264. this._prepareMaskMaterial = new NodeMaterial();
  265. this._prepareMaskMaterial.name = 'OutlineNode.prepareMask';
  266. /**
  267. * The copy material
  268. *
  269. * @private
  270. * @type {NodeMaterial}
  271. */
  272. this._materialCopy = new NodeMaterial();
  273. this._materialCopy.name = 'OutlineNode.copy';
  274. /**
  275. * The edge detection material.
  276. *
  277. * @private
  278. * @type {NodeMaterial}
  279. */
  280. this._edgeDetectionMaterial = new NodeMaterial();
  281. this._edgeDetectionMaterial.name = 'OutlineNode.edgeDetection';
  282. /**
  283. * The material that is used to render in the blur pass.
  284. *
  285. * @private
  286. * @type {NodeMaterial}
  287. */
  288. this._separableBlurMaterial = new NodeMaterial();
  289. this._separableBlurMaterial.name = 'OutlineNode.separableBlur';
  290. /**
  291. * The material that is used to render in the blur pass.
  292. *
  293. * @private
  294. * @type {NodeMaterial}
  295. */
  296. this._separableBlurMaterial2 = new NodeMaterial();
  297. this._separableBlurMaterial2.name = 'OutlineNode.separableBlur2';
  298. /**
  299. * The final composite material.
  300. *
  301. * @private
  302. * @type {NodeMaterial}
  303. */
  304. this._compositeMaterial = new NodeMaterial();
  305. this._compositeMaterial.name = 'OutlineNode.composite';
  306. /**
  307. * A set to cache selected objects in the scene.
  308. *
  309. * @private
  310. * @type {Set<Object3D>}
  311. */
  312. this._selectionCache = new Set();
  313. /**
  314. * The result of the effect is represented as a separate texture node.
  315. *
  316. * @private
  317. * @type {PassTextureNode}
  318. */
  319. this._textureNode = passTexture( this, this._renderTargetComposite.texture );
  320. }
  321. /**
  322. * A mask value that represents the visible edge.
  323. *
  324. * @return {Node<float>} The visible edge.
  325. */
  326. get visibleEdge() {
  327. return this.r;
  328. }
  329. /**
  330. * A mask value that represents the hidden edge.
  331. *
  332. * @return {Node<float>} The hidden edge.
  333. */
  334. get hiddenEdge() {
  335. return this.g;
  336. }
  337. /**
  338. * Returns the result of the effect as a texture node.
  339. *
  340. * @return {PassTextureNode} A texture node that represents the result of the effect.
  341. */
  342. getTextureNode() {
  343. return this._textureNode;
  344. }
  345. /**
  346. * Sets the size of the effect.
  347. *
  348. * @param {number} width - The width of the effect.
  349. * @param {number} height - The height of the effect.
  350. */
  351. setSize( width, height ) {
  352. this._renderTargetDepthBuffer.setSize( width, height );
  353. this._renderTargetMaskBuffer.setSize( width, height );
  354. this._renderTargetComposite.setSize( width, height );
  355. // downsample 1
  356. let resx = Math.round( width / this.downSampleRatio );
  357. let resy = Math.round( height / this.downSampleRatio );
  358. this._renderTargetMaskDownSampleBuffer.setSize( resx, resy );
  359. this._renderTargetEdgeBuffer1.setSize( resx, resy );
  360. this._renderTargetBlurBuffer1.setSize( resx, resy );
  361. // downsample 2
  362. resx = Math.round( resx / 2 );
  363. resy = Math.round( resy / 2 );
  364. this._renderTargetEdgeBuffer2.setSize( resx, resy );
  365. this._renderTargetBlurBuffer2.setSize( resx, resy );
  366. }
  367. /**
  368. * This method is used to render the effect once per frame.
  369. *
  370. * @param {NodeFrame} frame - The current node frame.
  371. */
  372. updateBefore( frame ) {
  373. const { renderer } = frame;
  374. const { camera, scene } = this;
  375. _rendererState = RendererUtils.resetRendererAndSceneState( renderer, scene, _rendererState );
  376. //
  377. const size = renderer.getDrawingBufferSize( _size );
  378. this.setSize( size.width, size.height );
  379. //
  380. renderer.setClearColor( 0xffffff, 1 );
  381. this._updateSelectionCache();
  382. // 1. Draw non-selected objects in the depth buffer
  383. scene.overrideMaterial = this._depthMaterial;
  384. renderer.setRenderTarget( this._renderTargetDepthBuffer );
  385. renderer.setRenderObjectFunction( ( object, ...params ) => {
  386. if ( this._selectionCache.has( object ) === false ) {
  387. renderer.renderObject( object, ...params );
  388. }
  389. } );
  390. renderer.render( scene, camera );
  391. // 2. Draw only the selected objects by comparing the depth buffer of non-selected objects
  392. scene.overrideMaterial = this._prepareMaskMaterial;
  393. renderer.setRenderTarget( this._renderTargetMaskBuffer );
  394. renderer.setRenderObjectFunction( ( object, ...params ) => {
  395. if ( this._selectionCache.has( object ) === true ) {
  396. renderer.renderObject( object, ...params );
  397. }
  398. } );
  399. renderer.render( scene, camera );
  400. //
  401. renderer.setRenderObjectFunction( _rendererState.renderObjectFunction );
  402. this._selectionCache.clear();
  403. // 3. Downsample to (at least) half resolution
  404. _quadMesh.material = this._materialCopy;
  405. renderer.setRenderTarget( this._renderTargetMaskDownSampleBuffer );
  406. _quadMesh.render( renderer );
  407. // 4. Perform edge detection (half resolution)
  408. _quadMesh.material = this._edgeDetectionMaterial;
  409. renderer.setRenderTarget( this._renderTargetEdgeBuffer1 );
  410. _quadMesh.render( renderer );
  411. // 5. Apply blur (half resolution)
  412. this._blurColorTextureUniform.value = this._renderTargetEdgeBuffer1.texture;
  413. this._blurDirection.value.copy( _BLUR_DIRECTION_X );
  414. _quadMesh.material = this._separableBlurMaterial;
  415. renderer.setRenderTarget( this._renderTargetBlurBuffer1 );
  416. _quadMesh.render( renderer );
  417. this._blurColorTextureUniform.value = this._renderTargetBlurBuffer1.texture;
  418. this._blurDirection.value.copy( _BLUR_DIRECTION_Y );
  419. renderer.setRenderTarget( this._renderTargetEdgeBuffer1 );
  420. _quadMesh.render( renderer );
  421. // 6. Apply blur (quarter resolution)
  422. this._blurColorTextureUniform.value = this._renderTargetEdgeBuffer1.texture;
  423. this._blurDirection.value.copy( _BLUR_DIRECTION_X );
  424. _quadMesh.material = this._separableBlurMaterial2;
  425. renderer.setRenderTarget( this._renderTargetBlurBuffer2 );
  426. _quadMesh.render( renderer );
  427. this._blurColorTextureUniform.value = this._renderTargetBlurBuffer2.texture;
  428. this._blurDirection.value.copy( _BLUR_DIRECTION_Y );
  429. renderer.setRenderTarget( this._renderTargetEdgeBuffer2 );
  430. _quadMesh.render( renderer );
  431. // 7. Composite
  432. _quadMesh.material = this._compositeMaterial;
  433. renderer.setRenderTarget( this._renderTargetComposite );
  434. _quadMesh.render( renderer );
  435. // restore
  436. RendererUtils.restoreRendererAndSceneState( renderer, scene, _rendererState );
  437. }
  438. /**
  439. * This method is used to setup the effect's TSL code.
  440. *
  441. * @param {NodeBuilder} builder - The current node builder.
  442. * @return {PassTextureNode}
  443. */
  444. setup() {
  445. // prepare mask material
  446. const prepareMask = () => {
  447. const depth = this._depthTextureUniform.sample( screenUV );
  448. let viewZNode;
  449. if ( this.camera.isPerspectiveCamera ) {
  450. viewZNode = perspectiveDepthToViewZ( depth, this._cameraNear, this._cameraFar );
  451. } else {
  452. viewZNode = orthographicDepthToViewZ( depth, this._cameraNear, this._cameraFar );
  453. }
  454. const depthTest = positionView.z.lessThanEqual( viewZNode ).select( 1, 0 );
  455. return vec4( 0.0, depthTest, 1.0, 1.0 );
  456. };
  457. this._prepareMaskMaterial.fragmentNode = prepareMask();
  458. this._prepareMaskMaterial.needsUpdate = true;
  459. // copy material
  460. this._materialCopy.fragmentNode = this._maskTextureUniform;
  461. this._materialCopy.needsUpdate = true;
  462. // edge detection material
  463. const edgeDetection = Fn( () => {
  464. const resolution = textureSize( this._maskTextureDownsSampleUniform );
  465. const invSize = vec2( 1 ).div( resolution ).toVar();
  466. const uvOffset = vec4( 1.0, 0.0, 0.0, 1.0 ).mul( vec4( invSize, invSize ) );
  467. const uvNode = uv();
  468. const c1 = this._maskTextureDownsSampleUniform.sample( uvNode.add( uvOffset.xy ) ).toVar();
  469. const c2 = this._maskTextureDownsSampleUniform.sample( uvNode.sub( uvOffset.xy ) ).toVar();
  470. const c3 = this._maskTextureDownsSampleUniform.sample( uvNode.add( uvOffset.yw ) ).toVar();
  471. const c4 = this._maskTextureDownsSampleUniform.sample( uvNode.sub( uvOffset.yw ) ).toVar();
  472. const diff1 = mul( c1.r.sub( c2.r ), 0.5 );
  473. const diff2 = mul( c3.r.sub( c4.r ), 0.5 );
  474. const d = vec2( diff1, diff2 ).length();
  475. const a1 = min( c1.g, c2.g );
  476. const a2 = min( c3.g, c4.g );
  477. const visibilityFactor = min( a1, a2 );
  478. const edgeColor = visibilityFactor.oneMinus().greaterThan( 0.001 ).select( this._visibleEdgeColor, this._hiddenEdgeColor );
  479. return vec4( edgeColor, 1 ).mul( d );
  480. } );
  481. this._edgeDetectionMaterial.fragmentNode = edgeDetection();
  482. this._edgeDetectionMaterial.needsUpdate = true;
  483. // separable blur material
  484. const MAX_RADIUS = 4;
  485. const gaussianPdf = Fn( ( [ x, sigma ] ) => {
  486. return float( 0.39894 ).mul( exp( float( - 0.5 ).mul( x ).mul( x ).div( sigma.mul( sigma ) ) ).div( sigma ) );
  487. } );
  488. const separableBlur = Fn( ( [ kernelRadius ] ) => {
  489. const resolution = textureSize( this._maskTextureDownsSampleUniform );
  490. const invSize = vec2( 1 ).div( resolution ).toVar();
  491. const uvNode = uv();
  492. const sigma = kernelRadius.div( 2 ).toVar();
  493. const weightSum = gaussianPdf( 0, sigma ).toVar();
  494. const diffuseSum = this._blurColorTextureUniform.sample( uvNode ).mul( weightSum ).toVar();
  495. const delta = this._blurDirection.mul( invSize ).mul( kernelRadius ).div( MAX_RADIUS ).toVar();
  496. const uvOffset = delta.toVar();
  497. Loop( { start: int( 1 ), end: int( MAX_RADIUS ), type: 'int', condition: '<=' }, ( { i } ) => {
  498. const x = kernelRadius.mul( float( i ) ).div( MAX_RADIUS );
  499. const w = gaussianPdf( x, sigma );
  500. const sample1 = this._blurColorTextureUniform.sample( uvNode.add( uvOffset ) );
  501. const sample2 = this._blurColorTextureUniform.sample( uvNode.sub( uvOffset ) );
  502. diffuseSum.addAssign( sample1.add( sample2 ).mul( w ) );
  503. weightSum.addAssign( w.mul( 2 ) );
  504. uvOffset.addAssign( delta );
  505. } );
  506. return diffuseSum.div( weightSum );
  507. } );
  508. this._separableBlurMaterial.fragmentNode = separableBlur( this.edgeThicknessNode );
  509. this._separableBlurMaterial.needsUpdate = true;
  510. this._separableBlurMaterial2.fragmentNode = separableBlur( MAX_RADIUS );
  511. this._separableBlurMaterial2.needsUpdate = true;
  512. // composite material
  513. const composite = Fn( () => {
  514. const edgeValue1 = this._edge1TextureUniform;
  515. const edgeValue2 = this._edge2TextureUniform;
  516. const maskColor = this._maskTextureUniform;
  517. const edgeValue = edgeValue1.add( edgeValue2.mul( this.edgeGlowNode ) );
  518. return maskColor.r.mul( edgeValue );
  519. } );
  520. this._compositeMaterial.fragmentNode = composite();
  521. this._compositeMaterial.needsUpdate = true;
  522. return this._textureNode;
  523. }
  524. /**
  525. * Frees internal resources. This method should be called
  526. * when the effect is no longer required.
  527. */
  528. dispose() {
  529. this.selectedObjects.length = 0;
  530. this._renderTargetDepthBuffer.dispose();
  531. this._renderTargetMaskBuffer.dispose();
  532. this._renderTargetMaskDownSampleBuffer.dispose();
  533. this._renderTargetEdgeBuffer1.dispose();
  534. this._renderTargetEdgeBuffer2.dispose();
  535. this._renderTargetBlurBuffer1.dispose();
  536. this._renderTargetBlurBuffer2.dispose();
  537. this._renderTargetComposite.dispose();
  538. this._depthMaterial.dispose();
  539. this._prepareMaskMaterial.dispose();
  540. this._materialCopy.dispose();
  541. this._edgeDetectionMaterial.dispose();
  542. this._separableBlurMaterial.dispose();
  543. this._separableBlurMaterial2.dispose();
  544. this._compositeMaterial.dispose();
  545. }
  546. /**
  547. * Updates the selection cache based on the selected objects.
  548. *
  549. * @private
  550. */
  551. _updateSelectionCache() {
  552. for ( let i = 0; i < this.selectedObjects.length; i ++ ) {
  553. const selectedObject = this.selectedObjects[ i ];
  554. selectedObject.traverse( ( object ) => {
  555. if ( object.isMesh ) this._selectionCache.add( object );
  556. } );
  557. }
  558. }
  559. }
  560. export default OutlineNode;
  561. /**
  562. * TSL function for creating an outline effect around selected objects.
  563. *
  564. * @tsl
  565. * @function
  566. * @param {Scene} scene - A reference to the scene.
  567. * @param {Camera} camera - The camera the scene is rendered with.
  568. * @param {Object} params - The configuration parameters.
  569. * @param {Array<Object3D>} params.selectedObjects - An array of selected objects.
  570. * @param {Node<float>} [params.edgeThickness=float(1)] - The thickness of the edges.
  571. * @param {Node<float>} [params.edgeGlow=float(0)] - Can be used for animated glow/pulse effects.
  572. * @param {number} [params.downSampleRatio=2] - The downsample ratio.
  573. * @returns {OutlineNode}
  574. */
  575. export const outline = ( scene, camera, params ) => nodeObject( new OutlineNode( scene, camera, params ) );