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.
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