OutlineNode.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812
  1. import { DepthTexture, FloatType, RenderTarget, Vector2, TempNode, QuadMesh, NodeMaterial, SpriteNodeMaterial, 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, color } 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 renderPipeline = new THREE.RenderPipeline( 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. * renderPipeline.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.colorNode = color( 0, 0, 0 );
  257. this._depthMaterial.name = 'OutlineNode.depth';
  258. this._depthSpriteMaterial = new SpriteNodeMaterial();
  259. this._depthSpriteMaterial.colorNode = color( 0, 0, 0 );
  260. this._depthSpriteMaterial.name = 'OutlineNode.depthSprite';
  261. /**
  262. * The material for preparing the mask.
  263. *
  264. * @private
  265. * @type {NodeMaterial}
  266. */
  267. this._prepareMaskMaterial = new NodeMaterial();
  268. this._prepareMaskMaterial.name = 'OutlineNode.prepareMask';
  269. this._prepareMaskSpriteMaterial = new SpriteNodeMaterial();
  270. this._prepareMaskSpriteMaterial.name = 'OutlineNode.prepareMaskSprite';
  271. /**
  272. * The copy material
  273. *
  274. * @private
  275. * @type {NodeMaterial}
  276. */
  277. this._materialCopy = new NodeMaterial();
  278. this._materialCopy.name = 'OutlineNode.copy';
  279. /**
  280. * The edge detection material.
  281. *
  282. * @private
  283. * @type {NodeMaterial}
  284. */
  285. this._edgeDetectionMaterial = new NodeMaterial();
  286. this._edgeDetectionMaterial.name = 'OutlineNode.edgeDetection';
  287. /**
  288. * The material that is used to render in the blur pass.
  289. *
  290. * @private
  291. * @type {NodeMaterial}
  292. */
  293. this._separableBlurMaterial = new NodeMaterial();
  294. this._separableBlurMaterial.name = 'OutlineNode.separableBlur';
  295. /**
  296. * The material that is used to render in the blur pass.
  297. *
  298. * @private
  299. * @type {NodeMaterial}
  300. */
  301. this._separableBlurMaterial2 = new NodeMaterial();
  302. this._separableBlurMaterial2.name = 'OutlineNode.separableBlur2';
  303. /**
  304. * The final composite material.
  305. *
  306. * @private
  307. * @type {NodeMaterial}
  308. */
  309. this._compositeMaterial = new NodeMaterial();
  310. this._compositeMaterial.name = 'OutlineNode.composite';
  311. /**
  312. * A set to cache selected objects in the scene.
  313. *
  314. * @private
  315. * @type {Set<Object3D>}
  316. */
  317. this._selectionCache = new Set();
  318. /**
  319. * The number of selected objects from the previous frame. Used to detect
  320. * the transition to an empty selection so the composite render target can
  321. * be cleared once and avoid leaving a stale outline on screen.
  322. *
  323. * @private
  324. * @type {number}
  325. * @default 0
  326. */
  327. this._lastSelectionCount = 0;
  328. /**
  329. * The result of the effect is represented as a separate texture node.
  330. *
  331. * @private
  332. * @type {PassTextureNode}
  333. */
  334. this._textureNode = passTexture( this, this._renderTargetComposite.texture );
  335. }
  336. /**
  337. * A mask value that represents the visible edge.
  338. *
  339. * @return {Node<float>} The visible edge.
  340. */
  341. get visibleEdge() {
  342. return this.r;
  343. }
  344. /**
  345. * A mask value that represents the hidden edge.
  346. *
  347. * @return {Node<float>} The hidden edge.
  348. */
  349. get hiddenEdge() {
  350. return this.g;
  351. }
  352. /**
  353. * Returns the result of the effect as a texture node.
  354. *
  355. * @return {PassTextureNode} A texture node that represents the result of the effect.
  356. */
  357. getTextureNode() {
  358. return this._textureNode;
  359. }
  360. /**
  361. * Sets the size of the effect.
  362. *
  363. * @param {number} width - The width of the effect.
  364. * @param {number} height - The height of the effect.
  365. */
  366. setSize( width, height ) {
  367. this._renderTargetDepthBuffer.setSize( width, height );
  368. this._renderTargetMaskBuffer.setSize( width, height );
  369. this._renderTargetComposite.setSize( width, height );
  370. // downsample 1
  371. let resx = Math.round( width / this.downSampleRatio );
  372. let resy = Math.round( height / this.downSampleRatio );
  373. this._renderTargetMaskDownSampleBuffer.setSize( resx, resy );
  374. this._renderTargetEdgeBuffer1.setSize( resx, resy );
  375. this._renderTargetBlurBuffer1.setSize( resx, resy );
  376. // downsample 2
  377. resx = Math.round( resx / 2 );
  378. resy = Math.round( resy / 2 );
  379. this._renderTargetEdgeBuffer2.setSize( resx, resy );
  380. this._renderTargetBlurBuffer2.setSize( resx, resy );
  381. }
  382. /**
  383. * This method is used to render the effect once per frame.
  384. *
  385. * @param {NodeFrame} frame - The current node frame.
  386. */
  387. updateBefore( frame ) {
  388. const { renderer } = frame;
  389. const { camera, scene } = this;
  390. this._updateSelectionCache();
  391. // If no objects are selected, all subsequent passes can be skipped since
  392. // the outline would be empty anyway. The composite render target is cleared
  393. // once when transitioning to an empty selection so a previously rendered
  394. // outline does not linger on screen.
  395. if ( this._selectionCache.size === 0 ) {
  396. if ( this._lastSelectionCount > 0 ) {
  397. _rendererState = RendererUtils.resetRendererState( renderer, _rendererState );
  398. renderer.setRenderTarget( this._renderTargetComposite );
  399. renderer.setClearColor( 0x000000, 0 );
  400. renderer.clear();
  401. RendererUtils.restoreRendererState( renderer, _rendererState );
  402. this._lastSelectionCount = 0;
  403. }
  404. return;
  405. }
  406. this._lastSelectionCount = this._selectionCache.size;
  407. _rendererState = RendererUtils.resetRendererAndSceneState( renderer, scene, _rendererState );
  408. //
  409. const size = renderer.getDrawingBufferSize( _size );
  410. this.setSize( size.width, size.height );
  411. //
  412. renderer.setClearColor( 0xffffff, 1 );
  413. const currentSceneName = scene.name;
  414. // 1. Draw non-selected objects in the depth buffer
  415. renderer.setRenderTarget( this._renderTargetDepthBuffer );
  416. renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode, clippingContext ) => {
  417. if ( this._selectionCache.has( object ) === false ) {
  418. const overrideMaterial = object.isSprite ? this._depthSpriteMaterial : this._depthMaterial;
  419. renderer.renderObject( object, scene, camera, geometry, overrideMaterial, group, lightsNode, clippingContext );
  420. }
  421. } );
  422. scene.name = 'Outline [ Non-Selected Objects Pass ]';
  423. renderer.render( scene, camera );
  424. // 2. Draw only the selected objects by comparing the depth buffer of non-selected objects
  425. renderer.setRenderTarget( this._renderTargetMaskBuffer );
  426. renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode, clippingContext ) => {
  427. if ( this._selectionCache.has( object ) === true ) {
  428. const overrideMaterial = object.isSprite ? this._prepareMaskSpriteMaterial : this._prepareMaskMaterial;
  429. renderer.renderObject( object, scene, camera, geometry, overrideMaterial, group, lightsNode, clippingContext );
  430. }
  431. } );
  432. scene.name = 'Outline [ Selected Objects Pass ]';
  433. renderer.render( scene, camera );
  434. //
  435. renderer.setRenderObjectFunction( _rendererState.renderObjectFunction );
  436. this._selectionCache.clear();
  437. scene.name = currentSceneName;
  438. // 3. Downsample to (at least) half resolution
  439. _quadMesh.material = this._materialCopy;
  440. _quadMesh.name = 'Outline [ Downsample ]';
  441. renderer.setRenderTarget( this._renderTargetMaskDownSampleBuffer );
  442. _quadMesh.render( renderer );
  443. // 4. Perform edge detection (half resolution)
  444. _quadMesh.material = this._edgeDetectionMaterial;
  445. _quadMesh.name = 'Outline [ Edge Detection ]';
  446. renderer.setRenderTarget( this._renderTargetEdgeBuffer1 );
  447. _quadMesh.render( renderer );
  448. // 5. Apply blur (half resolution)
  449. this._blurColorTextureUniform.value = this._renderTargetEdgeBuffer1.texture;
  450. this._blurDirection.value.copy( _BLUR_DIRECTION_X );
  451. _quadMesh.material = this._separableBlurMaterial;
  452. _quadMesh.name = 'Outline [ Blur Half Resolution ]';
  453. renderer.setRenderTarget( this._renderTargetBlurBuffer1 );
  454. _quadMesh.render( renderer );
  455. this._blurColorTextureUniform.value = this._renderTargetBlurBuffer1.texture;
  456. this._blurDirection.value.copy( _BLUR_DIRECTION_Y );
  457. renderer.setRenderTarget( this._renderTargetEdgeBuffer1 );
  458. _quadMesh.render( renderer );
  459. // 6. Apply blur (quarter resolution)
  460. this._blurColorTextureUniform.value = this._renderTargetEdgeBuffer1.texture;
  461. this._blurDirection.value.copy( _BLUR_DIRECTION_X );
  462. _quadMesh.material = this._separableBlurMaterial2;
  463. _quadMesh.name = 'Outline [ Blur Quarter Resolution ]';
  464. renderer.setRenderTarget( this._renderTargetBlurBuffer2 );
  465. _quadMesh.render( renderer );
  466. this._blurColorTextureUniform.value = this._renderTargetBlurBuffer2.texture;
  467. this._blurDirection.value.copy( _BLUR_DIRECTION_Y );
  468. renderer.setRenderTarget( this._renderTargetEdgeBuffer2 );
  469. _quadMesh.render( renderer );
  470. // 7. Composite
  471. _quadMesh.material = this._compositeMaterial;
  472. _quadMesh.name = 'Outline [ Blur Quarter Resolution ]';
  473. renderer.setRenderTarget( this._renderTargetComposite );
  474. _quadMesh.render( renderer );
  475. // restore
  476. RendererUtils.restoreRendererAndSceneState( renderer, scene, _rendererState );
  477. }
  478. /**
  479. * This method is used to setup the effect's TSL code.
  480. *
  481. * @param {NodeBuilder} builder - The current node builder.
  482. * @return {PassTextureNode}
  483. */
  484. setup() {
  485. // prepare mask material
  486. const prepareMask = () => {
  487. const depth = this._depthTextureUniform.sample( screenUV );
  488. let viewZNode;
  489. if ( this.camera.isPerspectiveCamera ) {
  490. viewZNode = perspectiveDepthToViewZ( depth, this._cameraNear, this._cameraFar );
  491. } else {
  492. viewZNode = orthographicDepthToViewZ( depth, this._cameraNear, this._cameraFar );
  493. }
  494. const depthTest = positionView.z.lessThanEqual( viewZNode ).select( 1, 0 );
  495. return vec3( 0.0, depthTest, 1.0 );
  496. };
  497. this._prepareMaskMaterial.colorNode = prepareMask();
  498. this._prepareMaskMaterial.needsUpdate = true;
  499. this._prepareMaskSpriteMaterial.colorNode = prepareMask();
  500. this._prepareMaskSpriteMaterial.needsUpdate = true;
  501. // copy material
  502. this._materialCopy.fragmentNode = this._maskTextureUniform;
  503. this._materialCopy.needsUpdate = true;
  504. // edge detection material
  505. const edgeDetection = Fn( () => {
  506. const resolution = textureSize( this._maskTextureDownsSampleUniform );
  507. const invSize = vec2( 1 ).div( resolution ).toVar();
  508. const uvOffset = vec4( 1.0, 0.0, 0.0, 1.0 ).mul( vec4( invSize, invSize ) );
  509. const uvNode = uv();
  510. const c1 = this._maskTextureDownsSampleUniform.sample( uvNode.add( uvOffset.xy ) ).toVar();
  511. const c2 = this._maskTextureDownsSampleUniform.sample( uvNode.sub( uvOffset.xy ) ).toVar();
  512. const c3 = this._maskTextureDownsSampleUniform.sample( uvNode.add( uvOffset.yw ) ).toVar();
  513. const c4 = this._maskTextureDownsSampleUniform.sample( uvNode.sub( uvOffset.yw ) ).toVar();
  514. const diff1 = mul( c1.r.sub( c2.r ), 0.5 );
  515. const diff2 = mul( c3.r.sub( c4.r ), 0.5 );
  516. const d = vec2( diff1, diff2 ).length();
  517. const a1 = min( c1.g, c2.g );
  518. const a2 = min( c3.g, c4.g );
  519. const visibilityFactor = min( a1, a2 );
  520. const edgeColor = visibilityFactor.oneMinus().greaterThan( 0.001 ).select( this._visibleEdgeColor, this._hiddenEdgeColor );
  521. return vec4( edgeColor, 1 ).mul( d );
  522. } );
  523. this._edgeDetectionMaterial.fragmentNode = edgeDetection();
  524. this._edgeDetectionMaterial.needsUpdate = true;
  525. // separable blur material
  526. const MAX_RADIUS = 4;
  527. const gaussianPdf = Fn( ( [ x, sigma ] ) => {
  528. return float( 0.39894 ).mul( exp( float( - 0.5 ).mul( x ).mul( x ).div( sigma.mul( sigma ) ) ).div( sigma ) );
  529. } );
  530. const separableBlur = Fn( ( [ kernelRadius ] ) => {
  531. const resolution = textureSize( this._maskTextureDownsSampleUniform );
  532. const invSize = vec2( 1 ).div( resolution ).toVar();
  533. const uvNode = uv();
  534. const sigma = kernelRadius.div( 2 ).toVar();
  535. const weightSum = gaussianPdf( 0, sigma ).toVar();
  536. const diffuseSum = this._blurColorTextureUniform.sample( uvNode ).mul( weightSum ).toVar();
  537. const delta = this._blurDirection.mul( invSize ).mul( kernelRadius ).div( MAX_RADIUS ).toVar();
  538. const uvOffset = delta.toVar();
  539. Loop( { start: int( 1 ), end: int( MAX_RADIUS ), type: 'int', condition: '<=' }, ( { i } ) => {
  540. const x = kernelRadius.mul( float( i ) ).div( MAX_RADIUS );
  541. const w = gaussianPdf( x, sigma );
  542. const sample1 = this._blurColorTextureUniform.sample( uvNode.add( uvOffset ) );
  543. const sample2 = this._blurColorTextureUniform.sample( uvNode.sub( uvOffset ) );
  544. diffuseSum.addAssign( sample1.add( sample2 ).mul( w ) );
  545. weightSum.addAssign( w.mul( 2 ) );
  546. uvOffset.addAssign( delta );
  547. } );
  548. return diffuseSum.div( weightSum );
  549. } );
  550. this._separableBlurMaterial.fragmentNode = separableBlur( this.edgeThicknessNode );
  551. this._separableBlurMaterial.needsUpdate = true;
  552. this._separableBlurMaterial2.fragmentNode = separableBlur( MAX_RADIUS );
  553. this._separableBlurMaterial2.needsUpdate = true;
  554. // composite material
  555. const composite = Fn( () => {
  556. const edgeValue1 = this._edge1TextureUniform;
  557. const edgeValue2 = this._edge2TextureUniform;
  558. const maskColor = this._maskTextureUniform;
  559. const edgeValue = edgeValue1.add( edgeValue2.mul( this.edgeGlowNode ) );
  560. return maskColor.r.mul( edgeValue );
  561. } );
  562. this._compositeMaterial.fragmentNode = composite();
  563. this._compositeMaterial.needsUpdate = true;
  564. return this._textureNode;
  565. }
  566. /**
  567. * Frees internal resources. This method should be called
  568. * when the effect is no longer required.
  569. */
  570. dispose() {
  571. this.selectedObjects.length = 0;
  572. this._renderTargetDepthBuffer.dispose();
  573. this._renderTargetMaskBuffer.dispose();
  574. this._renderTargetMaskDownSampleBuffer.dispose();
  575. this._renderTargetEdgeBuffer1.dispose();
  576. this._renderTargetEdgeBuffer2.dispose();
  577. this._renderTargetBlurBuffer1.dispose();
  578. this._renderTargetBlurBuffer2.dispose();
  579. this._renderTargetComposite.dispose();
  580. this._depthMaterial.dispose();
  581. this._depthSpriteMaterial.dispose();
  582. this._prepareMaskMaterial.dispose();
  583. this._prepareMaskSpriteMaterial.dispose();
  584. this._materialCopy.dispose();
  585. this._edgeDetectionMaterial.dispose();
  586. this._separableBlurMaterial.dispose();
  587. this._separableBlurMaterial2.dispose();
  588. this._compositeMaterial.dispose();
  589. }
  590. /**
  591. * Updates the selection cache based on the selected objects.
  592. *
  593. * @private
  594. */
  595. _updateSelectionCache() {
  596. for ( let i = 0; i < this.selectedObjects.length; i ++ ) {
  597. const selectedObject = this.selectedObjects[ i ];
  598. selectedObject.traverse( ( object ) => {
  599. if ( object.isMesh || object.isSprite ) this._selectionCache.add( object );
  600. } );
  601. }
  602. }
  603. }
  604. export default OutlineNode;
  605. /**
  606. * TSL function for creating an outline effect around selected objects.
  607. *
  608. * @tsl
  609. * @function
  610. * @param {Scene} scene - A reference to the scene.
  611. * @param {Camera} camera - The camera the scene is rendered with.
  612. * @param {Object} params - The configuration parameters.
  613. * @param {Array<Object3D>} [params.selectedObjects] - An array of selected objects.
  614. * @param {Node<float>} [params.edgeThickness=float(1)] - The thickness of the edges.
  615. * @param {Node<float>} [params.edgeGlow=float(0)] - Can be used for animated glow/pulse effects.
  616. * @param {number} [params.downSampleRatio=2] - The downsample ratio.
  617. * @returns {OutlineNode}
  618. */
  619. export const outline = ( scene, camera, params ) => new OutlineNode( scene, camera, params );
粤ICP备19079148号