A Pen by Lea Rosema on CodePen.
Created
November 28, 2020 04:42
-
-
Save lostintangent/ad09fe8fe20be528cf0b324dc1098390 to your computer and use it in GitHub Desktop.
ScrollTrigger Underwater world #anydayshaders 15
This file contains hidden or 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
<digital-art dpr="auto" aria-hidden="true"> | |
<script type="buffer" name="position" data-size="2"> | |
[-1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1] | |
</script> | |
<script type="vert"> | |
precision highp float; | |
uniform float time; | |
uniform vec2 resolution; | |
varying vec4 vPos; | |
attribute vec4 position; | |
void main() { | |
vPos = position; | |
gl_Position = position; | |
} | |
</script> | |
<script type="frag"> | |
precision highp float; | |
uniform vec2 resolution; | |
uniform float time; | |
uniform float scrollPos; | |
const float PI = 3.141592654; | |
const float gridSize = 10.; | |
const float fishGrid = 12.; | |
vec2 coords() { | |
vec2 p = gl_FragCoord.xy / resolution - .5; | |
float aspect = resolution.x / resolution.y; | |
p.x *= aspect; | |
return p; | |
} | |
vec2 rotate(vec2 p, float a) { | |
return vec2(p.x * cos(a) - p.y * sin(a), | |
p.x * sin(a) + p.y * cos(a)); | |
} | |
// function from https://www.shadertoy.com/view/3ll3zr | |
float sdHeart(in vec2 p, float s) { | |
p /= s; | |
vec2 q = p; | |
q.x *= 0.5 + .5 * q.y; | |
q.y -= abs(p.x) * .63; | |
return (length(q) - .7) * s; | |
} | |
float sdCircle(in vec2 p, float r) { | |
return length(p) - r; | |
} | |
float sdStar(in vec2 p, in float r, in int n, in float m) | |
{ | |
// next 4 lines can be precomputed for a given shape | |
float an = 3.141593/float(n); | |
float en = 3.141593/m; // m is between 2 and n | |
vec2 acs = vec2(cos(an),sin(an)); | |
vec2 ecs = vec2(cos(en),sin(en)); // ecs=vec2(0,1) for regular polygon, | |
float bn = mod(atan(p.x,p.y),2.0*an) - an; | |
p = length(p)*vec2(cos(bn),abs(sin(bn))); | |
p -= r*acs; | |
p += ecs*clamp( -dot(p,ecs), 0.0, r*acs.y/ecs.y); | |
return length(p)*sign(p.x); | |
} | |
float distanceField(vec2 p) { | |
// return sdHeart(p, 9.0 + 2.5 * sin(time * 1e-3)); | |
float t = time * 1e-3 * 20.; | |
return sdStar(rotate(mod(p, gridSize) - vec2(gridSize*.5) - vec2(sin(floor(p.y / gridSize) * gridSize + t * .03) * .7, sin(floor(p.x / gridSize) * gridSize + t * .05) * .3), t * .002 + floor(p.y / gridSize) * .2 + floor(p.x / gridSize) * .3), 4., 5, 3.5 + .1 | |
* floor(p.x / gridSize) + sin(t * .1) * .2) + sin(4. * p.x * .5 + time * 2e-3) * cos(p.y * .5 + time * 2e-3) * .05; | |
} | |
float bubblesDF(vec2 p) { | |
float grid = 3.; | |
float t = time * 1e-4; | |
float xi = floor(p.x / grid) * grid; | |
float yi = floor(p.y / grid) * grid; | |
vec2 p1 = vec2(sin(yi * .4 + t * 2.), cos(xi + t * 3.)) * grid * .25; | |
return sdCircle(mod(p, grid) - grid * .5 - p1, .5 + .2 * sin(t + xi)) + sin(p.x * 2.) * cos(p.y * 3.) * .1; | |
} | |
float subtract(float a, float b) { | |
return max(-a, b); | |
} | |
vec2 fishCoords(vec2 p) { | |
float grid = fishGrid; | |
float rot = 45. * PI / 180.; | |
vec2 motion = vec2(time * 1e-3, 0.); | |
float xi = floor(p.x / grid) * grid; | |
float yi = floor(p.y / grid) * grid; | |
return rotate(mod(rotate(p - motion, rot), grid) - (grid) * .5, -rot); | |
} | |
float fishDF(vec2 p) { | |
vec2 p0 = fishCoords(p); | |
float df = 9999.; | |
df = min(df, sdCircle(p0 * vec2(1.5, 1.) - vec2(.3, 0), 1.)); | |
df = subtract(sdCircle(p0 * vec2(1.5, 1.) - vec2(-.8, 0), 1.), df); | |
df = min(df, sdCircle(p0 - vec2(2., 0), 1.4)); | |
return df + sin(p.x) * cos(p.y * 2.) * .1; | |
} | |
vec3 clownFishTexture(vec2 p) { | |
vec2 p0 = fishCoords(p); | |
float y = sdCircle(p0 - vec2(2.7, .2), .2); | |
float x = -.3 + sin(p.x * 2. - time * 2e-3 + cos(p.y * 1.2)); | |
vec3 stripedOrange = vec3(.8, .3, .0) + smoothstep(0., .3, x); | |
vec3 black = vec3(0.); | |
return mix(black, stripedOrange, smoothstep(0., .1, y)); | |
} | |
vec3 shade(in vec2 p) { | |
vec2 p00 = coords() - vec2(0, scrollPos * .33); | |
vec2 p0 = p00 + vec2(sin(1. + time * 2e-4) * sin(p00.x * 10. + time * 1e-4) * .2, 0.); | |
float sdf = distanceField(p); | |
float sdfBubbles = bubblesDF(p); | |
vec3 fg = vec3(.2 + sin(floor(p.x/gridSize) * gridSize), .3+ .2 * sin(floor(p.y/gridSize) * gridSize), .5) * .7 | |
+ .5; | |
vec3 bg = vec3(0., .1, .2) + vec3(0, .1, .1) * smoothstep(0., .05, clamp(sin(p0.x * 10. + p0.y * 20. + time * 3e-4), 0., 1.)); | |
float star = smoothstep(0., .1, sdf); | |
float fish = smoothstep(0., .1, fishDF(p00 * 27.)); | |
vec3 fishFg = clownFishTexture(p00 * 27.); | |
vec3 bg2 = vec3(0, .1, .1) * smoothstep(0., .05, clamp(sin(p0.x * 5. + p0.y * 8.) * sin(2. + p0.x * 8. + p0.y * 16.), 0., 1.)); | |
vec3 bg3 = smoothstep(0., .1, -sdfBubbles) * vec3(.1); | |
return mix(fg, mix(fishFg, bg, fish), star) + bg2 + bg3; | |
} | |
void main () { | |
vec2 p0 = coords() - vec2(0., sin(time * 2e-5)) - vec2(0, scrollPos); | |
vec2 p = rotate(p0, 45. * PI / 180.); | |
vec3 col = shade(p * 27.); | |
gl_FragColor = vec4(col, 1.0); | |
} | |
</script> | |
</digital-art> |
This file contains hidden or 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
console.clear(); | |
class DigitalArt extends HTMLElement { | |
constructor() { | |
super(); | |
this.canvas = null; | |
this.gl = null; | |
this.onResize = this.onResize.bind(this); | |
this.onScroll = this.onScroll.bind(this); | |
this.loop = this.loop.bind(this); | |
} | |
static register() { | |
customElements.define("digital-art", DigitalArt); | |
} | |
connectedCallback() { | |
if (! this.gl) { | |
this.setup(); | |
} | |
} | |
disconnectedCallback() { | |
this.dispose(); | |
} | |
get devicePixelRatio() { | |
return parseFloat(this.getAttribute('dpr')) || window.devicePixelRatio; | |
} | |
onScroll(e) { | |
const { gl, program } = this; | |
const uScrollPos = gl.getUniformLocation(program, "scrollPos"); | |
gl.uniform1f(uScrollPos, window.scrollY / document.body.clientHeight); | |
} | |
onResize() { | |
const { canvas, gl, program } = this; | |
const width = this.clientWidth; | |
const height = this.clientHeight; | |
const dpr = this.devicePixelRatio; | |
canvas.width = width * dpr; | |
canvas.height = height * dpr; | |
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); | |
const uResolution = gl.getUniformLocation(program, "resolution"); | |
gl.uniform2fv(uResolution, [gl.drawingBufferWidth, gl.drawingBufferHeight]); | |
} | |
createShader(type, code) { | |
const { gl } = this; | |
const sh = gl.createShader(type, code); | |
gl.shaderSource(sh, code); | |
gl.compileShader(sh); | |
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) { | |
throw gl.getShaderInfoLog(sh); | |
} | |
return sh; | |
} | |
createBuffers() { | |
const { gl, program } = this; | |
const bufferScripts = [...this.querySelectorAll('[type=buffer]')]; | |
this.buffers = {}; | |
let count = -1; | |
bufferScripts.forEach(container => { | |
const name = container.getAttribute('name') || 'position'; | |
const recordSize = parseInt(container.getAttribute('data-size'), 10) || 1; | |
const data = new Float32Array(JSON.parse(container.textContent.trim())); | |
const buffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
gl.bufferData( | |
gl.ARRAY_BUFFER, | |
data, | |
gl.STATIC_DRAW | |
); | |
const attribLoc = gl.getAttribLocation(program, name); | |
this.buffers[name] = { buffer, data, attribLoc, recordSize }; | |
count = Math.max(count, (data.length / recordSize)|0); | |
gl.enableVertexAttribArray(attribLoc); | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
gl.vertexAttribPointer(attribLoc, recordSize, gl.FLOAT, false, 0, 0); | |
}); | |
this.count = count; | |
} | |
loop(time = 0) { | |
const { gl, program } = this; | |
const uTime = gl.getUniformLocation(program, "time"); | |
gl.uniform1f(uTime, time); | |
gl.drawArrays(gl.TRIANGLES, 0, this.count); | |
this.frame = requestAnimationFrame(this.loop); | |
} | |
createPrograms() { | |
const { gl } = this; | |
const fragScript = this.querySelector('[type=frag]'); | |
const vertScript = this.querySelector('[type=vert]'); | |
const HEADER = 'precision highp float;'; | |
const DEFAULT_VERT = HEADER + 'attribute vec4 position;void main(){gl_Position=position;}'; | |
const DEFAULT_FRAG = HEADER + 'void main(){gl_FragColor=vec4(1.,0,0,1.);}'; | |
this.fragCode = fragScript?.textContent || DEFAULT_FRAG; | |
this.vertCode = vertScript?.textContent || DEFAULT_VERT; | |
const program = gl.createProgram(); | |
this.program = program; | |
this.gl = gl; | |
this.fragShader = this.createShader(gl.FRAGMENT_SHADER, this.fragCode); | |
this.vertShader = this.createShader(gl.VERTEX_SHADER, this.vertCode); | |
gl.attachShader(program, this.fragShader); | |
gl.attachShader(program, this.vertShader); | |
gl.linkProgram(program); | |
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { | |
throw gl.getProgramInfoLog(program); | |
} | |
} | |
setup() { | |
this.canvas = document.createElement('canvas'); | |
this.dpr = window.devicePixelRatio; | |
this.appendChild(this.canvas); | |
this.gl = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl'); | |
this.createPrograms(); | |
const { program, gl } = this; | |
gl.useProgram(program); | |
this.createBuffers(); | |
gl.clearColor(0, 0, 0, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
this.onResize(); | |
window.addEventListener('resize', this.onResize, false); | |
window.addEventListener('scroll', this.onScroll, false); | |
this.frame = requestAnimationFrame(this.loop); | |
} | |
dispose() { | |
cancelAnimationFrame(this.loop); | |
this.frame = -1; | |
window.removeEventListener('scroll', this.onScroll, false); | |
window.removeEventListener('resize', this.onResize, false); | |
Object.entries(this.buffers).forEach(([name, buf]) => { | |
this.gl.deleteBuffer(buf.buffer); | |
}); | |
this.gl.deleteProgram(this.program); | |
const loseCtx = this.gl.getExtension('WEBGL_lose_context'); | |
if (loseCtx && typeof loseCtx.loseContext === 'function') { | |
loseCtx.loseContext(); | |
} | |
this.removeChild(this.canvas); | |
this.gl = null; | |
this.canvas = null; | |
this.buffers = {} | |
} | |
} | |
DigitalArt.register(); |
This file contains hidden or 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
body { | |
margin: 0; | |
height: 500vh; | |
overflow-x: hidden; | |
} | |
digital-art { | |
position: fixed; | |
top: 0; left: 0; | |
display: block; | |
width: 100vw; | |
height: 100vh; | |
pointer-events: none; | |
} | |
digital-art canvas { | |
display: block; | |
width: 100%; | |
height: 100%; | |
} | |
/* Hide scrollbars in windows */ | |
::-webkit-scrollbar { | |
display: none; | |
} | |
body { | |
-ms-overflow-style: none; /* IE and Edge */ | |
scrollbar-width: none; /* Firefox (not sure if that works) */ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment