Skip to content

Instantly share code, notes, and snippets.

@artemuzz
Last active October 7, 2019 12:10
Show Gist options
  • Save artemuzz/e9029f8ea374d839a30bab1ef81d187b to your computer and use it in GitHub Desktop.
Save artemuzz/e9029f8ea374d839a30bab1ef81d187b to your computer and use it in GitHub Desktop.
Soothe
<script src="https://threejs.org/build/three.js"></script>
//
// 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;
}
@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