├── .babelrc ├── .gitignore ├── images ├── 80s.png ├── haze.png ├── white.png ├── lookup.png ├── rainbow.png ├── original.png └── pinkHaze.png ├── .npmignore ├── shaders ├── pass.vert └── lut.frag ├── index.html ├── demo-no-post.js ├── LICENSE.md ├── package.json ├── app.js ├── demo.js └── README.md /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: [ "es2015" ] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | -------------------------------------------------------------------------------- /images/80s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/threejs-post-process-example/HEAD/images/80s.png -------------------------------------------------------------------------------- /images/haze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/threejs-post-process-example/HEAD/images/haze.png -------------------------------------------------------------------------------- /images/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/threejs-post-process-example/HEAD/images/white.png -------------------------------------------------------------------------------- /images/lookup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/threejs-post-process-example/HEAD/images/lookup.png -------------------------------------------------------------------------------- /images/rainbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/threejs-post-process-example/HEAD/images/rainbow.png -------------------------------------------------------------------------------- /images/original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/threejs-post-process-example/HEAD/images/original.png -------------------------------------------------------------------------------- /images/pinkHaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/threejs-post-process-example/HEAD/images/pinkHaze.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | .npmignore 10 | LICENSE.md -------------------------------------------------------------------------------- /shaders/pass.vert: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | void main() { 3 | vUv = uv; 4 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 5 | } 6 | -------------------------------------------------------------------------------- /shaders/lut.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | #define LUT_FLIP_Y 4 | 5 | varying vec2 vUv; 6 | uniform sampler2D tDiffuse; 7 | uniform sampler2D tLookup; 8 | 9 | #pragma glslify: lut = require('glsl-lut') 10 | 11 | void main () { 12 | gl_FragColor = texture2D(tDiffuse, vUv); 13 | gl_FragColor.rgb = lut(gl_FragColor, tLookup).rgb; 14 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | threejs-post-process-example 7 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo-no-post.js: -------------------------------------------------------------------------------- 1 | /* 2 | Example without any post-processing. 3 | Renders the scene directly to the . 4 | 5 | npm run no-post 6 | */ 7 | 8 | // Require our modules 9 | global.THREE = require('three'); 10 | const createApp = require('./app'); 11 | const createLoop = require('raf-loop'); 12 | 13 | // Create our basic ThreeJS application 14 | // We can enable MSAA since there is no post-processing 15 | const { 16 | renderer, 17 | camera, 18 | scene, 19 | updateProjectionMatrix 20 | } = createApp({ antialias: true }); 21 | 22 | // Render loop 23 | createLoop(function () { 24 | // Render the scene directly to 25 | updateProjectionMatrix(); 26 | renderer.render(scene, camera); 27 | }).start(); 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Jam3 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "threejs-post-process-example", 3 | "version": "1.0.0", 4 | "description": "a tutorial on ThreeJS post processing", 5 | "main": "index.js", 6 | "private": true, 7 | "license": "MIT", 8 | "author": { 9 | "name": "Matt DesLauriers", 10 | "email": "dave.des@gmail.com", 11 | "url": "https://github.com/mattdesl" 12 | }, 13 | "dependencies": { 14 | "babel-preset-es2015": "^6.3.13", 15 | "glsl-lut": "^1.1.0", 16 | "glslify": "^5.0.2", 17 | "raf-loop": "^1.1.3", 18 | "three": "^0.73.2", 19 | "three-effectcomposer": "0.0.1", 20 | "three-orbit-controls": "^72.0.0", 21 | "three-shader-fxaa": "^5.0.2", 22 | "three-vignette-background": "^1.0.2" 23 | }, 24 | "semistandard": { 25 | "globals": [ 26 | "THREE" 27 | ] 28 | }, 29 | "devDependencies": { 30 | "babelify": "^7.2.0", 31 | "browserify": "^13.0.0", 32 | "budo": "^8.0.3", 33 | "uglify-js": "^2.6.1" 34 | }, 35 | "scripts": { 36 | "test": "node test.js", 37 | "start": "budo demo.js:bundle.js --live", 38 | "no-post": "budo demo-no-post.js:bundle.js --live", 39 | "build": "browserify demo.js | uglifyjs -m -c warnings=false > bundle.js" 40 | }, 41 | "keywords": [ 42 | "post", 43 | "processing", 44 | "tutorial", 45 | "threejs" 46 | ], 47 | "repository": { 48 | "type": "git", 49 | "url": "git://github.com/Jam3/threejs-post-process-example.git" 50 | }, 51 | "homepage": "https://github.com/Jam3/threejs-post-process-example", 52 | "bugs": { 53 | "url": "https://github.com/Jam3/threejs-post-process-example/issues" 54 | }, 55 | "browserify": { 56 | "transform": [ 57 | "babelify", 58 | "glslify" 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is a generic "ThreeJS Application" 3 | helper which sets up a scene, geometry, 4 | vignette background, and camera controls. 5 | */ 6 | 7 | const OrbitControls = require('three-orbit-controls')(THREE); 8 | const createBackground = require('three-vignette-background'); 9 | 10 | module.exports = createApp; 11 | function createApp (opt = {}) { 12 | // Scale for retina 13 | const dpr = window.devicePixelRatio; 14 | 15 | // Our WebGL renderer with alpha and device-scaled 16 | const renderer = new THREE.WebGLRenderer(opt); 17 | renderer.setPixelRatio(dpr); 18 | renderer.setClearColor('#222023', 1); 19 | 20 | // Show the on screen 21 | const canvas = renderer.domElement; 22 | document.body.appendChild(canvas); 23 | 24 | // 3D camera looking at [ 0, 0, 0 ] 25 | const camera = new THREE.PerspectiveCamera(50, 1, 0.01, 1000); 26 | camera.position.set(0, 0, -6); 27 | camera.lookAt(new THREE.Vector3(0, 0, 0)); 28 | 29 | // 3D scene 30 | const scene = new THREE.Scene(); 31 | 32 | // Add a gradient background to highlight our tonemap effects 33 | var background = createBackground(); 34 | scene.add(background); 35 | 36 | // Simple 3D geometry 37 | const material = new THREE.MeshPhongMaterial(); 38 | const geometry = new THREE.TorusKnotGeometry(1, 0.025, 64, 64, 14, 3, 6); 39 | const mesh = new THREE.Mesh(geometry, material); 40 | mesh.rotation.y += Math.PI / 2; 41 | scene.add(mesh); 42 | 43 | // 3D orbit controller 44 | const controls = new OrbitControls(camera, canvas); 45 | 46 | // Basic lighting 47 | const light = new THREE.HemisphereLight('#f9b641', '#361448', 1); 48 | scene.add(light); 49 | 50 | // Update frame size 51 | window.addEventListener('resize', resize); 52 | resize(); 53 | 54 | // Create a requestAnimationFrame loop 55 | return { 56 | renderer, 57 | camera, 58 | controls, 59 | scene, 60 | mesh, 61 | updateProjectionMatrix 62 | }; 63 | 64 | function updateProjectionMatrix () { 65 | const width = window.innerWidth; 66 | const height = window.innerHeight; 67 | const aspect = width / height; 68 | 69 | // Add styling to our background element 70 | background.style({ 71 | aspect: aspect, 72 | aspectCorrection: true, 73 | grainScale: 0 74 | }); 75 | 76 | // Update camera matrices 77 | camera.aspect = aspect; 78 | camera.updateProjectionMatrix(); 79 | } 80 | 81 | function resize () { 82 | renderer.setSize(window.innerWidth, window.innerHeight); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /demo.js: -------------------------------------------------------------------------------- 1 | /* 2 | Example with post-processing. 3 | Renders the scene with an EffectComposer, 4 | using FXAA and Lookup Table color transforms. 5 | 6 | npm run start 7 | */ 8 | 9 | // Require our modules 10 | global.THREE = require('three'); 11 | const createApp = require('./app'); 12 | const createLoop = require('raf-loop'); 13 | const createFXAA = require('three-shader-fxaa'); 14 | const EffectComposer = require('three-effectcomposer')(THREE); 15 | const glslify = require('glslify'); 16 | 17 | // Create our basic ThreeJS application 18 | const { 19 | renderer, 20 | camera, 21 | scene, 22 | updateProjectionMatrix 23 | } = createApp(); 24 | 25 | // Create a new offscreen framebuffer 26 | const target = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight); 27 | target.texture.stencil = false; 28 | target.texture.minFilter = THREE.LinearFilter; 29 | target.texture.magFilter = THREE.LinearFilter; 30 | target.texture.format = THREE.RGBFormat; 31 | target.texture.generateMipmaps = false; 32 | 33 | // Create a new composer for post-processing 34 | const composer = new EffectComposer(renderer, target); 35 | 36 | // Copy scene to framebuffer 37 | composer.addPass(new EffectComposer.RenderPass(scene, camera)); 38 | 39 | // Add the FXAA shader 40 | composer.addPass(new EffectComposer.ShaderPass(createFXAA())); 41 | 42 | // Add the Lookup Table shader 43 | const lut = new EffectComposer.ShaderPass({ 44 | vertexShader: glslify(__dirname + '/shaders/pass.vert'), 45 | fragmentShader: glslify(__dirname + '/shaders/lut.frag'), 46 | uniforms: { 47 | tDiffuse: { type: 't', value: new THREE.Texture() }, 48 | tLookup: { type: 't', value: new THREE.Texture() } 49 | } 50 | }); 51 | composer.addPass(lut); 52 | 53 | // Setup our lookup table for the color transform shader 54 | const tLookup = new THREE.TextureLoader().load('images/lookup.png'); 55 | tLookup.generateMipmaps = false; 56 | tLookup.minFilter = THREE.LinearFilter; 57 | lut.uniforms.tLookup.value = tLookup; 58 | 59 | // Last pass should be rendered to screen! 60 | composer.passes[composer.passes.length - 1].renderToScreen = true; 61 | 62 | // Set initial size on our render targets 63 | resize(); 64 | 65 | // Render loop 66 | createLoop(() => { 67 | // Update shader passes with new screen resolution 68 | composer.passes.forEach(pass => { 69 | if (pass.uniforms && pass.uniforms.resolution) { 70 | pass.uniforms.resolution.value.set( 71 | target.width, target.height 72 | ); 73 | } 74 | }); 75 | 76 | // Render scene with post-processing 77 | updateProjectionMatrix(); 78 | composer.render(); 79 | }).start(); 80 | 81 | window.addEventListener('resize', resize); 82 | 83 | function resize () { 84 | // We need to resize the composer carefully to 85 | // make sure it looks good at all sizes! 86 | const dpr = renderer.getPixelRatio(); 87 | const targets = [ 88 | composer.renderTarget1, 89 | composer.renderTarget2 90 | ]; 91 | targets.forEach(target => { 92 | target.setSize(dpr * window.innerWidth, dpr * window.innerHeight); 93 | }); 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Post-Processing in ThreeJS 2 | 3 | ![images](http://i.imgur.com/bC1OMNR.jpg) 4 | 5 | *before and after post-processing – click [here](http://jam3.github.io/threejs-post-process-example) for a live demo* 6 | 7 | --- 8 | 9 | This is an example of post-processing effects in [ThreeJS](http://threejs.org/), including FXAA and Lookup Table color transforms. 10 | 11 | This example also provides some insight into the development workflow at Jam3, and how we scale and re-use code across some of our WebGL experiences. 12 | 13 | To build this demo, we used the following tools: 14 | 15 | - [npm](https://www.npmjs.com/) to install dependencies 16 | - [budo](https://www.npmjs.com/package/budo) for a fast development server 17 | - [browserify](https://www.npmjs.com/package/browserify) to bundle dependencies 18 | - [babelify](https://www.npmjs.com/package/babelify) for ES2015 transpiling 19 | - [glslify](https://www.npmjs.com/package/glslify) to inline GLSL shaders into our JavaScript bundle 20 | 21 | We bring together some of the following modules: 22 | 23 | - [three-orbit-controls](https://www.npmjs.com/package/three-orbit-controls) – a modular OrbitControls for ThreeJS camera 24 | - [three-effectcomposer](https://www.npmjs.com/package/three-effectcomposer) – a modular EffectComposer for ThreeJS post-processing 25 | - [three-vignette-background](https://www.npmjs.com/package/three-vignette-background) – adds a simple gradient background to your ThreeJS application 26 | - [three-shader-fxaa](https://www.npmjs.com/package/three-shader-fxaa) – an optimized FXAA shader for ThreeJS 27 | - [glsl-lut](https://www.npmjs.com/package/glsl-lut) – a generic Lookup Table GLSL component for color transforms 28 | 29 | ## Running from Source 30 | 31 | You can `git clone` this repo to run from source. 32 | 33 | ```sh 34 | git clone https://github.com/Jam3/threejs-post-process-example.git 35 | cd threejs-post-process-example 36 | 37 | # install dependencies 38 | npm install 39 | ``` 40 | 41 | Now you can run either demo: 42 | 43 | ```sh 44 | # with post-processing 45 | npm run start 46 | 47 | # without post-processing 48 | npm run no-post 49 | ``` 50 | 51 | Or build the static site: 52 | 53 | ```sh 54 | npm run build 55 | ``` 56 | 57 | ## Effects 58 | 59 | ##### FXAA 60 | 61 | For an optimized Fast Approximate Antialiasing (FXAA) shader, we use [three-shader-fxaa](https://github.com/mattdesl/three-shader-fxaa). 62 | 63 | ##### Color Lookup Transforms 64 | 65 | We use [glsl-lut](https://github.com/mattdesl/glsl-lut) for the efficient color lookup transforms. 66 | 67 | - [pass.vert](./shaders/pass.vert) – a simple "pass through" vertex shader 68 | - [lut.frag](./shaders/lut.frag) – a fragment shader which transforms colors with `glsl-lut` 69 | 70 | The [images/](./images) folder includes various lookup table examples, including the ["identity" lookup table](./images/original.png), which can be adjusted for your own effects. 71 | 72 | ## Further Reading 73 | 74 | - [npm Modules for Frontend JavaScript](https://github.com/jam3/jam3-lesson-module-basics) 75 | - [Modular and Versioned GLSL with `glslify`](http://mattdesl.svbtle.com/glslify) 76 | - [A Browserify Example for Fast Prototyping](https://github.com/mattdesl/browserify-example) --------------------------------------------------------------------------------