Home Reference Source

src/core/EffectComposer.js

  1. import {
  2. DepthStencilFormat,
  3. DepthTexture,
  4. LinearFilter,
  5. SRGBColorSpace,
  6. UnsignedByteType,
  7. UnsignedIntType,
  8. UnsignedInt248Type,
  9. Vector2,
  10. WebGLRenderTarget
  11. } from "three";
  12.  
  13. import { Timer } from "./Timer.js";
  14. import { ClearMaskPass } from "../passes/ClearMaskPass.js";
  15. import { CopyPass } from "../passes/CopyPass.js";
  16. import { MaskPass } from "../passes/MaskPass.js";
  17. import { Pass } from "../passes/Pass.js";
  18.  
  19. /**
  20. * The EffectComposer may be used in place of a normal WebGLRenderer.
  21. *
  22. * The auto clear behaviour of the provided renderer will be disabled to prevent unnecessary clear operations.
  23. *
  24. * It is common practice to use a {@link RenderPass} as the first pass to automatically clear the buffers and render a
  25. * scene for further processing.
  26. *
  27. * @implements {Resizable}
  28. * @implements {Disposable}
  29. */
  30.  
  31. export class EffectComposer {
  32.  
  33. /**
  34. * Constructs a new effect composer.
  35. *
  36. * @param {WebGLRenderer} renderer - The renderer that should be used.
  37. * @param {Object} [options] - The options.
  38. * @param {Boolean} [options.depthBuffer=true] - Whether the main render targets should have a depth buffer.
  39. * @param {Boolean} [options.stencilBuffer=false] - Whether the main render targets should have a stencil buffer.
  40. * @param {Boolean} [options.alpha] - Deprecated. Buffers are always RGBA since three r137.
  41. * @param {Number} [options.multisampling=0] - The number of samples used for multisample antialiasing. Requires WebGL 2.
  42. * @param {Number} [options.frameBufferType] - The type of the internal frame buffers. It's recommended to use HalfFloatType if possible.
  43. */
  44.  
  45. constructor(renderer = null, {
  46. depthBuffer = true,
  47. stencilBuffer = false,
  48. multisampling = 0,
  49. frameBufferType
  50. } = {}) {
  51.  
  52. /**
  53. * The renderer.
  54. *
  55. * @type {WebGLRenderer}
  56. * @private
  57. */
  58.  
  59. this.renderer = null;
  60.  
  61. /**
  62. * The input buffer.
  63. *
  64. * Two identical buffers are used to avoid reading from and writing to the same render target.
  65. *
  66. * @type {WebGLRenderTarget}
  67. * @private
  68. */
  69.  
  70. this.inputBuffer = this.createBuffer(depthBuffer, stencilBuffer, frameBufferType, multisampling);
  71.  
  72. /**
  73. * The output buffer.
  74. *
  75. * @type {WebGLRenderTarget}
  76. * @private
  77. */
  78.  
  79. this.outputBuffer = this.inputBuffer.clone();
  80.  
  81. /**
  82. * A copy pass used for copying masked scenes.
  83. *
  84. * @type {CopyPass}
  85. * @private
  86. */
  87.  
  88. this.copyPass = new CopyPass();
  89.  
  90. /**
  91. * A depth texture.
  92. *
  93. * @type {DepthTexture}
  94. * @private
  95. */
  96.  
  97. this.depthTexture = null;
  98.  
  99. /**
  100. * The passes.
  101. *
  102. * @type {Pass[]}
  103. * @private
  104. */
  105.  
  106. this.passes = [];
  107.  
  108. /**
  109. * A timer.
  110. *
  111. * @type {Timer}
  112. * @private
  113. */
  114.  
  115. this.timer = new Timer();
  116.  
  117. /**
  118. * Determines whether the last pass automatically renders to screen.
  119. *
  120. * @type {Boolean}
  121. */
  122.  
  123. this.autoRenderToScreen = true;
  124.  
  125. this.setRenderer(renderer);
  126.  
  127. }
  128.  
  129. /**
  130. * The current amount of samples used for multisample anti-aliasing.
  131. *
  132. * @type {Number}
  133. */
  134.  
  135. get multisampling() {
  136.  
  137. // TODO Raise min three version to 138 and remove || 0.
  138. return this.inputBuffer.samples || 0;
  139.  
  140. }
  141.  
  142. /**
  143. * Sets the amount of MSAA samples.
  144. *
  145. * Requires WebGL 2. Set to zero to disable multisampling.
  146. *
  147. * @type {Number}
  148. */
  149.  
  150. set multisampling(value) {
  151.  
  152. const buffer = this.inputBuffer;
  153. const multisampling = this.multisampling;
  154.  
  155. if(multisampling > 0 && value > 0) {
  156.  
  157. this.inputBuffer.samples = value;
  158. this.outputBuffer.samples = value;
  159. this.inputBuffer.dispose();
  160. this.outputBuffer.dispose();
  161.  
  162. } else if(multisampling !== value) {
  163.  
  164. this.inputBuffer.dispose();
  165. this.outputBuffer.dispose();
  166.  
  167. // Enable or disable MSAA.
  168. this.inputBuffer = this.createBuffer(
  169. buffer.depthBuffer,
  170. buffer.stencilBuffer,
  171. buffer.texture.type,
  172. value
  173. );
  174.  
  175. this.inputBuffer.depthTexture = this.depthTexture;
  176. this.outputBuffer = this.inputBuffer.clone();
  177.  
  178. }
  179.  
  180. }
  181.  
  182. /**
  183. * Returns the internal timer.
  184. *
  185. * @return {Timer} The timer.
  186. */
  187.  
  188. getTimer() {
  189.  
  190. return this.timer;
  191.  
  192. }
  193.  
  194. /**
  195. * Returns the renderer.
  196. *
  197. * @return {WebGLRenderer} The renderer.
  198. */
  199.  
  200. getRenderer() {
  201.  
  202. return this.renderer;
  203.  
  204. }
  205.  
  206. /**
  207. * Sets the renderer.
  208. *
  209. * @param {WebGLRenderer} renderer - The renderer.
  210. */
  211.  
  212. setRenderer(renderer) {
  213.  
  214. this.renderer = renderer;
  215.  
  216. if(renderer !== null) {
  217.  
  218. const size = renderer.getSize(new Vector2());
  219. const alpha = renderer.getContext().getContextAttributes().alpha;
  220. const frameBufferType = this.inputBuffer.texture.type;
  221.  
  222. if(frameBufferType === UnsignedByteType && renderer.outputColorSpace === SRGBColorSpace) {
  223.  
  224. this.inputBuffer.texture.colorSpace = SRGBColorSpace;
  225. this.outputBuffer.texture.colorSpace = SRGBColorSpace;
  226.  
  227. this.inputBuffer.dispose();
  228. this.outputBuffer.dispose();
  229.  
  230. }
  231.  
  232. renderer.autoClear = false;
  233. this.setSize(size.width, size.height);
  234.  
  235. for(const pass of this.passes) {
  236.  
  237. pass.initialize(renderer, alpha, frameBufferType);
  238.  
  239. }
  240.  
  241. }
  242.  
  243. }
  244.  
  245. /**
  246. * Replaces the current renderer with the given one.
  247. *
  248. * The auto clear mechanism of the provided renderer will be disabled. If the new render size differs from the
  249. * previous one, all passes will be updated.
  250. *
  251. * By default, the DOM element of the current renderer will automatically be removed from its parent node and the DOM
  252. * element of the new renderer will take its place.
  253. *
  254. * @deprecated Use setRenderer instead.
  255. * @param {WebGLRenderer} renderer - The new renderer.
  256. * @param {Boolean} updateDOM - Indicates whether the old canvas should be replaced by the new one in the DOM.
  257. * @return {WebGLRenderer} The old renderer.
  258. */
  259.  
  260. replaceRenderer(renderer, updateDOM = true) {
  261.  
  262. const oldRenderer = this.renderer;
  263. const parent = oldRenderer.domElement.parentNode;
  264.  
  265. this.setRenderer(renderer);
  266.  
  267. if(updateDOM && parent !== null) {
  268.  
  269. parent.removeChild(oldRenderer.domElement);
  270. parent.appendChild(renderer.domElement);
  271.  
  272. }
  273.  
  274. return oldRenderer;
  275.  
  276. }
  277.  
  278. /**
  279. * Creates a depth texture attachment that will be provided to all passes.
  280. *
  281. * Note: When a shader reads from a depth texture and writes to a render target that uses the same depth texture
  282. * attachment, the depth information will be lost. This happens even if `depthWrite` is disabled.
  283. *
  284. * @private
  285. * @return {DepthTexture} The depth texture.
  286. */
  287.  
  288. createDepthTexture() {
  289.  
  290. const depthTexture = this.depthTexture = new DepthTexture();
  291.  
  292. // Hack: Make sure the input buffer uses the depth texture.
  293. this.inputBuffer.depthTexture = depthTexture;
  294. this.inputBuffer.dispose();
  295.  
  296. if(this.inputBuffer.stencilBuffer) {
  297.  
  298. depthTexture.format = DepthStencilFormat;
  299. depthTexture.type = UnsignedInt248Type;
  300.  
  301. } else {
  302.  
  303. depthTexture.type = UnsignedIntType;
  304.  
  305. }
  306.  
  307. return depthTexture;
  308.  
  309. }
  310.  
  311. /**
  312. * Deletes the current depth texture.
  313. *
  314. * @private
  315. */
  316.  
  317. deleteDepthTexture() {
  318.  
  319. if(this.depthTexture !== null) {
  320.  
  321. this.depthTexture.dispose();
  322. this.depthTexture = null;
  323.  
  324. // Update the input buffer.
  325. this.inputBuffer.depthTexture = null;
  326. this.inputBuffer.dispose();
  327.  
  328. for(const pass of this.passes) {
  329.  
  330. pass.setDepthTexture(null);
  331.  
  332. }
  333.  
  334. }
  335.  
  336. }
  337.  
  338. /**
  339. * Creates a new render target.
  340. *
  341. * @deprecated Create buffers manually via WebGLRenderTarget instead.
  342. * @param {Boolean} depthBuffer - Whether the render target should have a depth buffer.
  343. * @param {Boolean} stencilBuffer - Whether the render target should have a stencil buffer.
  344. * @param {Number} type - The frame buffer type.
  345. * @param {Number} multisampling - The number of samples to use for antialiasing.
  346. * @return {WebGLRenderTarget} A new render target that equals the renderer's canvas.
  347. */
  348.  
  349. createBuffer(depthBuffer, stencilBuffer, type, multisampling) {
  350.  
  351. const renderer = this.renderer;
  352. const size = (renderer === null) ? new Vector2() : renderer.getDrawingBufferSize(new Vector2());
  353.  
  354. const options = {
  355. minFilter: LinearFilter,
  356. magFilter: LinearFilter,
  357. stencilBuffer,
  358. depthBuffer,
  359. type
  360. };
  361.  
  362. const renderTarget = new WebGLRenderTarget(size.width, size.height, options);
  363.  
  364. if(multisampling > 0) {
  365.  
  366. renderTarget.ignoreDepthForMultisampleCopy = false;
  367. renderTarget.samples = multisampling;
  368.  
  369. }
  370.  
  371. if(type === UnsignedByteType && renderer !== null && renderer.outputColorSpace === SRGBColorSpace) {
  372.  
  373. renderTarget.texture.colorSpace = SRGBColorSpace;
  374.  
  375. }
  376.  
  377. renderTarget.texture.name = "EffectComposer.Buffer";
  378. renderTarget.texture.generateMipmaps = false;
  379.  
  380. return renderTarget;
  381.  
  382. }
  383.  
  384. /**
  385. * Can be used to change the main scene for all registered passes and effects.
  386. *
  387. * @param {Scene} scene - The scene.
  388. */
  389.  
  390. setMainScene(scene) {
  391.  
  392. for(const pass of this.passes) {
  393.  
  394. pass.mainScene = scene;
  395.  
  396. }
  397.  
  398. }
  399.  
  400. /**
  401. * Can be used to change the main camera for all registered passes and effects.
  402. *
  403. * @param {Camera} camera - The camera.
  404. */
  405.  
  406. setMainCamera(camera) {
  407.  
  408. for(const pass of this.passes) {
  409.  
  410. pass.mainCamera = camera;
  411.  
  412. }
  413.  
  414. }
  415.  
  416. /**
  417. * Adds a pass, optionally at a specific index.
  418. *
  419. * @param {Pass} pass - A new pass.
  420. * @param {Number} [index] - An index at which the pass should be inserted.
  421. */
  422.  
  423. addPass(pass, index) {
  424.  
  425. const passes = this.passes;
  426. const renderer = this.renderer;
  427.  
  428. const drawingBufferSize = renderer.getDrawingBufferSize(new Vector2());
  429. const alpha = renderer.getContext().getContextAttributes().alpha;
  430. const frameBufferType = this.inputBuffer.texture.type;
  431.  
  432. pass.setRenderer(renderer);
  433. pass.setSize(drawingBufferSize.width, drawingBufferSize.height);
  434. pass.initialize(renderer, alpha, frameBufferType);
  435.  
  436. if(this.autoRenderToScreen) {
  437.  
  438. if(passes.length > 0) {
  439.  
  440. passes[passes.length - 1].renderToScreen = false;
  441.  
  442. }
  443.  
  444. if(pass.renderToScreen) {
  445.  
  446. this.autoRenderToScreen = false;
  447.  
  448. }
  449.  
  450. }
  451.  
  452. if(index !== undefined) {
  453.  
  454. passes.splice(index, 0, pass);
  455.  
  456. } else {
  457.  
  458. passes.push(pass);
  459.  
  460. }
  461.  
  462. if(this.autoRenderToScreen) {
  463.  
  464. passes[passes.length - 1].renderToScreen = true;
  465.  
  466. }
  467.  
  468. if(pass.needsDepthTexture || this.depthTexture !== null) {
  469.  
  470. if(this.depthTexture === null) {
  471.  
  472. const depthTexture = this.createDepthTexture();
  473.  
  474. for(pass of passes) {
  475.  
  476. pass.setDepthTexture(depthTexture);
  477.  
  478. }
  479.  
  480. } else {
  481.  
  482. pass.setDepthTexture(this.depthTexture);
  483.  
  484. }
  485.  
  486. }
  487.  
  488. }
  489.  
  490. /**
  491. * Removes a pass.
  492. *
  493. * @param {Pass} pass - The pass.
  494. */
  495.  
  496. removePass(pass) {
  497.  
  498. const passes = this.passes;
  499. const index = passes.indexOf(pass);
  500. const exists = (index !== -1);
  501. const removed = exists && (passes.splice(index, 1).length > 0);
  502.  
  503. if(removed) {
  504.  
  505. if(this.depthTexture !== null) {
  506.  
  507. // Check if the depth texture is still required.
  508. const reducer = (a, b) => (a || b.needsDepthTexture);
  509. const depthTextureRequired = passes.reduce(reducer, false);
  510.  
  511. if(!depthTextureRequired) {
  512.  
  513. if(pass.getDepthTexture() === this.depthTexture) {
  514.  
  515. pass.setDepthTexture(null);
  516.  
  517. }
  518.  
  519. this.deleteDepthTexture();
  520.  
  521. }
  522.  
  523. }
  524.  
  525. if(this.autoRenderToScreen) {
  526.  
  527. // Check if the removed pass was the last one.
  528. if(index === passes.length) {
  529.  
  530. pass.renderToScreen = false;
  531.  
  532. if(passes.length > 0) {
  533.  
  534. passes[passes.length - 1].renderToScreen = true;
  535.  
  536. }
  537.  
  538. }
  539.  
  540. }
  541.  
  542. }
  543.  
  544. }
  545.  
  546. /**
  547. * Removes all passes.
  548. */
  549.  
  550. removeAllPasses() {
  551.  
  552. const passes = this.passes;
  553.  
  554. this.deleteDepthTexture();
  555.  
  556. if(passes.length > 0) {
  557.  
  558. if(this.autoRenderToScreen) {
  559.  
  560. passes[passes.length - 1].renderToScreen = false;
  561.  
  562. }
  563.  
  564. this.passes = [];
  565.  
  566. }
  567.  
  568. }
  569.  
  570. /**
  571. * Renders all enabled passes in the order in which they were added.
  572. *
  573. * @param {Number} [deltaTime] - The time since the last frame in seconds.
  574. */
  575.  
  576. render(deltaTime) {
  577.  
  578. const renderer = this.renderer;
  579. const copyPass = this.copyPass;
  580.  
  581. let inputBuffer = this.inputBuffer;
  582. let outputBuffer = this.outputBuffer;
  583.  
  584. let stencilTest = false;
  585. let context, stencil, buffer;
  586.  
  587. if(deltaTime === undefined) {
  588.  
  589. this.timer.update();
  590. deltaTime = this.timer.getDelta();
  591.  
  592. }
  593.  
  594. for(const pass of this.passes) {
  595.  
  596. if(pass.enabled) {
  597.  
  598. pass.render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest);
  599.  
  600. if(pass.needsSwap) {
  601.  
  602. if(stencilTest) {
  603.  
  604. copyPass.renderToScreen = pass.renderToScreen;
  605. context = renderer.getContext();
  606. stencil = renderer.state.buffers.stencil;
  607.  
  608. // Preserve the unaffected pixels.
  609. stencil.setFunc(context.NOTEQUAL, 1, 0xffffffff);
  610. copyPass.render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest);
  611. stencil.setFunc(context.EQUAL, 1, 0xffffffff);
  612.  
  613. }
  614.  
  615. buffer = inputBuffer;
  616. inputBuffer = outputBuffer;
  617. outputBuffer = buffer;
  618.  
  619. }
  620.  
  621. if(pass instanceof MaskPass) {
  622.  
  623. stencilTest = true;
  624.  
  625. } else if(pass instanceof ClearMaskPass) {
  626.  
  627. stencilTest = false;
  628.  
  629. }
  630.  
  631. }
  632.  
  633. }
  634.  
  635. }
  636.  
  637. /**
  638. * Sets the size of the buffers, passes and the renderer.
  639. *
  640. * @param {Number} width - The width.
  641. * @param {Number} height - The height.
  642. * @param {Boolean} [updateStyle] - Determines whether the style of the canvas should be updated.
  643. */
  644.  
  645. setSize(width, height, updateStyle) {
  646.  
  647. const renderer = this.renderer;
  648. const currentSize = renderer.getSize(new Vector2());
  649.  
  650. if(width === undefined || height === undefined) {
  651.  
  652. width = currentSize.width;
  653. height = currentSize.height;
  654.  
  655. }
  656.  
  657. if(currentSize.width !== width || currentSize.height !== height) {
  658.  
  659. // Update the logical render size.
  660. renderer.setSize(width, height, updateStyle);
  661.  
  662. }
  663.  
  664. // The drawing buffer size takes the device pixel ratio into account.
  665. const drawingBufferSize = renderer.getDrawingBufferSize(new Vector2());
  666. this.inputBuffer.setSize(drawingBufferSize.width, drawingBufferSize.height);
  667. this.outputBuffer.setSize(drawingBufferSize.width, drawingBufferSize.height);
  668.  
  669. for(const pass of this.passes) {
  670.  
  671. pass.setSize(drawingBufferSize.width, drawingBufferSize.height);
  672.  
  673. }
  674.  
  675. }
  676.  
  677. /**
  678. * Resets this composer by deleting all passes and creating new buffers.
  679. */
  680.  
  681. reset() {
  682.  
  683. this.dispose();
  684. this.autoRenderToScreen = true;
  685.  
  686. }
  687.  
  688. /**
  689. * Disposes this composer and all passes.
  690. */
  691.  
  692. dispose() {
  693.  
  694. for(const pass of this.passes) {
  695.  
  696. pass.dispose();
  697.  
  698. }
  699.  
  700. this.passes = [];
  701.  
  702. if(this.inputBuffer !== null) {
  703.  
  704. this.inputBuffer.dispose();
  705.  
  706. }
  707.  
  708. if(this.outputBuffer !== null) {
  709.  
  710. this.outputBuffer.dispose();
  711.  
  712. }
  713.  
  714. this.deleteDepthTexture();
  715. this.copyPass.dispose();
  716. this.timer.dispose();
  717.  
  718. Pass.fullscreenGeometry.dispose();
  719.  
  720. }
  721.  
  722. }