Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save vaalentin/f1c30e8c404f956c30089971b3e8708a to your computer and use it in GitHub Desktop.
Save vaalentin/f1c30e8c404f956c30089971b3e8708a to your computer and use it in GitHub Desktop.

Revisions

  1. vaalentin created this gist May 23, 2017.
    528 changes: 528 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,528 @@
    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)
    }
    }