body {
margin: 0;
padding: 0;
}
// Port from Shadertoy to THREE.js: https://www.shadertoy.com/view/4sG3WV
const VERTEX_SHADER = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`
const BUFFER_A_FRAG = `
uniform vec4 iMouse;
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform vec2 iResolution;
uniform float iFrame;
varying vec2 vUv;
#define mousedata(a,b) texture2D( iChannel1, (0.5+vec2(a,b)) / iResolution.xy, -0.0 )
#define backbuffer(uv) texture2D( iChannel0, uv ).xy
float lineDist(vec2 p, vec2 start, vec2 end, float width) {
vec2 dir = start - end;
float lngth = length(dir);
dir /= lngth;
vec2 proj = max(0.0, min(lngth, dot((start - p), dir))) * dir;
return length( (start - p) - proj ) - (width / 2.0);
}
void main() {
vec2 uv = vUv;
vec2 col = uv;
if (iFrame > 2.) {
col = texture2D(iChannel0,uv).xy;
vec2 mouse = iMouse.xy/iResolution.xy;
vec2 p_mouse = mousedata(2.,0.).xy;
if (mousedata(4.,0.).x > 0.) {
col = backbuffer(uv+((p_mouse-mouse)*clamp(1.-(lineDist(uv,mouse,p_mouse,0.)*20.),0.,1.)*.7));
}
}
gl_FragColor = vec4(col,0.0,1.0);
}
`
const BUFFER_B_FRAG = `
uniform vec4 iMouse;
uniform sampler2D iChannel0;
uniform vec3 iResolution;
varying vec2 vUv;
bool pixelAt(vec2 coord, float a, float b) {
return (floor(coord.x) == a && floor(coord.y) == b);
}
vec4 backbuffer(float a,float b) {
return texture2D( iChannel0, (0.5+vec2(a,b)) / iResolution.xy, -100.0 );
}
void main( ) {
vec2 uv = vUv;// / iResolution.xy;
vec4 color = texture2D(iChannel0,uv);
if (pixelAt(gl_FragCoord.xy,0.,0.)) { //Surface position
gl_FragColor = vec4(backbuffer(0.,0.).rg+(backbuffer(4.,0.).r*(backbuffer(2.,0.).rg-backbuffer(1.,0.).rg)),0.,1.);
} else if (pixelAt(gl_FragCoord.xy,1.,0.)) { //New mouse position
gl_FragColor = vec4(iMouse.xy/iResolution.xy,0.,1.);
} else if (pixelAt(gl_FragCoord.xy,2.,0.)) { //Old mouse position
gl_FragColor = vec4(backbuffer(1.,0.).rg,0.,1.);
} else if (pixelAt(gl_FragCoord.xy,3.,0.)) { //New mouse holded
gl_FragColor = vec4(clamp(iMouse.z,0.,1.),0.,0.,1.);
} else if (pixelAt(gl_FragCoord.xy,4.,0.)) { //Old mouse holded
gl_FragColor = vec4(backbuffer(3.,0.).r,0.,0.,1.);
} else {
gl_FragColor = vec4(0.,0.,0.,1.);
}
}
`
const BUFFER_FINAL_FRAG = `
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
varying vec2 vUv;
void main() {
vec2 uv = vUv;
vec2 a = texture2D(iChannel1,uv).xy;
gl_FragColor = vec4(texture2D(iChannel0,a).rgb,1.0);
}
`
class App {
private width = 1024
private height = 512
private renderer = new THREE.WebGLRenderer()
private loader = new THREE.TextureLoader()
private mousePosition = new THREE.Vector4()
private orthoCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
private counter = 0
constructor() {
this.renderer.setSize(this.width, this.height)
document.body.appendChild(this.renderer.domElement)
this.renderer.domElement.addEventListener('mousedown', () => {
this.mousePosition.setZ(1)
this.counter = 0
})
this.renderer.domElement.addEventListener('mouseup', () => {
this.mousePosition.setZ(0)
})
this.renderer.domElement.addEventListener('mousemove', event => {
this.mousePosition.setX(event.clientX)
this.mousePosition.setY(this.height - event.clientY)
})
}
private targetA = new BufferManager(this.renderer, { width: this.width, height: this.height })
private targetB = new BufferManager(this.renderer, { width: this.width, height: this.height })
private targetC = new BufferManager(this.renderer, { width: this.width, height: this.height })
private bufferA: BufferShader
private bufferB: BufferShader
private bufferImage: BufferShader
public start() {
const resolution = new THREE.Vector3(this.width, this.height, window.devicePixelRatio)
const channel0 = this.loader.load('https://res.cloudinary.com/di4jisedp/image/upload/v1523722553/wallpaper.jpg')
this.loader.setCrossOrigin('')
this.bufferA = new BufferShader(BUFFER_A_FRAG, {
iFrame: { value: 0 },
iResolution: { value: resolution },
iMouse: { value: this.mousePosition },
iChannel0: { value: null },
iChannel1: { value: null }
})
this.bufferB = new BufferShader(BUFFER_B_FRAG, {
iFrame: { value: 0 },
iResolution: { value: resolution },
iMouse: { value: this.mousePosition },
iChannel0: { value: null }
})
this.bufferImage = new BufferShader(BUFFER_FINAL_FRAG, {
iResolution: { value: resolution },
iMouse: { value: this.mousePosition },
iChannel0: { value: channel0 },
iChannel1: { value: null }
})
this.animate()
}
private animate() {
requestAnimationFrame(() => {
this.bufferA.uniforms[ 'iFrame' ].value = this.counter++
this.bufferA.uniforms[ 'iChannel0' ].value = this.targetA.readBuffer.texture
this.bufferA.uniforms[ 'iChannel1' ].value = this.targetB.readBuffer.texture
this.targetA.render(this.bufferA.scene, this.orthoCamera)
this.bufferB.uniforms[ 'iChannel0' ].value = this.targetB.readBuffer.texture
this.targetB.render(this.bufferB.scene, this.orthoCamera)
this.bufferImage.uniforms[ 'iChannel1' ].value = this.targetA.readBuffer.texture
this.targetC.render(this.bufferImage.scene, this.orthoCamera, true)
this.animate()
})
}
}
class BufferShader {
public material: THREE.ShaderMaterial
public scene: THREE.Scene
constructor(fragmentShader: string, public uniforms: {} = {}) {
this.material = new THREE.ShaderMaterial({
fragmentShader,
vertexShader: VERTEX_SHADER,
uniforms
})
this.scene = new THREE.Scene()
this.scene.add(
new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), this.material)
)
}
}
class BufferManager {
public readBuffer: THREE.WebGLRenderTarget
public writeBuffer: THREE.WebGLRenderTarget
constructor(private renderer: THREE.WebGLRenderer, { width, height }) {
this.readBuffer = new THREE.WebGLRenderTarget(width, height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
type: THREE.FloatType,
stencilBuffer: false
})
this.writeBuffer = this.readBuffer.clone()
}
public swap() {
const temp = this.readBuffer
this.readBuffer = this.writeBuffer
this.writeBuffer = temp
}
public render(scene: THREE.Scene, camera: THREE.Camera, toScreen: boolean = false) {
if (toScreen) {
this.renderer.render(scene, camera)
} else {
this.renderer.render(scene, camera, this.writeBuffer, true)
}
this.swap()
}
}
document.addEventListener('DOMContentLoaded', () => {
(new App()).start()
})
View Compiled
This Pen doesn't use any external CSS resources.