Post-Processing is the addition of image effects or filters to your entire scene. This can change the feel of your scene and simulate interesting visual effects. Some examples are applying a sepia tone, or adding static the scene, giving it the feel of older television sets. To achieve this in Three.js we utilize shaders. The process involves creating an EffectComposer and then chaining together effects by adding passes to it. Passes are how we define the sequence of rendering and effects in the composer. There are different kinds of passes that achieve different results. I should also note that the classes and files used such as EffectComposer and the built in passes are not technically part of Three.js, but can be found in the examples included with the library.

Effect Composer

The EffectComposer is where you are going to add all your passes by calling addPass(). The first parameter it takes is your renderer. The second parameter is optional but takes a render target that allows you to specify different settings from your renderer, such as size. This composer will actually replace how we update the rendering of our application. Where we normally call renderer.render(scene, camera); to render our scene. We will instead call composer.render(); in our render loop to draw our scene.

var composer = new THREE.EffectComposer(renderer);

//NOTE: this goes in your render loop
composer.render();

Render Pass

The RenderPass is a pass for our composer that draws our scene and objects as they normally would be drawn in our renderer, with no effects. This will typically be the first pass you add to your composer. The RenderPass takes two parameters, just like your render call, the parameters are your scene, and camera objects.

var renderPass = new THREE.RenderPass(scene, camera);
composer.addPass(renderPass);

Shader Pass

The ShaderPass is a shader that provides a processing effect to the preceding texture provided in the composer sequence. This texture is the results from the previous pass. The examples include some shaders that you can use with the composer and are normally formatted to work with the ShaderPass class.

var shaderPass = new THREE.ShaderPass(THREE.SepiaShader);
composer.addPass(shaderPass);

You can also pass in your own effects to the ShaderPass. The structure it takes is simply an object with three properties, the uniforms, vertexShader, and fragmentShader. If you checked out the post on Custom Shader Materials, these should look familiar. You can see THREE.SepiaShader contents below. The uniforms property is just an object that has each uniform and it's value. The vertexShader and fragmentShader are simply strings containing the shader program. In the example below they are an array of strings that are joined together with line breaks, but this string content can come from anywhere. Using this as a model you can pass in your own custom shaders into a ShaderPass.

THREE.SepiaShader = {
	uniforms: {
		"tDiffuse": { value: null },
		"amount":   { value: 1.0 }
	},
	vertexShader: [
		"varying vec2 vUv;",
		"void main() {",
			"vUv = uv;",
			"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
		"}"
	].join( "\n" ),
	fragmentShader: [
		"uniform float amount;",
		"uniform sampler2D tDiffuse;",
		"varying vec2 vUv;",
		"void main() {",
			"vec4 color = texture2D( tDiffuse, vUv );",
			"vec3 c = color.rgb;",
			"color.r = dot( c, vec3( 1.0 - 0.607 * amount, 0.769 * amount, 0.189 * amount ) );",
			"color.g = dot( c, vec3( 0.349 * amount, 1.0 - 0.314 * amount, 0.168 * amount ) );",
			"color.b = dot( c, vec3( 0.272 * amount, 0.534 * amount, 1.0 - 0.869 * amount ) );",
			"gl_FragColor = vec4( min( vec3( 1.0 ), color.rgb ), color.a );",
		"}"
	].join( "\n" )
};

Texture Pass

TexturePass simply allows you to render a texture to your composer by taking in the texture itself as the map and the opacitcy of the texture.

var texturePass = new THREE.TexturePass(map, opacity);
composer.addPass(texturePass);

Many other passes can be found in the examples. Like ClearPass, MaskPass, and SavePass. Which all perform different operations on the composer to achieve different results. Examples how they are used can be found in the source code or the Three.js example projects.

Click here to download the demo.

Note: these examples were created using Three.js v79. Three.js is known to update frequently and sometimes cause breaking changes to the API so it may be worth checking the version you are using.