Last active
October 7, 2019 12:10
-
-
Save artemuzz/e9029f8ea374d839a30bab1ef81d187b to your computer and use it in GitHub Desktop.
Soothe
This file contains 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
<script src="https://threejs.org/build/three.js"></script> |
This file contains 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
// | |
// shaders | |
// | |
const basic_vert = ` | |
precision highp float; | |
void main() { | |
gl_Position = vec4( position, vec2(1.0) ); | |
} | |
`; | |
const prep_frag = ` | |
precision highp float; | |
void main() { | |
gl_FragColor.z = 0.012; | |
} | |
`; | |
const physics_frag = ` | |
precision highp float; | |
uniform vec3 mouse; | |
uniform vec3 pmouse; | |
uniform vec2 resolution; | |
uniform sampler2D texture; | |
float distToSegment( vec2 x1, vec2 x2, vec2 p ) { | |
vec2 v = x2 - x1; | |
vec2 w = p - x1; | |
float c1 = dot(w,v); | |
float c2 = dot(v,v); | |
// if c2 <= c1 == c1 | |
// if c2 > c1 == c2 | |
float div = mix( c2, c1, step( c2, c1 ) ); | |
// if c1 < 0 == 0.0 | |
float mult = step( 0.0, c1 ); | |
float b = c1 * mult / div; | |
vec2 pb = x1 + b*v; | |
return distance( p, pb ); | |
} | |
vec3 computeNormal( vec4 n ) { | |
// pixel scale | |
vec2 un = 1. / resolution; | |
vec2 uv = gl_FragCoord.xy * un; | |
// tex sample neighbour-4; | |
vec3 n_r = texture2D( texture, uv + vec2( 1, 0 ) * un ).xyz; | |
vec3 n_l = texture2D( texture, uv - vec2( 1, 0 ) * un ).xyz; | |
vec3 n_u = texture2D( texture, uv + vec2( 0, 1 ) * un ).xyz; | |
vec3 n_d = texture2D( texture, uv - vec2( 0, 1 ) * un ).xyz; | |
// partial differences n-4; | |
vec4 dn = vec4( n.z ); | |
dn -= vec4( n_r.z, n_l.z, n_u.z, n_d.z ); | |
// right - left, up - down; | |
vec2 xy = vec2( dn.x - dn.y, dn.z - dn.w ); | |
xy += n_r.xy + n_l.xy + n_u.xy + n_d.xy; | |
xy *= 0.972; // energy dissipation | |
float z; | |
z += dot( n_r.xy, - vec2( 1, 0 ) ); | |
z += dot( n_l.xy, + vec2( 1, 0 ) ); | |
z += dot( n_u.xy, - vec2( 0, 1 ) ); | |
z += dot( n_d.xy, + vec2( 0, 1 ) ); | |
return vec3( xy , z ) * 0.25; | |
} | |
void main() { | |
vec2 uv = gl_FragCoord.xy / resolution; | |
float asp = resolution.x / resolution.y; // aspect | |
// normal sampling | |
vec4 h = texture2D( texture, uv ); | |
// previous velocity | |
float vel = h.a; | |
// apply elastic-viscous acceleration | |
// acc = - offset*elasticity - vel*viscosity | |
vel += - ( h.z - 0.012 ) * 0.016 - vel * 0.059; | |
// compute normal advection | |
vec3 f = computeNormal( h ); | |
f.z += h.z + vel; | |
// mouse interaction - continuous distance from mouse | |
float dist = distToSegment( | |
vec2( pmouse.x * asp, pmouse.y), // previous mouse | |
vec2( mouse.x * asp, mouse.y), // current mouse | |
vec2( uv.x * asp, uv.y) // fragcoord | |
); | |
float mSize = 0.065; // mouse radius | |
float peak = 0.9; // max-height | |
float isDisp = step( 0.5, mouse.z ); // is displaced | |
if ( mouse.z > 0.5 && dist <= mSize ) { | |
float dst = ( mSize - dist ) / mSize; | |
f.z += pow( abs(dst), 1.9 ) * peak * 2.5; | |
f.xy -= f.xy * pow( abs(dst), 3.9 ) * 0.1; | |
f.z = min( peak, f.z ); | |
} | |
gl_FragColor = clamp( vec4( f, vel ), -1.0, 1.0); | |
} | |
`; | |
const light_frag = ` | |
precision highp float; | |
#define RECIPROCAL_PI 0.31830988618 | |
uniform vec2 resolution; | |
uniform sampler2D texture; | |
float rand(vec2 co){ | |
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); | |
} | |
// based on https://www.shadertoy.com/view/MslGR8 | |
vec3 dithering( vec3 color ) { | |
//Calculate grid position | |
float grid_position = rand( gl_FragCoord.xy ); | |
//Shift the individual colors differently, thus making it even harder to see the dithering pattern | |
vec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 ); | |
//modify shift acording to grid position. | |
dither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position ); | |
//shift the color by dither_shift | |
return color + dither_shift_RGB; | |
} | |
void main() { | |
vec2 uv = gl_FragCoord.xy / resolution.xy; | |
vec3 N = texture2D( texture, uv ).xyz; | |
vec3 viewPos = vec3( 0.0, 0.0, 1.2 ); | |
vec3 lightPos = vec3( 0.0, 1.5, 0.98 ); | |
vec3 fragPos = vec3( ( 2.0 * uv - 1.0 ), N.z ); | |
vec3 L = normalize( lightPos - fragPos ); | |
vec3 H = normalize( L + normalize( viewPos - fragPos ) ); | |
vec3 dN = vec3( N.xy, N.z/2.0 + 0.28 ); | |
float dif = max( dot( dN, L ), 0.0 ); | |
float spec = clamp( dot( normalize(N), H ), 0.0, 1.0 ); | |
float attenuation = 1.0 - length( lightPos - fragPos ) / 3.1; | |
vec3 dif_int = vec3( dif * 0.056 * attenuation ); | |
float shininess = 4.8; | |
float ref = RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( spec, shininess ); | |
vec3 spec_int = vec3( ref * 0.38 * pow( attenuation, 3.0 ) ); | |
vec3 col = dif_int + spec_int; | |
col = pow( col, vec3( 1.0 / 2.2 ) ); | |
col.r = mix( col.r * 1.28, col.r, length( dif_int ) * 1.2 / 3.0 ); | |
// col += 0.045; | |
gl_FragColor = vec4( dithering(col), 1.0 ); | |
} | |
`; | |
// | |
// three.js setup | |
// | |
const w = window.innerWidth; | |
const h = window.innerHeight; | |
const res = new THREE.Vector2(w, h); | |
const mousecoord = new THREE.Vector3(); | |
const mouse = new THREE.Vector3(); | |
const pmouse = new THREE.Vector3(); | |
var renderer = new THREE.WebGLRenderer(); | |
renderer.setSize(w, h); | |
document.body.appendChild(renderer.domElement); | |
const scene = new THREE.Scene(); | |
const camera = new THREE.Camera(); | |
// render targets | |
let rtt = new THREE.WebGLRenderTarget(w, h, { | |
minFilter: THREE.LinearFilter, | |
magFilter: THREE.LinearFilter, | |
format: THREE.RGBAFormat, | |
type: ( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) ? THREE.HalfFloatType : THREE.FloatType, | |
depthTest: false, | |
depthBuffer: false, | |
stencilBuffer: false | |
}); | |
let rtt2 = rtt.clone(); | |
// | |
// materials | |
// | |
const copyMaterial = new THREE.ShaderMaterial({ | |
vertexShader: basic_vert, | |
fragmentShader: prep_frag, | |
blending: THREE.NoBlending, | |
transparent: false, | |
fog: false, | |
lights: false, | |
depthWrite: false, | |
depthTest: false | |
}); | |
const physicsMaterial = new THREE.ShaderMaterial({ | |
uniforms: { | |
mouse: { type: 'v3', value: mouse }, | |
pmouse: { type: 'v3', value: pmouse }, | |
resolution: { type: 'v2', value: res }, | |
texture: { type: 't' }, | |
}, | |
vertexShader: basic_vert, | |
fragmentShader: physics_frag, | |
blending: THREE.NoBlending, | |
transparent: false, | |
fog: false, | |
lights: false, | |
depthWrite: false, | |
depthTest: false | |
}); | |
const lightsMaterial = new THREE.ShaderMaterial({ | |
uniforms: { | |
resolution: { type: 'v2', value: res }, | |
texture: { type: 't' }, | |
}, | |
vertexShader: basic_vert, | |
fragmentShader: light_frag, | |
blending: THREE.NoBlending, | |
transparent: false, | |
fog: false, | |
lights: false, | |
depthWrite: false, | |
depthTest: false | |
}); | |
// | |
// mesh setup | |
// | |
const geometry = new THREE.BufferGeometry(); | |
const vertices = new Float32Array([ | |
-1.0, -1.0, | |
3.0, -1.0, | |
-1.0, 3.0 | |
]); | |
geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 2)); | |
mesh = new THREE.Mesh(geometry, copyMaterial); | |
mesh.frustumCulled = false; | |
scene.add(mesh); | |
// | |
// pre-render to rtt | |
// | |
renderer.setRenderTarget(rtt); | |
renderer.render(scene, camera); | |
renderer.setRenderTarget(rtt2); | |
renderer.render(scene, camera); | |
mesh.material = physicsMaterial; | |
// | |
// listeners | |
// | |
renderer.domElement.addEventListener('mousemove', mousemove); | |
renderer.domElement.addEventListener('mouseout', mouseout); | |
renderer.domElement.addEventListener('touchmove', touch); | |
renderer.domElement.addEventListener('touchstart', touch); | |
renderer.domElement.addEventListener('touchend', touchend); | |
window.addEventListener('resize', resize); | |
renderer.setAnimationLoop(function () { | |
render(); | |
}); | |
function render() { | |
const tmp = rtt; | |
rtt = rtt2; | |
rtt2 = tmp; | |
pmouse.copy(mouse); | |
mouse.copy(mousecoord); | |
if (pmouse.z == 0) pmouse.copy(mouse); | |
mesh.material = physicsMaterial; | |
mesh.material.uniforms.texture.value = rtt2.texture; | |
renderer.setRenderTarget(rtt); | |
renderer.render(scene, camera); | |
mesh.material = lightsMaterial; | |
mesh.material.uniforms.texture.value = rtt.texture; | |
renderer.setRenderTarget(null); | |
renderer.render(scene, camera); | |
} | |
function resize() { | |
const h = window.innerHeight; | |
const w = window.innerWidth; | |
res.set(w, h); | |
camera.aspect = w / h; | |
rtt.setSize(w, h); | |
rtt2.setSize(w, h); | |
renderer.setSize(w, h); | |
} | |
function mousemove(evt) { | |
mousecoord.x = evt.pageX / window.innerWidth; | |
mousecoord.y = 1 - (evt.pageY / window.innerHeight); | |
mousecoord.z = 1; | |
} | |
function mouseout(evt) { | |
mousecoord.z = 0; | |
} | |
function touch(evt) { | |
evt.preventDefault(); | |
mousecoord.x = evt.touches[0].pageX / window.innerWidth; | |
mousecoord.y = 1 - evt.touches[0].pageY / window.innerHeight; | |
mousecoord.z = 1; | |
} | |
function touchend(evt) { | |
mousecoord.z = 0; | |
} |
This file contains 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 url('https://fonts.googleapis.com/css?family=Open+Sans:600&display=swap'); | |
* { | |
margin: 0; | |
border: 0; | |
box-sizing: | |
border-box; | |
} | |
body { | |
margin: 0; | |
position: fixed; | |
overflow: hidden; | |
background-color: #090d13; | |
user-select: none; | |
} | |
canvas { | |
width: 100%; | |
height: 100%; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment