Skip to content

Instantly share code, notes, and snippets.

@jftochka
Created November 29, 2022 09:07
Show Gist options
  • Save jftochka/2fd5e79a7315448ba107b27c8c555c7d to your computer and use it in GitHub Desktop.
Save jftochka/2fd5e79a7315448ba107b27c8c555c7d to your computer and use it in GitHub Desktop.
WebGL - Morphing Ball
<script type="x-shader/x-fragment" id="background-fragment">
float hue2rgb(float f1, float f2, float hue) {
if (hue < 0.0)
hue += 1.0;
else if (hue > 1.0)
hue -= 1.0;
float res;
if ((6.0 * hue) < 1.0)
res = f1 + (f2 - f1) * 6.0 * hue;
else if ((2.0 * hue) < 1.0)
res = f2;
else if ((3.0 * hue) < 2.0)
res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0;
else
res = f1;
return res;
}
vec3 hsl2rgb(vec3 hsl) {
vec3 rgb;
if (hsl.y == 0.0) {
rgb = vec3(hsl.z); // Luminance
} else {
float f2;
if (hsl.z < 0.5)
f2 = hsl.z * (1.0 + hsl.y);
else
f2 = hsl.z + hsl.y - hsl.y * hsl.z;
float f1 = 2.0 * hsl.z - f2;
rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0));
rgb.g = hue2rgb(f1, f2, hsl.x);
rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0));
}
return rgb;
}
vec3 hsl2rgb(float h, float s, float l) {
return hsl2rgb(vec3(h, s, l));
}
vec3 random3(vec3 c) {
float j = 4096.0*sin(dot(c,vec3(17.0, 59.4, 15.0)));
vec3 r;
r.z = fract(512.0*j);
j *= .125;
r.x = fract(512.0*j);
j *= .125;
r.y = fract(512.0*j);
return r-0.5;
}
const float F3 = 0.3333333;
const float G3 = 0.1666667;
float simplex3d(vec3 p) {
vec3 s = floor(p + dot(p, vec3(F3)));
vec3 x = p - s + dot(s, vec3(G3));
vec3 e = step(vec3(0.0), x - x.yzx);
vec3 i1 = e*(1.0 - e.zxy);
vec3 i2 = 1.0 - e.zxy*(1.0 - e);
vec3 x1 = x - i1 + G3;
vec3 x2 = x - i2 + 2.0*G3;
vec3 x3 = x - 1.0 + 3.0*G3;
vec4 w, d;
w.x = dot(x, x);
w.y = dot(x1, x1);
w.z = dot(x2, x2);
w.w = dot(x3, x3);
w = max(0.6 - w, 0.0);
d.x = dot(random3(s), x);
d.y = dot(random3(s + i1), x1);
d.z = dot(random3(s + i2), x2);
d.w = dot(random3(s + 1.0), x3);
w *= w;
w *= w;
d *= w;
return dot(d, vec4(52.0));
}
float hash(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); }
varying vec2 vUv;
uniform float u_progress;
uniform float u_time;
void main() {
float n = simplex3d(vec3(vUv.xy, u_time * 1.0));
vec3 color = hsl2rgb(
0.0 + n * 0.1,
0.0,
0.03
);
float val = hash(vUv + u_time);
gl_FragColor = vec4(color + vec3(val / 20.), 1.0);
}
</script>
<script type="x-shader/x-vertex" id="background-vertex">
varying vec2 vUv;
uniform float u_time;
void main() {
vec3 p = position;
vec4 mvPosition = modelViewMatrix * vec4(p, 1.0);
gl_PointSize = 10.0 * (1.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
vUv = uv;
}
</script>
<script type="x-shader/x-fragment" id="particle-fragment">
uniform float u_progress;
void main() {
gl_FragColor = vec4(0.4, 0.4, 0.4, u_progress);
}
</script>
<script type="x-shader/x-vertex" id="particle-vertex">
uniform float u_time;
void main() {
vec3 p = position;
p.y += 0.25*(sin(p.y * 5.0 + u_time) * 0.5 + 0.5);
p.z += 0.05*(sin(p.y * 10.0 + u_time) * 0.5 + 0.5);
vec4 mvPosition = modelViewMatrix * vec4(p, 1.0);
gl_PointSize = 10.0 * (1.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragment">
varying vec2 vUv;
varying vec3 v_color;
varying vec3 v_normal;
void main() {
vec3 light = vec3(0.0);
vec3 skyColor = vec3(1.000, 1.000, 0.547);
vec3 groundColor = vec3(0.562, 0.275, 0.111);
vec3 lightDirection = normalize(vec3(0.0, -1.0, -1.0));
light += dot(lightDirection, v_normal);
light = mix(skyColor, groundColor, dot(lightDirection, v_normal));
gl_FragColor = vec4(light * v_color, 1.0);
}
</script>
<script type="x-shader/x-vertex" id="vertex">
varying vec2 vUv;
varying vec3 v_color;
varying vec3 v_normal;
uniform float u_time;
uniform float u_progress;
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
vec4 permute(vec4 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); }
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
float snoise(vec3 v) {
const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
// First corner
vec3 i = floor(v + dot(v, C.yyy));
vec3 x0 = v - i + dot(i, C.xxx);
// Other corners
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min(g.xyz, l.zxy);
vec3 i2 = max(g.xyz, l.zxy);
// x0 = x0 - 0. + 0.0 * C
vec3 x1 = x0 - i1 + 1.0 * C.xxx;
vec3 x2 = x0 - i2 + 2.0 * C.xxx;
vec3 x3 = x0 - 1. + 3.0 * C.xxx;
// Permutations
i = mod(i, 289.0);
vec4 p = permute(permute(permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0));
// Gradients
// ( N*N points uniformly over a square, mapped onto an octahedron.)
float n_ = 1.0 / 7.0; // N=7
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,N*N)
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_); // mod(j,N)
vec4 x = x_ * ns.x + ns.yyyy;
vec4 y = y_ * ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4(x.xy, y.xy);
vec4 b1 = vec4(x.zw, y.zw);
vec4 s0 = floor(b0) * 2.0 + 1.0;
vec4 s1 = floor(b1) * 2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
vec3 p0 = vec3(a0.xy, h.x);
vec3 p1 = vec3(a0.zw, h.y);
vec3 p2 = vec3(a1.xy, h.z);
vec3 p3 = vec3(a1.zw, h.w);
//Normalise gradients
vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
// Mix final noise value
vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
m = m * m;
return 42.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1),
dot(p2, x2), dot(p3, x3)));
}
void main() {
vUv = uv;
float noise = snoise(position * u_progress + u_time / 10.0);
vec3 newPos = position * (noise + 0.7);
v_color = hsv2rgb(vec3(noise * 0.1 + 0.03, .7, 0.7));
v_normal = normal;
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPos, 1.0);
}
</script>
const fragment = document.querySelector("#fragment").textContent;
const vertex = document.querySelector("#vertex").textContent;
const fragmentParticle = document.querySelector("#particle-fragment")
.textContent;
const vertexParticle = document.querySelector("#particle-vertex").textContent;
const backgroundVertex = document.querySelector("#background-vertex")
.textContent;
const backgroundFragment = document.querySelector("#background-fragment")
.textContent;
class THREEScene {
constructor(container = document.body) {
this.container = container;
this.init();
}
init() {
this.setup();
this.camera();
this.addToScene();
this.createParticles();
this.createBackground();
this.eventListeners();
this.render();
this.animate();
}
setup() {
this.scene = new THREE.Scene();
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setSize(this.viewport.width, this.viewport.height);
this.renderer.setPixelRatio = window.devicePixelRatio;
this.container.appendChild(this.renderer.domElement);
this.material = new THREE.ShaderMaterial({
vertexShader: vertex,
fragmentShader: fragment,
wireframe: false,
uniforms: {
u_time: { value: 0 },
u_progress: { value: 0 }
}
});
this.pointsMaterial = new THREE.ShaderMaterial({
vertexShader: vertexParticle,
fragmentShader: fragmentParticle,
wireframe: false,
side: THREE.DoubleSide,
transparent: true,
uniforms: {
u_time: { value: 0 },
u_progress: { value: 0 }
}
});
this.clock = new THREE.Clock();
}
camera() {
const fov = 40;
const near = 0.1;
const far = 10000;
const aspectRatio = this.viewport.aspectRatio;
this.camera = new THREE.PerspectiveCamera(fov, aspectRatio, near, far);
//this.camera = new THREE.OrthographicCamera(
// this.viewport.width / -2,
//this.viewport.width / 2,
//this.viewport.height / 2,
//this.viewport.height / -2,
//1,
//1000
//);
this.camera.position.set(0, 0, 10);
//this.controls = new THREE.OrbitControls(
// this.camera,
//this.renderer.domElement
//);
}
addToScene() {
this.geometry = new THREE.SphereGeometry(1, 162, 162);
const sphere = new THREE.Mesh(this.geometry, this.material);
this.scene.add(sphere);
}
createParticles() {
const N = 30000;
const position = new Float32Array(N * 3);
this.particleGeometry = new THREE.BufferGeometry();
let inc = Math.PI * (3 - Math.sqrt(5));
let offset = 2 / N;
let radius = 2;
for (let i = 0; i < N; i++) {
let y = i * offset - 1 + offset / 2;
let r = Math.sqrt(1 - y * y);
let phi = i * inc;
position[3 * i] = radius * Math.cos(phi) * r;
position[3 * i + 1] = radius * y;
position[3 * i + 2] = radius * Math.sin(phi) * r;
}
this.particleGeometry.setAttribute(
"position",
new THREE.BufferAttribute(position, 3)
);
this.points = new THREE.Points(
this.particleGeometry,
this.pointsMaterial
);
this.scene.add(this.points);
}
createBackground() {
const geometry = new THREE.PlaneGeometry(100, 15, 16);
this.backgroundMaterial = new THREE.ShaderMaterial({
vertexShader: backgroundVertex,
fragmentShader: backgroundFragment,
wireframe: false,
uniforms: {
u_time: { value: 0 },
u_progress: { value: 0 }
}
});
const mesh = new THREE.Mesh(geometry, this.backgroundMaterial);
mesh.position.z = -2;
this.scene.add(mesh);
}
render() {
this.camera.lookAt(this.scene.position);
this.renderer.render(this.scene, this.camera);
this.material.uniforms.u_time.value = this.clock.getElapsedTime();
this.pointsMaterial.uniforms.u_time.value = this.clock.getElapsedTime();
this.backgroundMaterial.uniforms.u_time.value = this.clock.getElapsedTime();
this.points.rotation.y += 0.005;
requestAnimationFrame(() => {
this.render();
});
}
animate() {
gsap.timeline({
repeat: -1,
yoyo: true
})
.to(this.material.uniforms.u_progress, {
value: 5,
duration: 5,
ease: "power3.inOut"
})
.to(this.material.uniforms.u_progress, {
value: 1,
duration: 5,
ease: "power3.inOut"
});
gsap.to(this.pointsMaterial.uniforms.u_progress, {
value: 0.4,
duration: 5,
ease: "power3.inOut"
});
}
eventListeners() {
window.addEventListener("resize", this.onWindowResize.bind(this));
}
onWindowResize() {
this.material.uniforms.u_time.value = this.clock.getElapsedTime();
this.camera.aspect = this.viewport.aspectRatio;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.viewport.width, this.viewport.height);
}
get viewport() {
const width = this.container.clientWidth;
const height = this.container.clientHeight;
const aspectRatio = width / height;
return {
width,
height,
aspectRatio
};
}
}
const scene = new THREEScene();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
<script src="https://unpkg.co/gsap@3/dist/gsap.min.js"></script>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
height: 100vh;
width: 100vw;
}
canvas {
height: 100%;
width: 100%;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment