Created
May 23, 2017 07:51
-
-
Save vaalentin/f1c30e8c404f956c30089971b3e8708a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import THREE from 'three' | |
| import TrackballControl from 'three.trackball' | |
| import { textureLoader } from 'utils' | |
| const passThroughMaterial = new THREE.RawShaderMaterial({ | |
| uniforms: { | |
| uTexture: { type: 't', value: null } | |
| }, | |
| vertexShader: ` | |
| attribute vec3 position; | |
| attribute vec2 uv; | |
| varying vec2 vUv; | |
| void main() { | |
| vUv = uv; | |
| gl_Position = vec4(position, 1.0); | |
| } | |
| `, | |
| fragmentShader: ` | |
| precision mediump float; | |
| uniform sampler2D uTexture; | |
| varying vec2 vUv; | |
| void main() { | |
| gl_FragColor = texture2D(uTexture, vUv); | |
| } | |
| ` | |
| }) | |
| const blurMaterial = new THREE.RawShaderMaterial({ | |
| uniforms: { | |
| uTexture: { type: 't', value: null }, | |
| uResolution: { type: 'v2', value: new THREE.Vector2(0, 0) }, | |
| uDirection: { type: 'v2', value: new THREE.Vector2(0, 0) } | |
| }, | |
| vertexShader: ` | |
| attribute vec3 position; | |
| attribute vec2 uv; | |
| varying vec2 vUv; | |
| void main() { | |
| vUv = uv; | |
| gl_Position = vec4(position, 1.0); | |
| } | |
| `, | |
| fragmentShader: ` | |
| precision mediump float; | |
| vec4 blur9(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) { | |
| vec4 color = vec4(0.0); | |
| vec2 off1 = vec2(1.3846153846) * direction; | |
| vec2 off2 = vec2(3.2307692308) * direction; | |
| color += texture2D(image, uv) * 0.2270270270; | |
| color += texture2D(image, uv + (off1 / resolution)) * 0.3162162162; | |
| color += texture2D(image, uv - (off1 / resolution)) * 0.3162162162; | |
| color += texture2D(image, uv + (off2 / resolution)) * 0.0702702703; | |
| color += texture2D(image, uv - (off2 / resolution)) * 0.0702702703; | |
| return color; | |
| } | |
| uniform sampler2D uTexture; | |
| uniform vec2 uResolution; | |
| uniform vec2 uDirection; | |
| varying vec2 vUv; | |
| void main() { | |
| vec4 textureColor = texture2D(uTexture, vUv); | |
| gl_FragColor = blur9(uTexture, vUv, uResolution, uDirection); | |
| } | |
| ` | |
| }) | |
| const postProcessMaterial = new THREE.RawShaderMaterial({ | |
| uniforms: { | |
| uTexture: { type: 't', value: null }, | |
| uLookupTexture: { type: 't', value: null }, | |
| uResolution: { type: 'v2', value: new THREE.Vector2(0, 0) } | |
| }, | |
| vertexShader: ` | |
| attribute vec3 position; | |
| attribute vec2 uv; | |
| varying vec2 vUv; | |
| void main() { | |
| vUv = uv; | |
| gl_Position = vec4(position, 1.0); | |
| } | |
| `, | |
| fragmentShader: ` | |
| precision mediump float; | |
| #define LUT_FLIP_Y | |
| // lookup | |
| vec4 lookup(in vec4 textureColor, in sampler2D lookupTable) { | |
| #ifndef LUT_NO_CLAMP | |
| textureColor = clamp(textureColor, 0.0, 1.0); | |
| #endif | |
| mediump float blueColor = textureColor.b * 63.0; | |
| mediump vec2 quad1; | |
| quad1.y = floor(floor(blueColor) / 8.0); | |
| quad1.x = floor(blueColor) - (quad1.y * 8.0); | |
| mediump vec2 quad2; | |
| quad2.y = floor(ceil(blueColor) / 8.0); | |
| quad2.x = ceil(blueColor) - (quad2.y * 8.0); | |
| highp vec2 texPos1; | |
| texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); | |
| texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); | |
| #ifdef LUT_FLIP_Y | |
| texPos1.y = 1.0-texPos1.y; | |
| #endif | |
| highp vec2 texPos2; | |
| texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); | |
| texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); | |
| #ifdef LUT_FLIP_Y | |
| texPos2.y = 1.0-texPos2.y; | |
| #endif | |
| lowp vec4 newColor1 = texture2D(lookupTable, texPos1); | |
| lowp vec4 newColor2 = texture2D(lookupTable, texPos2); | |
| lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor)); | |
| return newColor; | |
| } | |
| uniform sampler2D uTexture; | |
| uniform sampler2D uLookupTexture; | |
| uniform vec2 uResolution; | |
| varying vec2 vUv; | |
| void main() { | |
| vec4 textureColor = texture2D(uTexture, vUv); | |
| gl_FragColor = lookup(textureColor, uLookupTexture); | |
| } | |
| ` | |
| }) | |
| const zoomBlurMaterial = new THREE.RawShaderMaterial({ | |
| uniforms: { | |
| uTexture: { type: 't', value: null }, | |
| uResolution: { type: 'v2', value: new THREE.Vector2(0, 0) }, | |
| uCenter: { type: 'v2', value: new THREE.Vector2(0, 0) }, | |
| uRadius: { type: 'f', value: 0 } | |
| }, | |
| vertexShader: ` | |
| attribute vec3 position; | |
| attribute vec2 uv; | |
| varying vec2 vUv; | |
| void main() { | |
| vUv = uv; | |
| gl_Position = vec4(position, 1.0); | |
| } | |
| `, | |
| fragmentShader: ` | |
| precision mediump float; | |
| uniform sampler2D uTexture; | |
| uniform vec2 uResolution; | |
| uniform vec2 uCenter; | |
| uniform float uRadius; | |
| varying vec2 vUv; | |
| float random(vec3 scale,float seed) { | |
| return fract(sin(dot(gl_FragCoord.xyz+seed, scale)) * 43758.5453 + seed); | |
| } | |
| void main() { | |
| // from https://github.com/spite/Wagner/blob/master/fragment-shaders/zoom-blur-fs.glsl | |
| vec4 color = vec4(0.0); | |
| float total = 0.0; | |
| vec2 toCenter = uCenter - vUv * uResolution; | |
| float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0); | |
| for(float i = 0.0; i <= 40.0; i++) { | |
| float percent = (i + offset) / 40.0; | |
| float weight = 4.0*(percent - percent * percent); | |
| vec4 sample = texture2D(uTexture, vUv + toCenter * percent * uRadius / uResolution); | |
| sample.rgb *= sample.a; | |
| color += sample * weight; | |
| total += weight; | |
| } | |
| gl_FragColor = color / total; | |
| } | |
| ` | |
| }) | |
| textureLoader.load('public/images/lookup.png', texture => { | |
| texture.generateMipmaps = false | |
| texture.minFilter = THREE.LinearFilter; | |
| texture.magFilter = THREE.LinearFilter; | |
| texture.wrapS = THREE.ClampToEdgeWrapping; | |
| texture.wrapT = THREE.ClampToEdgeWrapping; | |
| postProcessMaterial.uniforms.uLookupTexture.value = texture | |
| }) | |
| export default class Scene { | |
| /** | |
| * @param {HTMLElement} [$container] | |
| */ | |
| constructor($container, controls) { | |
| this._$container = $container | |
| const { offsetWidth: width, offsetHeight: height } = this._$container | |
| this._scene = this._getScene() | |
| this._screenScene = this._getScene() | |
| this._camera = this._getCamera(width, height) | |
| this._renderer = this._getRenderer(width, height) | |
| this._$container.appendChild(this._renderer.domElement) | |
| if(controls) { | |
| this._controls = this._getControls(this._camera, this._renderer) | |
| } | |
| this._fbos = this._getFbos(width, height) | |
| this._currentFboIndex = 0 | |
| this._screenQuad = new THREE.Mesh( | |
| new THREE.PlaneBufferGeometry(2, 2), | |
| null | |
| ) | |
| this._blurEnabled = true | |
| this._zoomBlurEnabled = true | |
| this._postProcessEnabled = true | |
| this._blur = false | |
| this._blurPasses = 6 | |
| this._blurRadius = new THREE.Vector3(0, 0, 0) | |
| this._zoomBlur = false | |
| blurMaterial.uniforms.uResolution.value.set(width, height) | |
| zoomBlurMaterial.uniforms.uResolution.value.set(width, height) | |
| zoomBlurMaterial.uniforms.uCenter.value.set(width / 2, height / 2) | |
| postProcessMaterial.uniforms.uResolution.value.set(width, height) | |
| this._screenScene.add(this._screenQuad) | |
| this._frameId = null | |
| this._bindMethods() | |
| this._addListeners() | |
| this._update() | |
| window.scene = this | |
| } | |
| _bindMethods() { | |
| this._handleResizeBound = this._handleResize.bind(this) | |
| this._updateBound = this._update.bind(this) | |
| this._renderBound = this._render.bind(this) | |
| } | |
| _addListeners() { | |
| window.addEventListener('resize', this._handleResizeBound) | |
| if(this._controls) { | |
| this._controls.addEventListener('change', this._renderBound) | |
| } | |
| } | |
| _removeListeners() { | |
| window.removeEventListener('resize', this._handleResizeBound) | |
| if(this._controls) { | |
| this._controls.removeEventListener('change', this._renderBound) | |
| } | |
| } | |
| /** | |
| * @param {float} width | |
| * @param {float} height | |
| * @returns {Array<THREE.WebGLRenderTarget} | |
| */ | |
| _getFbos(width, height) { | |
| return [ | |
| new THREE.WebGLRenderTarget(width, height, { | |
| minFilter: THREE.LinearFilter, | |
| magFilter: THREE.NearestFilter, | |
| format: THREE.RGBAFormat, | |
| depthBuffer: true, | |
| stencilBuffer: false | |
| }), | |
| new THREE.WebGLRenderTarget(width, height, { | |
| minFilter: THREE.LinearFilter, | |
| magFilter: THREE.NearestFilter, | |
| format: THREE.RGBAFormat, | |
| depthBuffer: true, | |
| stencilBuffer: false | |
| }) | |
| ] | |
| } | |
| _resetFboIndex() { | |
| this._currentFboIndex = 0 | |
| } | |
| _incrementFboIndex() { | |
| this._currentFboIndex = ++this._currentFboIndex === this._fbos.length ? 0 : this._currentFboIndex | |
| } | |
| /** | |
| * @returns {THREE.Scene} | |
| */ | |
| _getScene() { | |
| return new THREE.Scene() | |
| } | |
| /** | |
| * @param {float} [width] | |
| * @param {float} [height] | |
| * @returns {THREE.Camera} | |
| */ | |
| _getCamera(width, height) { | |
| const camera = new THREE.PerspectiveCamera(45, width / height, 1, 100) | |
| camera.position.z = 5 | |
| return camera | |
| } | |
| /** | |
| * @param {float} [width] | |
| * @param {float} [height] | |
| * @returns {THREE.WebGLRenderer} | |
| */ | |
| _getRenderer(width, height) { | |
| const renderer = new THREE.WebGLRenderer({ | |
| alpha: true, | |
| antialias: true | |
| }) | |
| renderer.setSize(width, height) | |
| renderer.setClearColor(0x000000, 0) | |
| return renderer | |
| } | |
| /** | |
| * @param {THREE.Camera} [camera] | |
| * @param {THREE.WebGLRenderer} [renderer] | |
| * @returns {TrackballControl} | |
| */ | |
| _getControls(camera, renderer) { | |
| const controls = new TrackballControl(camera) | |
| Object.assign(controls, { | |
| rotateSpeed: 1, | |
| zoomSpeed: 1, | |
| panSpeed: .8, | |
| noZoom: false, | |
| noPan: false, | |
| staticMoving: false, | |
| dynamicDampingFactor : .3 | |
| }) | |
| return controls | |
| } | |
| _handleResize() { | |
| const { offsetWidth: width, offsetHeight: height } = this._$container | |
| this._camera.aspect = width / height | |
| this._camera.updateProjectionMatrix() | |
| this._renderer.setSize(width, height) | |
| for(let renderTarget of this._fbos) { | |
| renderTarget.setSize(width, height) | |
| } | |
| blurMaterial.uniforms.uResolution.value.set(width, height) | |
| zoomBlurMaterial.uniforms.uResolution.value.set(width, height) | |
| zoomBlurMaterial.uniforms.uCenter.value.set(width / 2, height / 2) | |
| postProcessMaterial.uniforms.uResolution.value.set(width, height) | |
| } | |
| _update() { | |
| this._frameId = requestAnimationFrame(this._updateBound) | |
| if(this._controls) { | |
| this._controls.update() | |
| } | |
| this._render() | |
| } | |
| _render() { | |
| // no need for fbos | |
| // render directly to screen | |
| if(!this._blurEnabled && !this._zoomBlurEnabled && !this._postProcessEnabled) { | |
| this._renderer.render(this._scene, this._camera) | |
| return | |
| } | |
| this._resetFboIndex() | |
| // first, render scene to texture | |
| this._renderer.render(this._scene, this._camera, this._fbos[this._currentFboIndex]) | |
| // blur | |
| if(this._blurEnabled && this._blur) { | |
| this._screenQuad.material = blurMaterial | |
| for(let i = this._blurPasses - 1; i >= 0; --i) { | |
| const blurRadiusX = (this._blurRadius.x * (i + 1)) / this._blurPasses | |
| const blurRadiusY = (this._blurRadius.y * (i + 1)) / this._blurPasses | |
| // horizontal | |
| blurMaterial.uniforms.uDirection.value.set(blurRadiusX, 0) | |
| blurMaterial.uniforms.uTexture.value = this._fbos[this._currentFboIndex].texture | |
| this._incrementFboIndex() | |
| this._renderer.render(this._screenScene, this._camera, this._fbos[this._currentFboIndex]) | |
| // vertical | |
| blurMaterial.uniforms.uDirection.value.set(0, blurRadiusY) | |
| blurMaterial.uniforms.uTexture.value = this._fbos[this._currentFboIndex].texture | |
| if(i !== 0) { | |
| // next pass | |
| this._incrementFboIndex() | |
| this._renderer.render(this._screenScene, this._camera, this._fbos[this._currentFboIndex]) | |
| } | |
| } | |
| } | |
| // zoom blur | |
| if(this._zoomBlurEnabled && this._zoomBlur) { | |
| this._screenQuad.material = zoomBlurMaterial | |
| zoomBlurMaterial.uniforms.uTexture.value = this._fbos[this._currentFboIndex].texture | |
| zoomBlurMaterial.uniforms.uRadius.value = this._blurRadius.z | |
| this._incrementFboIndex() | |
| this._renderer.render(this._screenScene, this._camera, this._fbos[this._currentFboIndex]) | |
| } | |
| // post process | |
| if(this._postProcessEnabled) { | |
| this._screenQuad.material = postProcessMaterial | |
| postProcessMaterial.uniforms.uTexture.value = this._fbos[this._currentFboIndex].texture | |
| this._renderer.render(this._screenScene, this._camera) | |
| } else { | |
| this._screenQuad.material = passThroughMaterial | |
| passThroughMaterial.uniforms.uTexture.value = this._fbos[this._currentFboIndex].texture | |
| this._renderer.render(this._screenScene, this._camera) | |
| } | |
| } | |
| /** | |
| * @param {THREE.Object3D|Array<THREE.Object3D>} [children] | |
| */ | |
| add(...children) { | |
| for(let i = 0; i < children.length; ++i) { | |
| this._scene.add(children[i]) | |
| } | |
| } | |
| /** | |
| * @param {THREE.Object3D|Array<THREE.Object3D>} [children] | |
| */ | |
| remove(...children) { | |
| for(let i = 0; i < children.length; ++i) { | |
| this._scene.remove(children[i]) | |
| } | |
| } | |
| /** | |
| * @param {float} blurX | |
| */ | |
| setBlurRadiusX(blurX) { | |
| this._blurRadius.x = blurX | |
| this._blur = this._blurRadius.x !== 0 || this._blurRadius.y !== 0 | |
| } | |
| /** | |
| * @param {float} blurY | |
| */ | |
| setBlurRadiusY(blurY) { | |
| this._blurRadius.y = blurY | |
| this._blur = this._blurRadius.x !== 0 || this._blurRadius.y !== 0 | |
| } | |
| /** | |
| * @param {float} blurZ | |
| */ | |
| setBlurRadiusZ(blurZ) { | |
| this._blurRadius.z = blurZ | |
| this._zoomBlur = this._blurRadius !== 0 | |
| } | |
| /** | |
| * @param {float} fov | |
| */ | |
| setFov(fov) { | |
| this._camera.fov = fov | |
| this._camera.updateProjectionMatrix() | |
| } | |
| dispose() { | |
| this._removeListeners() | |
| cancelAnimationFrame(this._frameId) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment