├── .babelrc
├── .gitignore
├── LICENSE.md
├── README.md
├── build.js
├── package-lock.json
├── package.json
├── public
├── build
│ └── .gitkeep
└── index.html
├── server.js
└── src
├── config
└── postProcessing.js
├── core
├── App.js
├── Camera.js
├── Renderer.js
└── Scene.js
├── entities
└── Cube
│ ├── CubeMaterial.js
│ └── index.js
├── helpers
└── GUI.js
├── main.js
├── postProcessing
├── Composer.js
├── EffectComposer.js
├── PostProcessing.js
├── functions
│ ├── fxaa
│ │ └── index.glsl
│ ├── rgb
│ │ └── index.glsl
│ └── tilt-shift
│ │ └── index.glsl
├── passes
│ ├── BloomPass
│ │ ├── BloomPass.js
│ │ ├── convolution.frag
│ │ └── convolution.vert
│ ├── BloomUnrealPass
│ │ ├── BloomUnrealPass.js
│ │ ├── blur.frag
│ │ └── composite.frag
│ ├── ClearMaskPass
│ │ └── ClearMaskPass.js
│ ├── MaskPass
│ │ └── MaskPass.js
│ ├── Pass.js
│ ├── PostPass
│ │ ├── PostPass.js
│ │ └── post.frag
│ ├── RenderPass
│ │ └── RenderPass.js
│ ├── ShaderPass
│ │ └── ShaderPass.js
│ └── index.js
└── shaders
│ ├── basic.frag
│ ├── basic.vert
│ ├── copy.frag
│ └── luminosity-high.frag
├── shaders
├── cube.frag
└── cube.vert
├── signals
└── Window.js
├── stylesheets
└── main.styl
└── utils
└── math
├── clamp.js
├── index.js
├── lerp.js
├── map.js
├── normalize.js
├── random-float.js
├── random-hex-color.js
├── random-int.js
└── to-radians.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env"],
3 | "plugins": ["transform-object-rest-spread"]
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 | node_modules/
4 | dist/
5 | doc/
6 | .history/
7 | *.log
8 | bundle.js
9 | main.css
10 | *.map
11 | *.log
12 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright © 2016 [Fabien Motte](http://fabienmotte.com/).
5 |
6 | Permission is hereby granted, free of charge, to any person
7 | obtaining a copy of this software and associated documentation
8 | files (the “Software”), to deal in the Software without
9 | restriction, including without limitation the rights to use,
10 | copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the
12 | Software is furnished to do so, subject to the following
13 | conditions:
14 |
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ternion (ˈtɜːnɪən)
2 |
3 | [![stability][stability-image]][stability-url]
4 | [![js-standard-style][standard-image]][standard-url]
5 |
6 | 
7 |
8 | A simple and opinionated **starter kit** to **prototype** quickly your ideas with Three.js.
9 |
10 | ## Features
11 |
12 | - **Three.js** and **post processing**
13 | - **dat.GUI**
14 | - Code linted with **Javascript Standard Style**
15 | - [**budō**](https://github.com/mattdesl/budo) (with **LiveReload**) for a fast development server
16 | - ES2015 transpiling with **Babel 6** (*stage 0* enabled)
17 | - Development and production (with **UglifyJS** transform) builds
18 | - **Glslify** transform (vert/frag shaders)
19 | - Some useful **basic functions** and **helpers**
20 | - **Stylus** support for stylesheets
21 |
22 | ## Install
23 |
24 | Clone this repository and install dependencies :
25 |
26 | ```sh
27 | git clone https://github.com/FabienMotte/Ternion.git
28 | ```
29 |
30 | ```sh
31 | npm install
32 | ```
33 |
34 | ## Usage
35 |
36 | A **simple example** (see the picture above) is included as a demonstration.
37 | Now it's time to be creative and imagine something on your own !
38 |
39 | ### Development
40 |
41 | It starts a [budō](https://github.com/mattdesl/budo) server with LiveReload and open [http://localhost:9966/](http://localhost:9966/) for you.
42 |
43 | ```sh
44 | npm start
45 | ```
46 |
47 | ### Production
48 |
49 | It builds with Browserify a bundled file outputted here : `public/build/bundle.js`.
50 |
51 | ```sh
52 | npm run build
53 | ```
54 |
55 | ## Contribute
56 |
57 | This starter kit is opinionated, but feel free to submit issues or pull requests !
58 |
59 | ## Contributors
60 |
61 | - [Patrick Heng](https://github.com/patrickheng)
62 |
63 | ## License
64 |
65 | MIT, see [LICENSE.md](LICENSE.md) for details.
66 |
67 | [stability-image]: https://img.shields.io/badge/stability-stable-brightgreen.svg?style=flat-square
68 | [stability-url]: https://nodejs.org/api/documentation.html#documentation_stability_index
69 | [standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square
70 | [standard-url]: https://github.com/feross/standard
71 |
--------------------------------------------------------------------------------
/build.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const browserify = require('browserify')
3 |
4 | const b = browserify('./src/main.js', {
5 | paths: [
6 | path.join(__dirname, '/src')
7 | ],
8 | insertGlobalVars: {
9 | THREE: (file, dir) => `require('three')`
10 | }
11 | })
12 |
13 | b.bundle().pipe(process.stdout)
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ternion",
3 | "version": "5.0.0",
4 | "description": "Ternion, a simple starter kit to prototype quickly your ideas with Three.js",
5 | "main": "src/index.js",
6 | "private": true,
7 | "scripts": {
8 | "start": "concurrently -k -r \"node server.js\" \"npm run css:dev\"",
9 | "build": "NODE_ENV='production' node build.js | uglifyjs -c > public/build/bundle.js",
10 | "css:dev": "stylus -u autoprefixer-stylus -w ./src/stylesheets/main.styl -o public/build --sourcemap",
11 | "css:prod": "stylus -u autoprefixer-stylus ./src/stylesheets/main.styl -o public/build --compress",
12 | "test": "echo \"Error: no test specified\" && exit 1"
13 | },
14 | "keywords": [
15 | "budo",
16 | "glsl",
17 | "webgl",
18 | "three.js",
19 | "glslify",
20 | "starter",
21 | "boilerplate",
22 | "prototyping"
23 | ],
24 | "author": "Fabien Motte ",
25 | "license": "MIT",
26 | "repository": {
27 | "type": "git",
28 | "url": "https://github.com/FabienMotte/Ternion.git"
29 | },
30 | "bugs": {
31 | "url": "https://github.com/FabienMotte/Ternion/issues"
32 | },
33 | "homepage": "https://github.com/FabienMotte/Ternion",
34 | "dependencies": {
35 | "color-shader-functions": "0.0.2",
36 | "glslify-bare": "FabienMotte/glslify-bare",
37 | "lodash.debounce": "^4.0.8",
38 | "quark-raf": "^1.0.4",
39 | "quark-signal": "^1.1.2",
40 | "raf": "^3.4.0",
41 | "three": "^0.91.0",
42 | "three-orbitcontrols": "^2.0.0"
43 | },
44 | "devDependencies": {
45 | "autoprefixer-stylus": "^0.14.0",
46 | "babel-core": "^6.26.0",
47 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
48 | "babel-preset-env": "^1.6.1",
49 | "babelify": "^8.0.0",
50 | "browserify": "^16.1.1",
51 | "budo": "^11.2.0",
52 | "concurrently": "^3.5.1",
53 | "dat-gui": "^0.5.0",
54 | "glslify": "^6.1.1",
55 | "stylus": "^0.54.5",
56 | "uglify-js": "^3.3.15"
57 | },
58 | "browserify": {
59 | "transform": [
60 | "babelify",
61 | "glslify-bare"
62 | ]
63 | },
64 | "standard": {
65 | "globals": [
66 | "THREE",
67 | "TweenMax",
68 | "TimelineMax",
69 | "Back",
70 | "Bounce",
71 | "Circ",
72 | "Cubic",
73 | "Ease",
74 | "Elastic",
75 | "Expo",
76 | "Linear",
77 | "Power0",
78 | "Power1",
79 | "Power2",
80 | "Power3",
81 | "Power4",
82 | "Quad",
83 | "RoughEase",
84 | "Sine",
85 | "SplitText"
86 | ]
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/public/build/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabienMotte/Ternion/e14a4e63ca777e3b82b52cf1b8243469d12b26ca/public/build/.gitkeep
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Ternion
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const budo = require('budo')
3 |
4 | budo('src/main.js', {
5 | serve: 'build/bundle.js',
6 | live: true,
7 | open: true,
8 | dir: 'public',
9 | stream: process.stdout,
10 | watchGlob: '**/*.{html,css,frag,vert,glsl}',
11 | browserify: {
12 | paths: [
13 | path.join(__dirname, '/src')
14 | ],
15 | insertGlobalVars: {
16 | THREE: (file, dir) => `require('three')`
17 | }
18 | }
19 | })
20 |
--------------------------------------------------------------------------------
/src/config/postProcessing.js:
--------------------------------------------------------------------------------
1 | import { BloomUnrealPass, PostPass } from 'postProcessing/passes'
2 |
3 | export default {
4 | active: true,
5 | passes: [
6 | {
7 | active: true,
8 | name: 'bloomPass',
9 | constructor: new BloomUnrealPass({
10 | resolution: new THREE.Vector2(256, 256),
11 | strength: 2,
12 | radius: 1,
13 | threshold: 0.04
14 | })
15 | },
16 | {
17 | active: true,
18 | name: 'postPass',
19 | constructor: new PostPass()
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/src/core/App.js:
--------------------------------------------------------------------------------
1 | import Renderer from 'core/Renderer'
2 | import Camera from 'core/Camera'
3 | import Scene from 'core/Scene'
4 |
5 | class App {
6 | static begin () {
7 | const renderer = new Renderer()
8 | const container = document.getElementById('webgl-container')
9 | container.appendChild(renderer.domElement)
10 |
11 | const camera = new Camera(75, window.innerWidth / window.innerHeight, 0.1, 10000)
12 | camera.position.z = 5
13 |
14 | const scene = new Scene(renderer, camera) // eslint-disable-line
15 | }
16 | }
17 |
18 | export default App
19 |
--------------------------------------------------------------------------------
/src/core/Camera.js:
--------------------------------------------------------------------------------
1 | import OrbitControls from 'three-orbitcontrols'
2 | import Window from 'signals/Window'
3 | import GUI from 'helpers/GUI'
4 |
5 | class Camera extends THREE.PerspectiveCamera {
6 | constructor (fov, aspect, near, far) {
7 | super(fov, aspect, near, far)
8 |
9 | this.controls = new OrbitControls(this, document.getElementById('webgl-container'))
10 | this.controls.enabled = true
11 |
12 | this.bind()
13 | this.addGUI()
14 | }
15 |
16 | bind () {
17 | this.onWindowResize = this.onWindowResize.bind(this)
18 | Window.onResize.add(this.onWindowResize)
19 | }
20 |
21 | addGUI () {
22 | GUI.add(this.controls, 'enabled').name('OrbitControls')
23 | }
24 |
25 | update () {
26 | this.controls.update()
27 | }
28 |
29 | onWindowResize (width, height) {
30 | this.aspect = width / height
31 | this.updateProjectionMatrix()
32 | }
33 | }
34 |
35 | export default Camera
36 |
--------------------------------------------------------------------------------
/src/core/Renderer.js:
--------------------------------------------------------------------------------
1 | import Window from 'signals/Window'
2 |
3 | class Renderer extends THREE.WebGLRenderer {
4 | constructor (options = { antialias: true, alpha: false }) {
5 | super(options)
6 |
7 | this.setSize(window.innerWidth, window.innerHeight)
8 | this.setPixelRatio(window.devicePixelRatio)
9 | this.setClearColor(0x0a0a0a, 1.0)
10 |
11 | this.bind()
12 | }
13 |
14 | bind () {
15 | this.onWindowResize = this.onWindowResize.bind(this)
16 | Window.onResize.add(this.onWindowResize)
17 | }
18 |
19 | onWindowResize (width, height) {
20 | this.setSize(width, height)
21 | }
22 | }
23 |
24 | export default Renderer
25 |
--------------------------------------------------------------------------------
/src/core/Scene.js:
--------------------------------------------------------------------------------
1 | import Raf from 'quark-raf'
2 | import Cube from 'entities/Cube'
3 | import PostProcessing from 'postProcessing/PostProcessing'
4 |
5 | class Scene extends THREE.Scene {
6 | constructor (renderer, camera) {
7 | super()
8 |
9 | this.renderer = renderer
10 | this.camera = camera
11 | this.postProcessing = new PostProcessing(this, this.renderer, this.camera)
12 |
13 | this.bind()
14 | this.createScene()
15 | }
16 |
17 | bind () {
18 | this.render = this.render.bind(this)
19 | Raf.add(this.render)
20 | }
21 |
22 | createScene () {
23 | this.cube = new Cube()
24 | this.add(this.cube)
25 | }
26 |
27 | render (delta, time) {
28 | const cube = this.cube
29 |
30 | cube.rotation.x += 0.01
31 | cube.rotation.y += 0.02
32 |
33 | cube.update(time)
34 |
35 | this.postProcessing.update(delta, time)
36 | this.camera.update()
37 | }
38 | }
39 |
40 | export default Scene
41 |
--------------------------------------------------------------------------------
/src/entities/Cube/CubeMaterial.js:
--------------------------------------------------------------------------------
1 | import vertexShader from 'shaders/cube.vert'
2 | import fragmentShader from 'shaders/cube.frag'
3 |
4 | class CubeMaterial extends THREE.ShaderMaterial {
5 | constructor (options) {
6 | super(options)
7 |
8 | this.vertexShader = vertexShader
9 | this.fragmentShader = fragmentShader
10 |
11 | this.uniforms = {
12 | time: { type: 'f', value: 0.0 },
13 | color: { type: 'c', value: new THREE.Color(0xffffff) }
14 | }
15 | }
16 |
17 | update (time) {
18 | this.uniforms.time.value = time * 0.3
19 | }
20 | }
21 |
22 | export default CubeMaterial
23 |
--------------------------------------------------------------------------------
/src/entities/Cube/index.js:
--------------------------------------------------------------------------------
1 | import CubeMaterial from './CubeMaterial'
2 | import GUI from 'helpers/GUI'
3 |
4 | class Cube extends THREE.Object3D {
5 | constructor () {
6 | super()
7 |
8 | this.geometry = new THREE.BoxGeometry(1, 1, 1)
9 | this.material = new CubeMaterial({ wireframe: true })
10 | this.mesh = new THREE.Mesh(this.geometry, this.material)
11 | this.add(this.mesh)
12 |
13 | this.addGUI()
14 | }
15 |
16 | addGUI () {
17 | const positionFolder = GUI.addFolder('Cube Position')
18 | const scaleFolder = GUI.addFolder('Cube Scale')
19 |
20 | positionFolder.add(this.position, 'x').name('position x').min(-10).max(10).step(0.5)
21 | positionFolder.add(this.position, 'y').name('position y').min(-10).max(10).step(0.5)
22 | positionFolder.add(this.position, 'z').name('position z').min(-10).max(10).step(0.5)
23 |
24 | scaleFolder.add(this.scale, 'x').name('scale x').min(0).max(10).step(0.1)
25 | scaleFolder.add(this.scale, 'y').name('scale y').min(0).max(10).step(0.1)
26 | scaleFolder.add(this.scale, 'z').name('scale z').min(0).max(10).step(0.1)
27 | }
28 |
29 | update (time) {
30 | this.material.update(time / 1000)
31 | }
32 | }
33 |
34 | export default Cube
35 |
--------------------------------------------------------------------------------
/src/helpers/GUI.js:
--------------------------------------------------------------------------------
1 | import dat from 'dat-gui'
2 |
3 | const GUI = new dat.GUI({ autoPlace: false, scrollable: true })
4 | document.body.appendChild(GUI.domElement)
5 |
6 | export default GUI
7 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './core/App'
2 |
3 | let the = App.begin() // eslint-disable-line
4 |
--------------------------------------------------------------------------------
/src/postProcessing/Composer.js:
--------------------------------------------------------------------------------
1 | import { ShaderPass, MaskPass, ClearMaskPass } from './passes'
2 |
3 | class Composer {
4 | constructor (renderer, renderTarget) {
5 | this.renderer = renderer
6 |
7 | if (renderTarget === undefined) {
8 | const parameters = {
9 | minFilter: THREE.LinearFilter,
10 | magFilter: THREE.LinearFilter,
11 | format: THREE.RGBAFormat,
12 | stencilBuffer: false
13 | }
14 |
15 | const size = this.renderer.getSize()
16 | renderTarget = new THREE.WebGLRenderTarget(size.width, size.height, parameters)
17 | }
18 |
19 | this.renderTarget1 = renderTarget
20 | this.renderTarget2 = renderTarget.clone()
21 |
22 | this.writeBuffer = this.renderTarget1
23 | this.readBuffer = this.renderTarget2
24 |
25 | this.passes = []
26 |
27 | this.copyPass = new ShaderPass()
28 | }
29 |
30 | swapBuffers () {
31 | const tmp = this.readBuffer
32 | this.readBuffer = this.writeBuffer
33 | this.writeBuffer = tmp
34 | }
35 |
36 | addPass (pass) {
37 | this.passes.push(pass)
38 |
39 | const size = this.renderer.getSize()
40 | const pixelRatio = this.renderer.getPixelRatio()
41 | pass.setSize(size.width * pixelRatio, size.height * pixelRatio)
42 | }
43 |
44 | insertPass (pass, index) {
45 | this.passes.splice(index, 0, pass)
46 | }
47 |
48 | render (delta, time) {
49 | let maskActive = false
50 |
51 | const passesLength = this.passes.length
52 |
53 | for (let i = 0; i < passesLength; i++) {
54 | const pass = this.passes[i]
55 |
56 | pass.render(this.renderer, this.writeBuffer, this.readBuffer, delta, time, maskActive)
57 |
58 | if (pass.needsSwap) {
59 | if (maskActive) {
60 | const context = this.renderer.context
61 |
62 | context.stencilFunc(context.NOTEQUAL, 1, 0xffffffff)
63 | this.copyPass.render(this.renderer, this.writeBuffer, this.readBuffer, delta, time)
64 | context.stencilFunc(context.EQUAL, 1, 0xffffffff)
65 | }
66 |
67 | this.swapBuffers()
68 | }
69 |
70 | if (MaskPass !== undefined) {
71 | if (pass instanceof MaskPass) {
72 | maskActive = true
73 | } else if (pass instanceof ClearMaskPass) {
74 | maskActive = false
75 | }
76 | }
77 | }
78 | }
79 |
80 | reset (renderTarget) {
81 | if (renderTarget === undefined) {
82 | const size = this.renderer.getSize()
83 |
84 | renderTarget = this.renderTarget1.clone()
85 | renderTarget.setSize(size.width, size.height)
86 | }
87 |
88 | this.renderTarget1.dispose()
89 | this.renderTarget2.dispose()
90 | this.renderTarget1 = renderTarget
91 | this.renderTarget2 = renderTarget.clone()
92 |
93 | this.writeBuffer = this.renderTarget1
94 | this.readBuffer = this.renderTarget2
95 | }
96 |
97 | setSize (width, height) {
98 | this.renderTarget1.setSize(width, height)
99 | this.renderTarget2.setSize(width, height)
100 |
101 | for (let i = 0; i < this.passes.length; i++) {
102 | this.passes[i].setSize(width, height)
103 | }
104 | }
105 | }
106 |
107 | export default Composer
108 |
--------------------------------------------------------------------------------
/src/postProcessing/EffectComposer.js:
--------------------------------------------------------------------------------
1 | import Composer from './Composer'
2 | import Window from 'signals/Window'
3 |
4 | class EffectComposer extends Composer {
5 | constructor (renderer) {
6 | super(renderer)
7 |
8 | this.renderer = renderer
9 |
10 | this.bind()
11 | }
12 |
13 | bind () {
14 | this.onWindowResize = this.onWindowResize.bind(this)
15 | Window.onResize.add(this.onWindowResize)
16 | }
17 |
18 | onWindowResize (width, height) {
19 | this.setSize(width * this.renderer.getPixelRatio(), height * this.renderer.getPixelRatio())
20 | }
21 | }
22 |
23 | export default EffectComposer
24 |
--------------------------------------------------------------------------------
/src/postProcessing/PostProcessing.js:
--------------------------------------------------------------------------------
1 | import EffectComposer from './EffectComposer'
2 | import { RenderPass } from './passes'
3 | import postProcessing from 'config/postProcessing'
4 | import GUI from 'helpers/GUI'
5 |
6 | class PostProcessing {
7 | constructor (scene, renderer, camera) {
8 | this.scene = scene
9 |
10 | this.camera = camera
11 |
12 | this.renderer = renderer
13 | this.configuration = postProcessing
14 |
15 | this.passes = this.configuration.passes.filter(pass => pass.active)
16 | this.active = this.configuration.active
17 |
18 | this.composer = new EffectComposer(this.renderer)
19 |
20 | this.composer.addPass(new RenderPass(this.scene, this.camera))
21 |
22 | for (let i = 0; i < this.passes.length; i++) {
23 | this.composer.addPass(this.passes[i].constructor)
24 | if (i === this.passes.length - 1) {
25 | this.passes[i].constructor.renderToScreen = true
26 | }
27 | }
28 |
29 | this.addGUI()
30 | }
31 |
32 | addGUI () {
33 | GUI.add(this, 'active').name('PostProcessing')
34 | }
35 |
36 | getPass (name) {
37 | return this.passes.find(pass => pass.name === name).constructor
38 | }
39 |
40 | update (delta, time) {
41 | if (this.active && this.passes.length) {
42 | this.composer.render(delta, time)
43 | } else {
44 | this.renderer.render(this.scene, this.camera)
45 | }
46 | }
47 | }
48 |
49 | export default PostProcessing
50 |
--------------------------------------------------------------------------------
/src/postProcessing/functions/fxaa/index.glsl:
--------------------------------------------------------------------------------
1 | #define FXAA_REDUCE_MIN (1.0 / 128.0)
2 | #define FXAA_REDUCE_MUL (1.0 / 8.0)
3 | #define FXAA_SPAN_MAX 8.0
4 |
5 | vec4 fxaa(sampler2D tInput, vec2 uv, vec2 resolution) {
6 | vec2 res = 1. / resolution;
7 |
8 | vec3 rgbNW = texture2D( tInput, ( uv.xy + vec2( -1.0, -1.0 ) * res ) ).xyz;
9 | vec3 rgbNE = texture2D( tInput, ( uv.xy + vec2( 1.0, -1.0 ) * res ) ).xyz;
10 | vec3 rgbSW = texture2D( tInput, ( uv.xy + vec2( -1.0, 1.0 ) * res ) ).xyz;
11 | vec3 rgbSE = texture2D( tInput, ( uv.xy + vec2( 1.0, 1.0 ) * res ) ).xyz;
12 | vec4 rgbaM = texture2D( tInput, uv.xy * res );
13 | vec3 rgbM = rgbaM.xyz;
14 | vec3 luma = vec3( 0.299, 0.587, 0.114 );
15 |
16 | float lumaNW = dot( rgbNW, luma );
17 | float lumaNE = dot( rgbNE, luma );
18 | float lumaSW = dot( rgbSW, luma );
19 | float lumaSE = dot( rgbSE, luma );
20 | float lumaM = dot( rgbM, luma );
21 | float lumaMin = min( lumaM, min( min( lumaNW, lumaNE ), min( lumaSW, lumaSE ) ) );
22 | float lumaMax = max( lumaM, max( max( lumaNW, lumaNE) , max( lumaSW, lumaSE ) ) );
23 |
24 | vec2 dir;
25 | dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
26 | dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
27 |
28 | float dirReduce = max( ( lumaNW + lumaNE + lumaSW + lumaSE ) * ( 0.25 * FXAA_REDUCE_MUL ), FXAA_REDUCE_MIN );
29 |
30 | float rcpDirMin = 1.0 / ( min( abs( dir.x ), abs( dir.y ) ) + dirReduce );
31 | dir = min( vec2( FXAA_SPAN_MAX, FXAA_SPAN_MAX),
32 | max( vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
33 | dir * rcpDirMin)) * res;
34 | vec4 rgbA = (1.0 / 2.0) * (
35 | texture2D(tInput, uv.xy + dir * (1.0 / 3.0 - 0.5)) +
36 | texture2D(tInput, uv.xy + dir * (2.0 / 3.0 - 0.5)));
37 | vec4 rgbB = rgbA * (1.0 / 2.0) + (1.0 / 4.0) * (
38 | texture2D(tInput, uv.xy + dir * (0.0 / 3.0 - 0.5)) +
39 | texture2D(tInput, uv.xy + dir * (3.0 / 3.0 - 0.5)));
40 | float lumaB = dot(rgbB, vec4(luma, 0.0));
41 |
42 | if ( ( lumaB < lumaMin ) || ( lumaB > lumaMax ) ) {
43 | return rgbA;
44 | } else {
45 | return rgbB;
46 | }
47 | }
48 |
49 | #pragma glslify: export(fxaa)
50 |
--------------------------------------------------------------------------------
/src/postProcessing/functions/rgb/index.glsl:
--------------------------------------------------------------------------------
1 | vec4 rgb(sampler2D tInput, vec2 pos, vec2 resolution, vec2 delta) {
2 | vec2 dir = pos - vec2(0.5);
3 | float d = .7 * length(dir);
4 | normalize(dir);
5 | vec2 value = d * dir * delta;
6 |
7 | vec4 c1 = texture2D(tInput, pos - value / resolution.x);
8 | vec4 c2 = texture2D(tInput, pos);
9 | vec4 c3 = texture2D(tInput, pos + value / resolution.y);
10 |
11 | return vec4(c1.r, c2.g, c3.b, c1.a + c2.a + c3.b);
12 | }
13 |
14 | #pragma glslify: export(rgb)
15 |
--------------------------------------------------------------------------------
/src/postProcessing/functions/tilt-shift/index.glsl:
--------------------------------------------------------------------------------
1 | const float steps = 3.0;
2 |
3 | const float minOffs = (float(steps - 1.0)) / -2.0;
4 | const float maxOffs = (float(steps - 1.0)) / +2.0;
5 |
6 | vec4 tiltShift(sampler2D tInput, vec2 uv, float blurAmount, float center, float stepSize) {
7 | float amount;
8 | vec4 blurred;
9 |
10 | amount = pow((uv.y * center) * 2.0 - 1.0, 2.0) * blurAmount;
11 |
12 | blurred = vec4(0.0, 0.0, 0.0, 1.0);
13 |
14 | for (float offsX = minOffs; offsX <= maxOffs; ++offsX) {
15 | for (float offsY = minOffs; offsY <= maxOffs; ++offsY) {
16 | vec2 temp_vUv = uv.xy;
17 |
18 | temp_vUv.x += offsX * amount * stepSize;
19 | temp_vUv.y += offsY * amount * stepSize;
20 |
21 | blurred += texture2D(tInput, temp_vUv);
22 | }
23 | }
24 |
25 | blurred /= float(steps * steps);
26 |
27 | return blurred;
28 | }
29 |
30 | #pragma glslify: export(tiltShift)
31 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/BloomPass/BloomPass.js:
--------------------------------------------------------------------------------
1 | import Pass from '../Pass'
2 | import copyVertexShader from '../../shaders/basic.vert'
3 | import copyFragmentShader from '../../shaders/copy.frag'
4 | import convolVertexShader from './convolution.vert'
5 | import convolFragmentShader from './convolution.frag'
6 |
7 | const blur = {
8 | x: new THREE.Vector2(0.001953125, 0.0),
9 | y: new THREE.Vector2(0.0, 0.001953125)
10 | }
11 |
12 | class BloomPass extends Pass {
13 | constructor ({ strength = 1, kernelSize = 16, sigma = 5.0, resolution = 256 }) {
14 | super()
15 |
16 | const options = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat }
17 |
18 | // Render targets
19 | this.renderTargetX = new THREE.WebGLRenderTarget(resolution, resolution, options)
20 | this.renderTargetY = new THREE.WebGLRenderTarget(resolution, resolution, options)
21 |
22 | // Copy material
23 | this.materialCopy = new THREE.ShaderMaterial({
24 | uniforms: {
25 | tInput: { value: null },
26 | uOpacity: { value: strength }
27 | },
28 | vertexShader: copyVertexShader,
29 | fragmentShader: copyFragmentShader,
30 | blending: THREE.AdditiveBlending,
31 | transparent: true
32 | })
33 |
34 | // Convolution material
35 | this.materialConvolution = new THREE.ShaderMaterial({
36 | defines: {
37 | KERNEL_SIZE_FLOAT: kernelSize.toFixed(1),
38 | KERNEL_SIZE_INT: kernelSize.toFixed(0)
39 | },
40 | uniforms: {
41 | tInput: { value: null },
42 | uImageIncrement: { value: new THREE.Vector2() },
43 | cKernel: { value: [] }
44 | },
45 | vertexShader: convolVertexShader,
46 | fragmentShader: convolFragmentShader
47 | })
48 |
49 | this.materialConvolution.uniforms['uImageIncrement'].value = blur.x
50 | this.materialConvolution.uniforms['cKernel'].value = this.buildKernel(sigma, kernelSize)
51 |
52 | this.needsSwap = false
53 |
54 | this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
55 | this.scene = new THREE.Scene()
56 |
57 | this.quad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), null)
58 | this.scene.add(this.quad)
59 | }
60 |
61 | buildKernel (sigma, kernelSize) {
62 | const values = new Array(kernelSize)
63 |
64 | const halfWidth = (kernelSize - 1) * 0.5
65 |
66 | let sum = 0.0
67 |
68 | for (let i = 0; i < kernelSize; ++i) {
69 | values[i] = this.gauss(i - halfWidth, sigma)
70 | sum += values[i]
71 | }
72 |
73 | // Normalize the kernel
74 | for (let i = 0; i < kernelSize; ++i) {
75 | values[i] /= sum
76 | }
77 |
78 | return values
79 | }
80 |
81 | gauss (x, sigma) {
82 | return Math.exp(-(x * x) / (2.0 * sigma * sigma))
83 | }
84 |
85 | render (renderer, writeBuffer, readBuffer, delta, time, maskActive) {
86 | const oldAutoClear = renderer.autoClear
87 | renderer.autoClear = false
88 |
89 | if (maskActive) {
90 | renderer.context.disable(renderer.context.STENCIL_TEST)
91 | }
92 |
93 | // Render quad with blured scene into texture (convolution pass 1)
94 | this.quad.material = this.materialConvolution
95 |
96 | this.materialConvolution.uniforms['tInput'].value = readBuffer.texture
97 | this.materialConvolution.uniforms['uImageIncrement'].value = blur.x
98 |
99 | renderer.render(this.scene, this.camera, this.renderTargetX, true)
100 |
101 | // Render quad with blured scene into texture (convolution pass 2)
102 | this.materialConvolution.uniforms['tInput'].value = this.renderTargetX.texture
103 | this.materialConvolution.uniforms['uImageIncrement'].value = blur.y
104 |
105 | renderer.render(this.scene, this.camera, this.renderTargetY, true)
106 |
107 | // Render original scene with superimposed blur to texture
108 | this.quad.material = this.materialCopy
109 |
110 | this.materialCopy.uniforms['tInput'].value = this.renderTargetY.texture
111 |
112 | if (maskActive) {
113 | renderer.context.enable(renderer.context.STENCIL_TEST)
114 | }
115 |
116 | renderer.render(this.scene, this.camera, readBuffer, this.clear)
117 | renderer.autoClear = oldAutoClear
118 | }
119 | }
120 |
121 | export default BloomPass
122 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/BloomPass/convolution.frag:
--------------------------------------------------------------------------------
1 | uniform float cKernel[KERNEL_SIZE_INT];
2 |
3 | uniform sampler2D tInput;
4 | uniform vec2 uImageIncrement;
5 |
6 | varying vec2 vUv;
7 |
8 | void main() {
9 | vec2 imageCoord = vUv;
10 | vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
11 |
12 | for (int i = 0; i < KERNEL_SIZE_INT; i ++) {
13 | sum += texture2D(tInput, imageCoord) * cKernel[ i ];
14 | imageCoord += uImageIncrement;
15 | }
16 |
17 | gl_FragColor = sum;
18 | }
19 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/BloomPass/convolution.vert:
--------------------------------------------------------------------------------
1 | uniform vec2 uImageIncrement;
2 |
3 | varying vec2 vUv;
4 |
5 | void main() {
6 | vUv = uv - ((KERNEL_SIZE_FLOAT - 1.0) / 2.0) * uImageIncrement;
7 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
8 | }
9 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/BloomUnrealPass/BloomUnrealPass.js:
--------------------------------------------------------------------------------
1 | import Pass from '../Pass'
2 | import basicVertexShader from '../../shaders/basic.vert'
3 | import copyFragmentShader from '../../shaders/copy.frag'
4 | import highFragmentShader from '../../shaders/luminosity-high.frag'
5 | import blurFragmentShader from './blur.frag'
6 | import compositeFragmentShader from './composite.frag'
7 |
8 | const blurDirection = {
9 | x: new THREE.Vector2(1.0, 0.0),
10 | y: new THREE.Vector2(0.0, 1.0)
11 | }
12 |
13 | class UnrealBloomPass extends Pass {
14 | constructor ({ resolution = new THREE.Vector2(256, 256), strength = 1, radius = 1, threshold = 0.8 }) {
15 | super()
16 |
17 | this.resolution = resolution
18 | this.strength = strength
19 | this.radius = radius
20 | this.threshold = threshold
21 |
22 | // Render targets
23 | const options = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat }
24 | this.renderTargetsHorizontal = []
25 | this.renderTargetsVertical = []
26 | this.nMips = 5
27 | let resx = Math.round(this.resolution.x / 2)
28 | let resy = Math.round(this.resolution.y / 2)
29 |
30 | this.renderTargetBright = new THREE.WebGLRenderTarget(resx, resy, options)
31 | this.renderTargetBright.texture.generateMipmaps = false
32 |
33 | for (let i = 0; i < this.nMips; i++) {
34 | let renderTarget = new THREE.WebGLRenderTarget(resx, resy, options)
35 | renderTarget.texture.generateMipmaps = false
36 |
37 | this.renderTargetsHorizontal.push(renderTarget)
38 |
39 | renderTarget = new THREE.WebGLRenderTarget(resx, resy, options)
40 |
41 | renderTarget.texture.generateMipmaps = false
42 | this.renderTargetsVertical.push(renderTarget)
43 |
44 | resx = Math.round(resx / 2)
45 | resy = Math.round(resy / 2)
46 | }
47 |
48 | // Luminosity high pass material
49 | this.materialHighPassFilter = new THREE.ShaderMaterial({
50 | uniforms: {
51 | tInput: { value: null },
52 | luminosityThreshold: { value: 1.0 },
53 | smoothWidth: { value: 1.0 },
54 | defaultColor: { value: new THREE.Color(0x000000) },
55 | defaultOpacity: { value: 0.0 }
56 | },
57 | vertexShader: basicVertexShader,
58 | fragmentShader: highFragmentShader
59 | })
60 |
61 | this.materialHighPassFilter.uniforms['luminosityThreshold'].value = threshold
62 | this.materialHighPassFilter.uniforms['smoothWidth'].value = 0.01
63 |
64 | // Gaussian Blur Materials
65 | this.separableBlurMaterials = []
66 | const kernelSizeArray = [3, 5, 7, 9, 11]
67 |
68 | for (let i = 0; i < this.nMips; i++) {
69 | const kernelRadius = kernelSizeArray[i]
70 | const seperableBlurMaterial = new THREE.ShaderMaterial({
71 | defines: {
72 | KERNEL_RADIUS: kernelRadius,
73 | SIGMA: kernelRadius
74 | },
75 | uniforms: {
76 | colorTexture: { value: null },
77 | texSize: { value: new THREE.Vector2(0.5, 0.5) },
78 | direction: { value: new THREE.Vector2(0.5, 0.5) }
79 | },
80 | vertexShader: basicVertexShader,
81 | fragmentShader: blurFragmentShader
82 | })
83 | this.separableBlurMaterials.push(seperableBlurMaterial)
84 | this.separableBlurMaterials[i].uniforms['texSize'].value = new THREE.Vector2(resx, resy)
85 |
86 | resx = Math.round(resx / 2)
87 | resy = Math.round(resy / 2)
88 | }
89 |
90 | // Composite material
91 | this.compositeMaterial = new THREE.ShaderMaterial({
92 | defines: {
93 | NUM_MIPS: this.nMips
94 | },
95 | uniforms: {
96 | blurTexture1: { value: null },
97 | blurTexture2: { value: null },
98 | blurTexture3: { value: null },
99 | blurTexture4: { value: null },
100 | blurTexture5: { value: null },
101 | dirtTexture: { value: null },
102 | bloomStrength: { value: 1.0 },
103 | bloomFactors: { value: null },
104 | bloomTintColors: { value: null },
105 | bloomRadius: { value: 0.0 }
106 | },
107 | vertexShader: basicVertexShader,
108 | fragmentShader: compositeFragmentShader
109 | })
110 | this.compositeMaterial.uniforms['blurTexture1'].value = this.renderTargetsVertical[0].texture
111 | this.compositeMaterial.uniforms['blurTexture2'].value = this.renderTargetsVertical[1].texture
112 | this.compositeMaterial.uniforms['blurTexture3'].value = this.renderTargetsVertical[2].texture
113 | this.compositeMaterial.uniforms['blurTexture4'].value = this.renderTargetsVertical[3].texture
114 | this.compositeMaterial.uniforms['blurTexture5'].value = this.renderTargetsVertical[4].texture
115 | this.compositeMaterial.uniforms['bloomStrength'].value = strength
116 | this.compositeMaterial.uniforms['bloomRadius'].value = 0.1
117 | this.compositeMaterial.needsUpdate = true
118 |
119 | const bloomFactors = [1.0, 0.8, 0.6, 0.4, 0.2]
120 | this.compositeMaterial.uniforms['bloomFactors'].value = bloomFactors
121 | this.bloomTintColors = [
122 | new THREE.Vector3(1, 1, 1),
123 | new THREE.Vector3(1, 1, 1),
124 | new THREE.Vector3(1, 1, 1),
125 | new THREE.Vector3(1, 1, 1),
126 | new THREE.Vector3(1, 1, 1)
127 | ]
128 | this.compositeMaterial.uniforms['bloomTintColors'].value = this.bloomTintColors
129 |
130 | // Copy material
131 | this.materialCopy = new THREE.ShaderMaterial({
132 | uniforms: {
133 | tInput: { value: null },
134 | uOpacity: { value: 1.0 }
135 | },
136 | vertexShader: basicVertexShader,
137 | fragmentShader: copyFragmentShader,
138 | blending: THREE.AdditiveBlending,
139 | depthTest: false,
140 | depthWrite: false,
141 | transparent: true
142 | })
143 |
144 | this.enabled = true
145 | this.needsSwap = false
146 |
147 | this.oldClearColor = new THREE.Color()
148 | this.oldClearAlpha = 1
149 |
150 | this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
151 | this.scene = new THREE.Scene()
152 |
153 | this.quad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), null)
154 | this.scene.add(this.quad)
155 | }
156 |
157 | dispose () {
158 | for (let i = 0; i < this.renderTargetsHorizontal.length(); i++) {
159 | this.renderTargetsHorizontal[i].dispose()
160 | }
161 |
162 | for (let i = 0; i < this.renderTargetsVertical.length(); i++) {
163 | this.renderTargetsVertical[i].dispose()
164 | }
165 |
166 | this.renderTargetBright.dispose()
167 | }
168 |
169 | setSize (width, height) {
170 | let resx = Math.round(width / 2)
171 | let resy = Math.round(height / 2)
172 |
173 | this.renderTargetBright.setSize(resx, resy)
174 |
175 | for (let i = 0; i < this.nMips; i++) {
176 | this.renderTargetsHorizontal[i].setSize(resx, resy)
177 | this.renderTargetsVertical[i].setSize(resx, resy)
178 |
179 | this.separableBlurMaterials[i].uniforms['texSize'].value = new THREE.Vector2(resx, resy)
180 |
181 | resx = Math.round(resx / 2)
182 | resy = Math.round(resy / 2)
183 | }
184 | }
185 |
186 | render (renderer, writeBuffer, readBuffer, delta, time, maskActive) {
187 | this.oldClearColor.copy(renderer.getClearColor())
188 | this.oldClearAlpha = renderer.getClearAlpha()
189 | const oldAutoClear = renderer.autoClear
190 | renderer.autoClear = false
191 |
192 | renderer.setClearColor(new THREE.Color(0, 0, 0), 0)
193 |
194 | if (maskActive) {
195 | renderer.context.disable(renderer.context.STENCIL_TEST)
196 | }
197 |
198 | // 1. Extract Bright Areas
199 | this.materialHighPassFilter.uniforms['tInput'].value = readBuffer.texture
200 | this.materialHighPassFilter.uniforms['luminosityThreshold'].value = this.threshold
201 | this.quad.material = this.materialHighPassFilter
202 | renderer.render(this.scene, this.camera, this.renderTargetBright, true)
203 |
204 | // 2. Blur All the mips progressively
205 | let inputRenderTarget = this.renderTargetBright
206 |
207 | for (let i = 0; i < this.nMips; i++) {
208 | this.quad.material = this.separableBlurMaterials[i]
209 |
210 | this.separableBlurMaterials[i].uniforms['colorTexture'].value = inputRenderTarget.texture
211 | this.separableBlurMaterials[i].uniforms['direction'].value = blurDirection.x
212 |
213 | renderer.render(this.scene, this.camera, this.renderTargetsHorizontal[i], true)
214 |
215 | this.separableBlurMaterials[i].uniforms['colorTexture'].value = this.renderTargetsHorizontal[i].texture
216 | this.separableBlurMaterials[i].uniforms['direction'].value = blurDirection.y
217 |
218 | renderer.render(this.scene, this.camera, this.renderTargetsVertical[i], true)
219 |
220 | inputRenderTarget = this.renderTargetsVertical[i]
221 | }
222 |
223 | // Composite All the mips
224 | this.quad.material = this.compositeMaterial
225 | this.compositeMaterial.uniforms['bloomStrength'].value = this.strength
226 | this.compositeMaterial.uniforms['bloomRadius'].value = this.radius
227 | this.compositeMaterial.uniforms['bloomTintColors'].value = this.bloomTintColors
228 | renderer.render(this.scene, this.camera, this.renderTargetsHorizontal[0], true)
229 |
230 | // Blend it additively over the input texture
231 | this.quad.material = this.materialCopy
232 | this.materialCopy.uniforms['tInput'].value = this.renderTargetsHorizontal[0].texture
233 |
234 | if (maskActive) {
235 | renderer.context.enable(renderer.context.STENCIL_TEST)
236 | }
237 |
238 | renderer.render(this.scene, this.camera, readBuffer, this.clear)
239 |
240 | renderer.setClearColor(this.oldClearColor, this.oldClearAlpha)
241 | renderer.autoClear = oldAutoClear
242 | }
243 | }
244 |
245 | export default UnrealBloomPass
246 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/BloomUnrealPass/blur.frag:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | uniform sampler2D colorTexture;
4 | uniform vec2 texSize;
5 | uniform vec2 direction;
6 |
7 | varying vec2 vUv;
8 |
9 | float gaussianPdf(in float x, in float sigma) {
10 | return 0.39894 * exp(-0.5 * x * x / (sigma * sigma)) / sigma;
11 | }
12 |
13 | void main() {
14 | vec2 invSize = 1.0 / texSize;
15 | float fSigma = float(SIGMA);
16 | float weightSum = gaussianPdf(0.0, fSigma);
17 | vec3 diffuseSum = texture2D(colorTexture, vUv).rgb * weightSum;
18 |
19 | for (int i = 1; i < KERNEL_RADIUS; i ++) {
20 | float x = float(i);
21 | float w = gaussianPdf(x, fSigma);
22 | vec2 uvOffset = direction * invSize * x;
23 | vec3 sample1 = texture2D(colorTexture, vUv + uvOffset).rgb;
24 | vec3 sample2 = texture2D(colorTexture, vUv - uvOffset).rgb;
25 | diffuseSum += (sample1 + sample2) * w;
26 | weightSum += 2.0 * w;
27 | }
28 |
29 | gl_FragColor = vec4(diffuseSum / weightSum, 1.0);
30 | }
31 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/BloomUnrealPass/composite.frag:
--------------------------------------------------------------------------------
1 | uniform sampler2D blurTexture1;
2 | uniform sampler2D blurTexture2;
3 | uniform sampler2D blurTexture3;
4 | uniform sampler2D blurTexture4;
5 | uniform sampler2D blurTexture5;
6 | uniform sampler2D dirtTexture;
7 |
8 | uniform float bloomStrength;
9 | uniform float bloomRadius;
10 |
11 | uniform float bloomFactors[NUM_MIPS];
12 | uniform vec3 bloomTintColors[NUM_MIPS];
13 |
14 | varying vec2 vUv;
15 |
16 | float lerpBloomFactor(const in float factor) {
17 | float mirrorFactor = 1.2 - factor;
18 | return mix(factor, mirrorFactor, bloomRadius);
19 | }
20 |
21 | void main() {
22 | gl_FragColor = bloomStrength * (lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) +
23 | lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) +
24 | lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) +
25 | lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) +
26 | lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv));
27 | }
28 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/ClearMaskPass/ClearMaskPass.js:
--------------------------------------------------------------------------------
1 | import Pass from '../Pass'
2 |
3 | class ClearMaskPass extends Pass {
4 | constructor () {
5 | super()
6 |
7 | this.needsSwap = false
8 | }
9 |
10 | render (renderer, writeBuffer, readBuffer, delta, time, maskActive) {
11 | renderer.state.buffers.stencil.setTest(false)
12 | }
13 | }
14 |
15 | export default ClearMaskPass
16 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/MaskPass/MaskPass.js:
--------------------------------------------------------------------------------
1 | import Pass from '../Pass'
2 |
3 | class MaskPass extends Pass {
4 | constructor (scene, camera) {
5 | super()
6 |
7 | this.scene = scene
8 | this.camera = camera
9 |
10 | this.clear = true
11 | this.needsSwap = false
12 |
13 | this.inverse = false
14 | }
15 |
16 | render (renderer, writeBuffer, readBuffer, delta, maskActive) {
17 | const context = renderer.context
18 | const state = renderer.state
19 |
20 | // don't update color or depth
21 | state.buffers.color.setMask(false)
22 | state.buffers.depth.setMask(false)
23 |
24 | // lock buffers
25 | state.buffers.color.setLocked(true)
26 | state.buffers.depth.setLocked(true)
27 |
28 | // set up stencil
29 | var writeValue, clearValue
30 |
31 | if (this.inverse) {
32 | writeValue = 0
33 | clearValue = 1
34 | } else {
35 | writeValue = 1
36 | clearValue = 0
37 | }
38 |
39 | state.buffers.stencil.setTest(true)
40 | state.buffers.stencil.setOp(context.REPLACE, context.REPLACE, context.REPLACE)
41 | state.buffers.stencil.setFunc(context.ALWAYS, writeValue, 0xffffffff)
42 | state.buffers.stencil.setClear(clearValue)
43 |
44 | // draw into the stencil buffer
45 | renderer.render(this.scene, this.camera, readBuffer, this.clear)
46 | renderer.render(this.scene, this.camera, writeBuffer, this.clear)
47 |
48 | // unlock color and depth buffer for subsequent rendering
49 | state.buffers.color.setLocked(false)
50 | state.buffers.depth.setLocked(false)
51 |
52 | // only render where stencil is set to 1
53 | state.buffers.stencil.setFunc(context.EQUAL, 1, 0xffffffff) // draw if == 1
54 | state.buffers.stencil.setOp(context.KEEP, context.KEEP, context.KEEP)
55 | }
56 | }
57 |
58 | export default MaskPass
59 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/Pass.js:
--------------------------------------------------------------------------------
1 | class Pass {
2 | constructor () {
3 | // If set to true, the pass indicates to swap read and write buffer after rendering
4 | this.needsSwap = true
5 |
6 | // If set to true, the pass clears its buffer before rendering
7 | this.clear = false
8 |
9 | // If set to true, the result of the pass is rendered to screen
10 | this.renderToScreen = false
11 | }
12 |
13 | setSize (width, height) {}
14 |
15 | render (renderer, writeBuffer, readBuffer, delta, time, maskActive) {
16 | console.error('THREE.Pass: .render() must be implemented in derived pass.')
17 | }
18 | }
19 |
20 | export default Pass
21 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/PostPass/PostPass.js:
--------------------------------------------------------------------------------
1 | import ShaderPass from '../ShaderPass/ShaderPass'
2 | import fragmentShader from './post.frag'
3 |
4 | class PostPass extends ShaderPass {
5 | constructor () {
6 | super()
7 |
8 | this.material.uniforms = {
9 | ...this.material.uniforms,
10 | uResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
11 | uTime: { value: 0 },
12 | uVignette: { value: 1.9 },
13 | uRgbSplit: { value: new THREE.Vector2() }
14 | }
15 |
16 | this.material.fragmentShader = fragmentShader
17 | }
18 |
19 | setSize (width, height) {
20 | this.material.uniforms.uResolution.value = new THREE.Vector2(width, height)
21 | }
22 |
23 | render (renderer, writeBuffer, readBuffer, delta, time, maskActive) {
24 | super.render(renderer, writeBuffer, readBuffer, delta, time, maskActive)
25 |
26 | this.material.uniforms.uTime.value = time
27 | renderer.autoClear = false
28 | }
29 | }
30 |
31 | export default PostPass
32 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/PostPass/post.frag:
--------------------------------------------------------------------------------
1 | #pragma glslify: fxaa = require('../../functions/fxaa');
2 | #pragma glslify: vignette = require('color-shader-functions/vignette');
3 |
4 | uniform sampler2D tInput;
5 | uniform float uTime;
6 | uniform float uVignette;
7 | uniform vec2 uResolution;
8 | uniform vec2 uRgbSplit;
9 |
10 | varying vec2 vUv;
11 |
12 | void main() {
13 | vec4 color = texture2D(tInput, vUv);
14 |
15 | color = fxaa(tInput, vUv, uResolution);
16 | color = vignette(color, 1.1, uVignette, uResolution);
17 |
18 | gl_FragColor = color;
19 | }
20 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/RenderPass/RenderPass.js:
--------------------------------------------------------------------------------
1 | import Pass from '../Pass'
2 |
3 | class RenderPass extends Pass {
4 | constructor (scene, camera, overrideMaterial, clearColor, clearAlpha = 0) {
5 | super()
6 |
7 | this.scene = scene
8 | this.camera = camera
9 |
10 | this.overrideMaterial = overrideMaterial
11 |
12 | this.clearColor = clearColor
13 | this.clearAlpha = clearAlpha
14 |
15 | this.clear = true
16 | this.needsSwap = false
17 | }
18 |
19 | render (renderer, writeBuffer, readBuffer, delta, time, maskActive) {
20 | const oldAutoClear = renderer.autoClear
21 | renderer.autoClear = false
22 |
23 | this.scene.overrideMaterial = this.overrideMaterial
24 |
25 | let oldClearColor, oldClearAlpha
26 |
27 | if (this.clearColor) {
28 | oldClearColor = renderer.getClearColor().getHex()
29 | oldClearAlpha = renderer.getClearAlpha()
30 |
31 | renderer.setClearColor(this.clearColor, this.clearAlpha)
32 | }
33 |
34 | renderer.render(this.scene, this.camera, this.renderToScreen ? null : readBuffer, this.clear)
35 |
36 | if (this.clearColor) {
37 | renderer.setClearColor(oldClearColor, oldClearAlpha)
38 | }
39 |
40 | this.scene.overrideMaterial = null
41 | renderer.autoClear = oldAutoClear
42 | }
43 | }
44 |
45 | export default RenderPass
46 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/ShaderPass/ShaderPass.js:
--------------------------------------------------------------------------------
1 | import Pass from '../Pass'
2 | import vertexShader from '../../shaders/basic.vert'
3 | import fragmentShader from '../../shaders/copy.frag'
4 |
5 | class ShaderPass extends Pass {
6 | constructor (shader) {
7 | super()
8 |
9 | this.material = new THREE.ShaderMaterial({
10 | uniforms: {
11 | tInput: { value: null },
12 | uOpacity: { value: 1.0 }
13 | },
14 | vertexShader,
15 | fragmentShader
16 | })
17 |
18 | this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
19 | this.scene = new THREE.Scene()
20 |
21 | this.quad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), this.material)
22 | this.scene.add(this.quad)
23 | }
24 |
25 | render (renderer, writeBuffer, readBuffer, delta, time, maskActive) {
26 | this.material.uniforms['tInput'].value = readBuffer.texture
27 |
28 | if (this.renderToScreen) {
29 | renderer.render(this.scene, this.camera)
30 | } else {
31 | renderer.render(this.scene, this.camera, writeBuffer, this.clear)
32 | }
33 | }
34 | }
35 |
36 | export default ShaderPass
37 |
--------------------------------------------------------------------------------
/src/postProcessing/passes/index.js:
--------------------------------------------------------------------------------
1 | import RenderPass from './RenderPass/RenderPass'
2 | import PostPass from './PostPass/PostPass'
3 | import BloomPass from './BloomPass/BloomPass'
4 | import BloomUnrealPass from './BloomUnrealPass/BloomUnrealPass'
5 | import MaskPass from './MaskPass/MaskPass'
6 | import ShaderPass from './ShaderPass/ShaderPass'
7 | import ClearMaskPass from './ClearMaskPass/ClearMaskPass'
8 |
9 | export {
10 | RenderPass,
11 | PostPass,
12 | BloomPass,
13 | BloomUnrealPass,
14 | MaskPass,
15 | ShaderPass,
16 | ClearMaskPass
17 | }
18 |
--------------------------------------------------------------------------------
/src/postProcessing/shaders/basic.frag:
--------------------------------------------------------------------------------
1 | void main() {
2 | gl_FragColor = vec4(1.0);
3 | }
4 |
--------------------------------------------------------------------------------
/src/postProcessing/shaders/basic.vert:
--------------------------------------------------------------------------------
1 | varying vec2 vUv;
2 |
3 | void main() {
4 | vUv = uv;
5 |
6 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
7 | }
8 |
--------------------------------------------------------------------------------
/src/postProcessing/shaders/copy.frag:
--------------------------------------------------------------------------------
1 | uniform sampler2D tInput;
2 | uniform float uOpacity;
3 |
4 | varying vec2 vUv;
5 |
6 | void main() {
7 | vec4 color = texture2D(tInput, vUv);
8 | gl_FragColor = color * uOpacity;
9 | }
10 |
--------------------------------------------------------------------------------
/src/postProcessing/shaders/luminosity-high.frag:
--------------------------------------------------------------------------------
1 | uniform sampler2D tInput;
2 | uniform vec3 defaultColor;
3 | uniform float defaultOpacity;
4 | uniform float luminosityThreshold;
5 | uniform float smoothWidth;
6 |
7 | varying vec2 vUv;
8 |
9 | void main() {
10 |
11 | vec4 texel = texture2D(tInput, vUv);
12 | vec3 luma = vec3(0.299, 0.587, 0.114 );
13 | float v = dot(texel.xyz, luma);
14 |
15 | vec4 outputColor = vec4(defaultColor.rgb, defaultOpacity);
16 | float alpha = smoothstep(luminosityThreshold, luminosityThreshold + smoothWidth, v);
17 |
18 | gl_FragColor = mix(outputColor, texel, alpha);
19 | }
20 |
--------------------------------------------------------------------------------
/src/shaders/cube.frag:
--------------------------------------------------------------------------------
1 | uniform float time;
2 | uniform vec3 color;
3 |
4 | void main() {
5 |
6 | vec3 newColor = vec3(color.r * sin(time), color.g * sin(time + 10.0), color.b * sin(time + 20.0));
7 | gl_FragColor = vec4(newColor, 1.0);
8 |
9 | }
--------------------------------------------------------------------------------
/src/shaders/cube.vert:
--------------------------------------------------------------------------------
1 | void main() {
2 |
3 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
4 |
5 | }
--------------------------------------------------------------------------------
/src/signals/Window.js:
--------------------------------------------------------------------------------
1 | import Signal from 'quark-signal'
2 | import debounce from 'lodash.debounce'
3 |
4 | class Window {
5 | constructor () {
6 | this.width = window.innerWidth
7 | this.height = window.innerHeight
8 |
9 | this.createSignals()
10 | this.bind()
11 | }
12 |
13 | createSignals () {
14 | this.onResize = new Signal()
15 | }
16 |
17 | bind () {
18 | this.handleResize = this.handleResize.bind(this)
19 | window.addEventListener('resize', debounce(this.handleResize, 100))
20 | }
21 |
22 | handleResize () {
23 | this.width = window.innerWidth
24 | this.height = window.innerHeight
25 | this.onResize.dispatch(this.width, this.height)
26 | }
27 | }
28 |
29 | export default new Window()
30 |
--------------------------------------------------------------------------------
/src/stylesheets/main.styl:
--------------------------------------------------------------------------------
1 | html,
2 | body
3 | width 100%
4 | height 100%
5 | margin 0
6 | padding 0
7 | overflow hidden
8 |
9 | #webgl-container
10 | width 100%
11 | height 100%
12 |
13 | /* dat.gui */
14 | .dg.main
15 | position absolute
16 | top 0
17 | right 0
18 |
--------------------------------------------------------------------------------
/src/utils/math/clamp.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Clamp a value between two bounds
3 | *
4 | * @param {number} min Minimum boundary
5 | * @param {number} max Maximum boundary
6 | * @param {number} v Value to clamp
7 | * @return {number} Clamped value
8 | */
9 | export default function clamp (min, max, v) {
10 | if (v < min) return min
11 | if (v > max) return max
12 | return v
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/math/index.js:
--------------------------------------------------------------------------------
1 | export clamp from './clamp'
2 | export lerp from './lerp'
3 | export map from './map'
4 | export normalize from './normalize'
5 | export randomFloat from './random-float'
6 | export randomHexColor from './random-hex-color'
7 | export randomInt from './random-int'
8 | export toRadians from './to-radians'
9 |
--------------------------------------------------------------------------------
/src/utils/math/lerp.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Linear interpolation between two values (lerping)
3 | *
4 | * @param {number} x First point
5 | * @param {number} y Second point
6 | * @param {number} r Value to interpolate
7 | * @return {number} Lerped value
8 | */
9 | export default function lerp (x, y, r) {
10 | return x + ((y - x) * r)
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/math/map.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Re-maps a number from one range to another
3 | *
4 | * @param {number} value The incoming value to be converted
5 | * @param {number} start1 Lower bound of the value's current range
6 | * @param {number} stop1 Upper bound of the value's current range
7 | * @param {number} start2 Lower bound of the value's target range
8 | * @param {number} stop2 Upper bound of the value's target range
9 | * @return {number} Remapped number
10 | */
11 | export default function map (value, start1, stop1, start2, stop2) {
12 | return ((value - start1) / (stop1 - start1)) * (stop2 - start2) + start2
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/math/normalize.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Normalize a value between two bounds
3 | *
4 | * @param {number} min Minimum boundary
5 | * @param {number} max Maximum boundary
6 | * @param {number} x Value to normalize
7 | * @return {number} Normalized value
8 | */
9 | export default function normalize (min, max, x) {
10 | return (x - min) / (max - min)
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/math/random-float.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generate a random float
3 | *
4 | * @param {number} minValue Minimum boundary
5 | * @param {number} maxValue Maximum boundary
6 | * @param {number} precision Precision
7 | * @return {number} Generated float
8 | */
9 | export default function randomFloat (minValue, maxValue, precision = 2) {
10 | return parseFloat(Math.min(minValue + (Math.random() * (maxValue - minValue)), maxValue).toFixed(precision))
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/math/random-hex-color.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generate a random hexadecimal color
3 | *
4 | * @return {string} Hexadecimal color
5 | */
6 | export default function randomHexColor () {
7 | return '#' + Math.floor(Math.random() * 16777215).toString(16)
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/math/random-int.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generate a random integer
3 | *
4 | * @param {number} min Minimum boundary
5 | * @param {number} max Maximum boundary
6 | * @return {number} Generated integer
7 | */
8 | export default function randomInt (min, max) {
9 | return Math.floor(Math.random() * (max - min + 1) + min)
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/math/to-radians.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Convert a degrees value into radians
3 | *
4 | * @param {number} degrees Degrees
5 | * @return {number} Radians
6 | */
7 | export default function toRadians (degrees) {
8 | return degrees * Math.PI / 180
9 | }
10 |
--------------------------------------------------------------------------------