EffectComposer.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. import {
  2. Clock,
  3. HalfFloatType,
  4. NoBlending,
  5. Vector2,
  6. WebGLRenderTarget
  7. } from 'three';
  8. import { CopyShader } from '../shaders/CopyShader.js';
  9. import { ShaderPass } from './ShaderPass.js';
  10. import { ClearMaskPass, MaskPass } from './MaskPass.js';
  11. /**
  12. * Used to implement post-processing effects in three.js.
  13. * The class manages a chain of post-processing passes to produce the final visual result.
  14. * Post-processing passes are executed in order of their addition/insertion.
  15. * The last pass is automatically rendered to screen.
  16. *
  17. * This module can only be used with {@link WebGLRenderer}.
  18. *
  19. * ```js
  20. * const composer = new EffectComposer( renderer );
  21. *
  22. * // adding some passes
  23. * const renderPass = new RenderPass( scene, camera );
  24. * composer.addPass( renderPass );
  25. *
  26. * const glitchPass = new GlitchPass();
  27. * composer.addPass( glitchPass );
  28. *
  29. * const outputPass = new OutputPass()
  30. * composer.addPass( outputPass );
  31. *
  32. * function animate() {
  33. *
  34. * composer.render(); // instead of renderer.render()
  35. *
  36. * }
  37. * ```
  38. *
  39. * @three_import import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
  40. */
  41. class EffectComposer {
  42. /**
  43. * Constructs a new effect composer.
  44. *
  45. * @param {WebGLRenderer} renderer - The renderer.
  46. * @param {WebGLRenderTarget} [renderTarget] - This render target and a clone will
  47. * be used as the internal read and write buffers. If not given, the composer creates
  48. * the buffers automatically.
  49. */
  50. constructor( renderer, renderTarget ) {
  51. /**
  52. * The renderer.
  53. *
  54. * @type {WebGLRenderer}
  55. */
  56. this.renderer = renderer;
  57. this._pixelRatio = renderer.getPixelRatio();
  58. if ( renderTarget === undefined ) {
  59. const size = renderer.getSize( new Vector2() );
  60. this._width = size.width;
  61. this._height = size.height;
  62. renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } );
  63. renderTarget.texture.name = 'EffectComposer.rt1';
  64. } else {
  65. this._width = renderTarget.width;
  66. this._height = renderTarget.height;
  67. }
  68. this.renderTarget1 = renderTarget;
  69. this.renderTarget2 = renderTarget.clone();
  70. this.renderTarget2.texture.name = 'EffectComposer.rt2';
  71. /**
  72. * A reference to the internal write buffer. Passes usually write
  73. * their result into this buffer.
  74. *
  75. * @type {WebGLRenderTarget}
  76. */
  77. this.writeBuffer = this.renderTarget1;
  78. /**
  79. * A reference to the internal read buffer. Passes usually read
  80. * the previous render result from this buffer.
  81. *
  82. * @type {WebGLRenderTarget}
  83. */
  84. this.readBuffer = this.renderTarget2;
  85. /**
  86. * Whether the final pass is rendered to the screen (default framebuffer) or not.
  87. *
  88. * @type {boolean}
  89. * @default true
  90. */
  91. this.renderToScreen = true;
  92. /**
  93. * An array representing the (ordered) chain of post-processing passes.
  94. *
  95. * @type {Array<Pass>}
  96. */
  97. this.passes = [];
  98. /**
  99. * A copy pass used for internal swap operations.
  100. *
  101. * @private
  102. * @type {ShaderPass}
  103. */
  104. this.copyPass = new ShaderPass( CopyShader );
  105. this.copyPass.material.blending = NoBlending;
  106. /**
  107. * The internal clock for managing time data.
  108. *
  109. * @private
  110. * @type {Clock}
  111. */
  112. this.clock = new Clock();
  113. }
  114. /**
  115. * Swaps the internal read/write buffers.
  116. */
  117. swapBuffers() {
  118. const tmp = this.readBuffer;
  119. this.readBuffer = this.writeBuffer;
  120. this.writeBuffer = tmp;
  121. }
  122. /**
  123. * Adds the given pass to the pass chain.
  124. *
  125. * @param {Pass} pass - The pass to add.
  126. */
  127. addPass( pass ) {
  128. this.passes.push( pass );
  129. pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
  130. }
  131. /**
  132. * Inserts the given pass at a given index.
  133. *
  134. * @param {Pass} pass - The pass to insert.
  135. * @param {number} index - The index into the pass chain.
  136. */
  137. insertPass( pass, index ) {
  138. this.passes.splice( index, 0, pass );
  139. pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
  140. }
  141. /**
  142. * Removes the given pass from the pass chain.
  143. *
  144. * @param {Pass} pass - The pass to remove.
  145. */
  146. removePass( pass ) {
  147. const index = this.passes.indexOf( pass );
  148. if ( index !== - 1 ) {
  149. this.passes.splice( index, 1 );
  150. }
  151. }
  152. /**
  153. * Returns `true` if the pass for the given index is the last enabled pass in the pass chain.
  154. *
  155. * @param {number} passIndex - The pass index.
  156. * @return {boolean} Whether the pass for the given index is the last pass in the pass chain.
  157. */
  158. isLastEnabledPass( passIndex ) {
  159. for ( let i = passIndex + 1; i < this.passes.length; i ++ ) {
  160. if ( this.passes[ i ].enabled ) {
  161. return false;
  162. }
  163. }
  164. return true;
  165. }
  166. /**
  167. * Executes all enabled post-processing passes in order to produce the final frame.
  168. *
  169. * @param {number} deltaTime - The delta time in seconds. If not given, the composer computes
  170. * its own time delta value.
  171. */
  172. render( deltaTime ) {
  173. // deltaTime value is in seconds
  174. if ( deltaTime === undefined ) {
  175. deltaTime = this.clock.getDelta();
  176. }
  177. const currentRenderTarget = this.renderer.getRenderTarget();
  178. let maskActive = false;
  179. for ( let i = 0, il = this.passes.length; i < il; i ++ ) {
  180. const pass = this.passes[ i ];
  181. if ( pass.enabled === false ) continue;
  182. pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) );
  183. pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive );
  184. if ( pass.needsSwap ) {
  185. if ( maskActive ) {
  186. const context = this.renderer.getContext();
  187. const stencil = this.renderer.state.buffers.stencil;
  188. //context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff );
  189. stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff );
  190. this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime );
  191. //context.stencilFunc( context.EQUAL, 1, 0xffffffff );
  192. stencil.setFunc( context.EQUAL, 1, 0xffffffff );
  193. }
  194. this.swapBuffers();
  195. }
  196. if ( MaskPass !== undefined ) {
  197. if ( pass instanceof MaskPass ) {
  198. maskActive = true;
  199. } else if ( pass instanceof ClearMaskPass ) {
  200. maskActive = false;
  201. }
  202. }
  203. }
  204. this.renderer.setRenderTarget( currentRenderTarget );
  205. }
  206. /**
  207. * Resets the internal state of the EffectComposer.
  208. *
  209. * @param {WebGLRenderTarget} [renderTarget] - This render target has the same purpose like
  210. * the one from the constructor. If set, it is used to setup the read and write buffers.
  211. */
  212. reset( renderTarget ) {
  213. if ( renderTarget === undefined ) {
  214. const size = this.renderer.getSize( new Vector2() );
  215. this._pixelRatio = this.renderer.getPixelRatio();
  216. this._width = size.width;
  217. this._height = size.height;
  218. renderTarget = this.renderTarget1.clone();
  219. renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
  220. }
  221. this.renderTarget1.dispose();
  222. this.renderTarget2.dispose();
  223. this.renderTarget1 = renderTarget;
  224. this.renderTarget2 = renderTarget.clone();
  225. this.writeBuffer = this.renderTarget1;
  226. this.readBuffer = this.renderTarget2;
  227. }
  228. /**
  229. * Resizes the internal read and write buffers as well as all passes. Similar to {@link WebGLRenderer#setSize},
  230. * this method honors the current pixel ration.
  231. *
  232. * @param {number} width - The width in logical pixels.
  233. * @param {number} height - The height in logical pixels.
  234. */
  235. setSize( width, height ) {
  236. this._width = width;
  237. this._height = height;
  238. const effectiveWidth = this._width * this._pixelRatio;
  239. const effectiveHeight = this._height * this._pixelRatio;
  240. this.renderTarget1.setSize( effectiveWidth, effectiveHeight );
  241. this.renderTarget2.setSize( effectiveWidth, effectiveHeight );
  242. for ( let i = 0; i < this.passes.length; i ++ ) {
  243. this.passes[ i ].setSize( effectiveWidth, effectiveHeight );
  244. }
  245. }
  246. /**
  247. * Sets device pixel ratio. This is usually used for HiDPI device to prevent blurring output.
  248. * Setting the pixel ratio will automatically resize the composer.
  249. *
  250. * @param {number} pixelRatio - The pixel ratio to set.
  251. */
  252. setPixelRatio( pixelRatio ) {
  253. this._pixelRatio = pixelRatio;
  254. this.setSize( this._width, this._height );
  255. }
  256. /**
  257. * Frees the GPU-related resources allocated by this instance. Call this
  258. * method whenever the composer is no longer used in your app.
  259. */
  260. dispose() {
  261. this.renderTarget1.dispose();
  262. this.renderTarget2.dispose();
  263. this.copyPass.dispose();
  264. }
  265. }
  266. export { EffectComposer };