post-processing.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. <!DOCTYPE html><html lang="en"><head>
  2. <meta charset="utf-8">
  3. <title>Post Processing</title>
  4. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  5. <meta name="twitter:card" content="summary_large_image">
  6. <meta name="twitter:site" content="@threejs">
  7. <meta name="twitter:title" content="Three.js – Post Processing">
  8. <meta property="og:image" content="https://threejs.org/files/share.png">
  9. <link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
  10. <link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
  11. <link rel="stylesheet" href="../resources/lesson.css">
  12. <link rel="stylesheet" href="../resources/lang.css">
  13. <script type="importmap">
  14. {
  15. "imports": {
  16. "three": "../../build/three.module.js"
  17. }
  18. }
  19. </script>
  20. </head>
  21. <body>
  22. <div class="container">
  23. <div class="lesson-title">
  24. <h1>Post Processing</h1>
  25. </div>
  26. <div class="lesson">
  27. <div class="lesson-main">
  28. <p><em>Post processing</em> generally refers to applying some kind of effect or filter to
  29. a 2D image. In the case of THREE.js we have a scene with a bunch of meshes in
  30. it. We render that scene into a 2D image. Normally that image is rendered
  31. directly into the canvas and displayed in the browser but instead we can <a href="rendertargets.html">render
  32. it to a render target</a> and then apply some <em>post
  33. processing</em> effects to the result before drawing it to the canvas. It's called
  34. post processing because it happens after (post) the main scene processing.</p>
  35. <p>Examples of post processing are Instagram like filters,
  36. Photoshop filters, etc...</p>
  37. <p>THREE.js has some example classes to help setup a post processing pipeline. The
  38. way it works is you create an <code class="notranslate" translate="no">EffectComposer</code> and to it you add multiple <code class="notranslate" translate="no">Pass</code>
  39. objects. You then call <code class="notranslate" translate="no">EffectComposer.render</code> and it renders your scene to a
  40. <a href="rendertargets.html">render target</a> and then applies each <code class="notranslate" translate="no">Pass</code>.</p>
  41. <p>Each <code class="notranslate" translate="no">Pass</code> can be some post processing effect like adding a vignette, blurring,
  42. applying a bloom, applying film grain, adjusting the hue, saturation, contrast,
  43. etc... and finally rendering the result to the canvas.</p>
  44. <p>It's a little bit important to understand how <code class="notranslate" translate="no">EffectComposer</code> functions. It
  45. creates two <a href="rendertargets.html">render targets</a>. Let's call them
  46. <strong>rtA</strong> and <strong>rtB</strong>.</p>
  47. <p>Then, you call <code class="notranslate" translate="no">EffectComposer.addPass</code> to add each pass in the order you want
  48. to apply them. The passes are then applied <em>something like</em> this.</p>
  49. <div class="threejs_center"><img src="../resources/images/threejs-postprocessing.svg" style="width: 600px"></div>
  50. <p>First the scene you passed into <code class="notranslate" translate="no">RenderPass</code> is rendered to <strong>rtA</strong>, then
  51. <strong>rtA</strong> is passed to the next pass, whatever it is. That pass uses <strong>rtA</strong> as
  52. input to do whatever it does and writes the results to <strong>rtB</strong>. <strong>rtB</strong> is then
  53. passed to the next pass which uses <strong>rtB</strong> as input and writes back to <strong>rtA</strong>.
  54. This continues through all the passes. </p>
  55. <p>Each <code class="notranslate" translate="no">Pass</code> has 4 basic options</p>
  56. <h2 id="-enabled-"><code class="notranslate" translate="no">enabled</code></h2>
  57. <p>Whether or not to use this pass</p>
  58. <h2 id="-needsswap-"><code class="notranslate" translate="no">needsSwap</code></h2>
  59. <p>Whether or not to swap <code class="notranslate" translate="no">rtA</code> and <code class="notranslate" translate="no">rtB</code> after finishing this pass</p>
  60. <h2 id="-clear-"><code class="notranslate" translate="no">clear</code></h2>
  61. <p>Whether or not to clear before rendering this pass</p>
  62. <h2 id="-rendertoscreen-"><code class="notranslate" translate="no">renderToScreen</code></h2>
  63. <p>Whether or not to render to the canvas instead the current destination render
  64. target. In most use cases you do not set this flag explicitly since the last pass in the pass chain is automatically rendered to screen.</p>
  65. <p>Let's put together a basic example. We'll start with the example from <a href="responsive.html">the
  66. article on responsiveness</a>.</p>
  67. <p>To that first we create an <code class="notranslate" translate="no">EffectComposer</code>.</p>
  68. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const composer = new EffectComposer(renderer);
  69. </pre>
  70. <p>Then as the first pass we add a <code class="notranslate" translate="no">RenderPass</code> that will render our scene with our
  71. camera into the first render target.</p>
  72. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">composer.addPass(new RenderPass(scene, camera));
  73. </pre>
  74. <p>Next we add a <code class="notranslate" translate="no">BloomPass</code>. A <code class="notranslate" translate="no">BloomPass</code> renders its input to a generally
  75. smaller render target and blurs the result. It then adds that blurred result on
  76. top of the original input. This makes the scene <em>bloom</em></p>
  77. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const bloomPass = new BloomPass(
  78. 1, // strength
  79. 25, // kernel size
  80. 4, // sigma ?
  81. 256, // blur render target resolution
  82. );
  83. composer.addPass(bloomPass);
  84. </pre>
  85. <p>Next we had a <code class="notranslate" translate="no">FilmPass</code> that draws noise and scanlines on top of its input.</p>
  86. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const filmPass = new FilmPass(
  87. 0.35, // noise intensity
  88. 0.025, // scanline intensity
  89. 648, // scanline count
  90. false, // grayscale
  91. );
  92. composer.addPass(filmPass);
  93. </pre>
  94. <p>Finally we had a <code class="notranslate" translate="no">OutputPass</code> which performs color space conversion to sRGB and optional tone mapping.
  95. This pass is usually the last pass of the pass chain.
  96. </p>
  97. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const outputPass = new OutputPass();
  98. composer.addPass(outputPass);
  99. </pre>
  100. <p>To use these classes we need to import a bunch of scripts.</p>
  101. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import {EffectComposer} from 'three/addons/postprocessing/EffectComposer.js';
  102. import {RenderPass} from 'three/addons/postprocessing/RenderPass.js';
  103. import {BloomPass} from 'three/addons/postprocessing/BloomPass.js';
  104. import {FilmPass} from 'three/addons/postprocessing/FilmPass.js';
  105. import {OutputPass} from 'three/addons/postprocessing/OutputPass.js';
  106. </pre>
  107. <p>For pretty much any post processing <code class="notranslate" translate="no">EffectComposer.js</code>, <code class="notranslate" translate="no">RenderPass.js</code> and <code class="notranslate" translate="no">OutputPass.js</code>
  108. are required.</p>
  109. <p>The last things we need to do are to use <code class="notranslate" translate="no">EffectComposer.render</code> instead of
  110. <a href="/docs/#api/en/renderers/WebGLRenderer.render"><code class="notranslate" translate="no">WebGLRenderer.render</code></a> <em>and</em> to tell the <code class="notranslate" translate="no">EffectComposer</code> to match the size of
  111. the canvas.</p>
  112. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function render(now) {
  113. - time *= 0.001;
  114. +let then = 0;
  115. +function render(now) {
  116. + now *= 0.001; // convert to seconds
  117. + const deltaTime = now - then;
  118. + then = now;
  119. if (resizeRendererToDisplaySize(renderer)) {
  120. const canvas = renderer.domElement;
  121. camera.aspect = canvas.clientWidth / canvas.clientHeight;
  122. camera.updateProjectionMatrix();
  123. + composer.setSize(canvas.width, canvas.height);
  124. }
  125. cubes.forEach((cube, ndx) =&gt; {
  126. const speed = 1 + ndx * .1;
  127. - const rot = time * speed;
  128. + const rot = now * speed;
  129. cube.rotation.x = rot;
  130. cube.rotation.y = rot;
  131. });
  132. - renderer.render(scene, camera);
  133. + composer.render(deltaTime);
  134. requestAnimationFrame(render);
  135. }
  136. </pre>
  137. <p><code class="notranslate" translate="no">EffectComposer.render</code> takes a <code class="notranslate" translate="no">deltaTime</code> which is the time in seconds since
  138. the last frame was rendered. It passes this to the various effects in case any
  139. of them are animated. In this case the <code class="notranslate" translate="no">FilmPass</code> is animated.</p>
  140. <p></p><div translate="no" class="threejs_example_container notranslate">
  141. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/postprocessing.html"></iframe></div>
  142. <a class="threejs_center" href="/manual/examples/postprocessing.html" target="_blank">click here to open in a separate window</a>
  143. </div>
  144. <p></p>
  145. <p>To change effect parameters at runtime usually requires setting uniform values.
  146. Let's add a gui to adjust some of the parameters. Figuring out which values you
  147. can easily adjust and how to adjust them requires digging through the code for
  148. that effect.</p>
  149. <p>Looking inside
  150. <a href="https://github.com/mrdoob/three.js/blob/master/examples/jsm/postprocessing/BloomPass.js"><code class="notranslate" translate="no">BloomPass.js</code></a>
  151. I found this line:</p>
  152. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">this.copyUniforms[ "opacity" ].value = strength;
  153. </pre>
  154. <p>So we can set the strength by setting</p>
  155. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">bloomPass.copyUniforms.opacity.value = someValue;
  156. </pre>
  157. <p>Similarly looking in
  158. <a href="https://github.com/mrdoob/three.js/blob/master/examples/jsm/postprocessing/FilmPass.js"><code class="notranslate" translate="no">FilmPass.js</code></a>
  159. I found these lines:</p>
  160. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">if ( grayscale !== undefined ) this.uniforms.grayscale.value = grayscale;
  161. if ( noiseIntensity !== undefined ) this.uniforms.nIntensity.value = noiseIntensity;
  162. if ( scanlinesIntensity !== undefined ) this.uniforms.sIntensity.value = scanlinesIntensity;
  163. if ( scanlinesCount !== undefined ) this.uniforms.sCount.value = scanlinesCount;
  164. </pre>
  165. <p>So which makes it pretty clear how to set them.</p>
  166. <p>Let's make a quick GUI to set those values</p>
  167. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import {GUI} from 'three/addons/libs/lil-gui.module.min.js';
  168. </pre>
  169. <p>and</p>
  170. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gui = new GUI();
  171. {
  172. const folder = gui.addFolder('BloomPass');
  173. folder.add(bloomPass.copyUniforms.opacity, 'value', 0, 2).name('strength');
  174. folder.open();
  175. }
  176. {
  177. const folder = gui.addFolder('FilmPass');
  178. folder.add(filmPass.uniforms.grayscale, 'value').name('grayscale');
  179. folder.add(filmPass.uniforms.nIntensity, 'value', 0, 1).name('noise intensity');
  180. folder.add(filmPass.uniforms.sIntensity, 'value', 0, 1).name('scanline intensity');
  181. folder.add(filmPass.uniforms.sCount, 'value', 0, 1000).name('scanline count');
  182. folder.open();
  183. }
  184. </pre>
  185. <p>and now we can adjust those settings</p>
  186. <p></p><div translate="no" class="threejs_example_container notranslate">
  187. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/postprocessing-gui.html"></iframe></div>
  188. <a class="threejs_center" href="/manual/examples/postprocessing-gui.html" target="_blank">click here to open in a separate window</a>
  189. </div>
  190. <p></p>
  191. <p>That was a small step to making our own effect.</p>
  192. <p>Post processing effects use shaders. Shaders are written in a language called
  193. <a href="https://www.khronos.org/files/opengles_shading_language.pdf">GLSL (Graphics Library Shading Language)</a>. Going
  194. over the entire language is way too large a topic for these articles. A few
  195. resources to get start from would be maybe <a href="https://webglfundamentals.org/webgl/lessons/webgl-shaders-and-glsl.html">this article</a>
  196. and maybe <a href="https://thebookofshaders.com/">the Book of Shaders</a>.</p>
  197. <p>I think an example to get you started would be helpful though so let's make a
  198. simple GLSL post processing shader. We'll make one that lets us multiply the
  199. image by a color.</p>
  200. <p>For post processing THREE.js provides a useful helper called the <code class="notranslate" translate="no">ShaderPass</code>.
  201. It takes an object with info defining a vertex shader, a fragment shader, and
  202. the default inputs. It will handling setting up which texture to read from to
  203. get the previous pass's results and where to render to, either one of the
  204. <code class="notranslate" translate="no">EffectComposer</code>s render target or the canvas.</p>
  205. <p>Here's a simple post processing shader that multiplies the previous pass's
  206. result by a color. </p>
  207. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const colorShader = {
  208. uniforms: {
  209. tDiffuse: { value: null },
  210. color: { value: new THREE.Color(0x88CCFF) },
  211. },
  212. vertexShader: `
  213. varying vec2 vUv;
  214. void main() {
  215. vUv = uv;
  216. gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1);
  217. }
  218. `,
  219. fragmentShader: `
  220. varying vec2 vUv;
  221. uniform sampler2D tDiffuse;
  222. uniform vec3 color;
  223. void main() {
  224. vec4 previousPassColor = texture2D(tDiffuse, vUv);
  225. gl_FragColor = vec4(
  226. previousPassColor.rgb * color,
  227. previousPassColor.a);
  228. }
  229. `,
  230. };
  231. </pre>
  232. <p>Above <code class="notranslate" translate="no">tDiffuse</code> is the name that <code class="notranslate" translate="no">ShaderPass</code> uses to pass in the previous
  233. pass's result texture so we pretty much always need that. We then declare
  234. <code class="notranslate" translate="no">color</code> as a THREE.js <a href="/docs/#api/en/math/Color"><code class="notranslate" translate="no">Color</code></a>.</p>
  235. <p>Next we need a vertex shader. For post processing the vertex shader shown here
  236. is pretty much standard and rarely needs to be changed. Without going into too
  237. many details (see articles linked above) the variables <code class="notranslate" translate="no">uv</code>, <code class="notranslate" translate="no">projectionMatrix</code>,
  238. <code class="notranslate" translate="no">modelViewMatrix</code> and <code class="notranslate" translate="no">position</code> are all magically added by THREE.js.</p>
  239. <p>Finally we create a fragment shader. In it we get a pixel color from the
  240. previous pass with this line</p>
  241. <pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">vec4 previousPassColor = texture2D(tDiffuse, vUv);
  242. </pre>
  243. <p>we multiply it by our color and set <code class="notranslate" translate="no">gl_FragColor</code> to the result</p>
  244. <pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">gl_FragColor = vec4(
  245. previousPassColor.rgb * color,
  246. previousPassColor.a);
  247. </pre>
  248. <p>Adding some simple GUI to set the 3 values of the color</p>
  249. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gui = new GUI();
  250. gui.add(colorPass.uniforms.color.value, 'r', 0, 4).name('red');
  251. gui.add(colorPass.uniforms.color.value, 'g', 0, 4).name('green');
  252. gui.add(colorPass.uniforms.color.value, 'b', 0, 4).name('blue');
  253. </pre>
  254. <p>Gives us a simple postprocessing effect that multiplies by a color.</p>
  255. <p></p><div translate="no" class="threejs_example_container notranslate">
  256. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/postprocessing-custom.html"></iframe></div>
  257. <a class="threejs_center" href="/manual/examples/postprocessing-custom.html" target="_blank">click here to open in a separate window</a>
  258. </div>
  259. <p></p>
  260. <p>As mentioned about all the details of how to write GLSL and custom shaders is
  261. too much for these articles. If you really want to know how WebGL itself works
  262. then check out <a href="https://webglfundamentals.org">these articles</a>. Another great
  263. resources is just to
  264. <a href="https://github.com/mrdoob/three.js/tree/master/examples/jsm/shaders">read through the existing post processing shaders in the THREE.js repo</a>. Some
  265. are more complicated than others but if you start with the smaller ones you can
  266. hopefully get an idea of how they work.</p>
  267. <p>Most of the post processing effects in the THREE.js repo are unfortunately
  268. undocumented so to use them you'll have to <a href="https://github.com/mrdoob/three.js/tree/master/examples">read through the examples</a> or
  269. <a href="https://github.com/mrdoob/three.js/tree/master/examples/jsm/postprocessing">the code for the effects themselves</a>.
  270. Hopefully these simple example and the article on
  271. <a href="rendertargets.html">render targets</a> provide enough context to get started.</p>
  272. </div>
  273. </div>
  274. </div>
  275. <script src="../resources/prettify.js"></script>
  276. <script src="../resources/lesson.js"></script>
  277. </body></html>