<canvas id="canvas"></canvas>
body {
position: relative;
margin: 0;
height: 100vh;
}
#canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
import {
Renderer, Camera, Transform, Mesh,
Polyline, Color, Orbit, Vec2, Vec3, Post
} from "https://cdn.skypack.dev/ogl@0.0.73";
const canvas = document.getElementById('canvas');
const vertex = /* glsl */ `
precision highp float;
attribute float aOffset;
attribute vec3 position;
attribute vec3 next;
attribute vec3 prev;
attribute vec2 uv;
attribute float side;
uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat3 normalMatrix;
uniform vec2 uResolution;
uniform float uDPR;
uniform float uThickness;
uniform float uThicknessMax;
uniform float uMiter;
uniform float uTime;
uniform float uDuration;
uniform float uProgress;
uniform float uDir;
varying vec2 vUv;
varying vec2 vNormal;
varying float vDepth;
// ================================================
// Simplex 2D noise
// https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }
float snoise(vec2 v) {
const vec4 C = vec4(0.211324865405187, 0.366025403784439,
-0.577350269189626, 0.024390243902439);
vec2 i = floor(v + dot(v, C.yy) );
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1;
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod(i, 289.0);
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
+ i.x + vec3(0.0, i1.x, 1.0 ));
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy),
dot(x12.zw,x12.zw)), 0.0);
m = m*m ;
m = m*m ;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
float easeInQuad(float t) {
return t * t;
}
float easeOutQuad(float t) {
return t * (2.0 - t);
}
float easeInCubic(float t) {
return t * t * t;
}
float easeOutCubic(float t) {
float x = 1.0 - t;
return 1.0 - x * x * x;
}
void main() {
float progress = fract((uTime + aOffset) / uDuration);
float ease;
if (progress < 0.5) {
ease = easeInQuad(progress * 2.0);
}
else {
ease = 1.0 - easeOutQuad((progress - 0.5) * 2.0);
}
float thickness = mix(uThicknessMax, uThickness, aOffset);
float scale = mix(mix(0.5, 1.0, aOffset), 1.0, ease);
float instOffset = mix(0.25, 1.0, aOffset);
vec3 cPosition = position * instOffset * scale;
vec3 nPosition = next * instOffset * scale;
vec3 pPosition = prev * instOffset * scale;
float noiseAmp = 0.1;
float currNoise = snoise(cPosition.xy * 1.5 - uTime * 0.25) * noiseAmp;
float nextNoise = snoise(nPosition.xy * 1.5 - uTime * 0.25) * noiseAmp;
float prevNoise = snoise(pPosition.xy * 1.5 - uTime * 0.25) * noiseAmp;
mat4 mvp = projectionMatrix * modelViewMatrix;
vec4 currPos = mvp * vec4(cPosition * (1.0 + currNoise), 1);
vec4 nextPos = mvp * vec4(nPosition * (1.0 + nextNoise), 1);
vec4 prevPos = mvp * vec4(pPosition * (1.0 + prevNoise), 1);
vec2 aspect = vec2(uResolution.x / uResolution.y, 1);
vec2 currScreen = currPos.xy / currPos.w * aspect;
vec2 nextScreen = nextPos.xy / nextPos.w * aspect;
vec2 prevScreen = prevPos.xy / prevPos.w * aspect;
vec2 dirPrev = normalize(currScreen - prevScreen);
vec2 dirNext = normalize(nextScreen - currScreen);
vec2 dir2 = normalize(dirPrev + dirNext);
vec2 normal = vec2(-dir2.y, dir2.x);
normal /= mix(1.0, max(0.3, dot(normal, vec2(-dirPrev.y, dirPrev.x))), uMiter);
normal /= aspect;
float pixelWidthRatio = 1.0 / (uResolution.y / uDPR);
float pixelWidth = currPos.w * pixelWidthRatio;
normal *= pixelWidth * thickness;
currPos.xy -= normal * side;
vDepth = currNoise / noiseAmp * 0.5 + 0.5;
vUv = uv;
gl_Position = currPos;
}
`;
const fragment = /* glsl */ `
precision highp float;
uniform float uTime;
uniform vec3 uColor1;
uniform vec3 uColor2;
varying vec2 vUv;
varying float vDepth;
void main() {
float shading = vDepth * vDepth * 0.9 + 0.1;
gl_FragColor.rgb = mix(uColor1, uColor2, vDepth) * shading;
gl_FragColor.a = 1.0;
}
`;
const renderer = new Renderer({ canvas, antialias: true });
const gl = renderer.gl;
gl.clearColor(0.0, 0.0, 0.0, 1);
const camera = new Camera(gl, { fov: 15 });
camera.position.set(0, 0, 10);
const controls = new Orbit(camera, {
target: new Vec3(0, 0, 0),
});
function resize() {
const { innerWidth: width, innerHeight: height } = window;
renderer.setSize(width, height);
camera.perspective({ aspect: width / height });
}
window.addEventListener('resize', resize, false);
resize();
const scene = new Transform();
const segsPerLine = 512;
const points = [];
for (let i = 0; i <= segsPerLine; i++) {
const a = 2 * Math.PI * i / segsPerLine;
const x = Math.cos(a);
const y = Math.sin(a);
points.push(new Vec3(x, y, 0));
}
const instancesTotal = 16;
const offsets = new Float32Array(instancesTotal);
for (let i = 0; i < instancesTotal; i++) {
const offs = i / (instancesTotal - 1);
offsets[i] = offs;
}
const polyline = new Polyline(gl, {
points, vertex, fragment,
uniforms: {
uColor1: { value: new Color('#e91e63') },
uColor2: { value: new Color('#673ab7') },
uTime: { value: 0 },
uDuration: { value: 4 },
uProgress: { value: 0 },
uThickness: { value: 1 },
uThicknessMax: { value: 4 },
},
attributes: {
aOffset: { instanced: 1, size: 1, data: offsets }
}
});
const mesh = new Mesh(gl, polyline);
mesh.setParent(scene);
let begin = 0;
requestAnimationFrame(update);
function update(now) {
begin = begin || now;
mesh.program.uniforms.uTime.value = (now - begin) * 0.001;
controls.update();
renderer.render({ scene, camera });
requestAnimationFrame(update);
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.