OutlineNode.js 19 KB

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