Skip to content

Instantly share code, notes, and snippets.

@trvswgnr
Created October 23, 2024 02:08
Show Gist options
  • Save trvswgnr/92938582aac80816522cab9ac2a5e677 to your computer and use it in GitHub Desktop.
Save trvswgnr/92938582aac80816522cab9ac2a5e677 to your computer and use it in GitHub Desktop.
Snowfall WebGL Shader
// img#snowflake(src="https://cdn.jsdelivr.net/gh/trvswgnr/cdn/snowflake.png")
#snow
const holder = document.querySelector('#snow');
const count = 3000;
let wind = {
current: 0,
force: 0.01,
target: 0.1,
min: 0.1,
max: 0.1,
easing: 0.005
};
// Create a function to load the image
function loadImage(url) {
return new Promise((resolve, reject) => {
if (url.startsWith("data")) {
resolve(url);
}
const img = new Image();
img.crossOrigin = "anonymous"; // Enable CORS if needed
img.onload = () => {
// Create a canvas to convert the image to a data URL
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// Get the data URL
const dataUrl = canvas.toDataURL('image/png');
resolve(dataUrl);
};
img.onerror = () => {
reject(new Error(`Failed to load image: ${url}`));
};
img.src = url;
});
}
// Initialize the snow effect
async function initSnow(imageUrl) {
try {
// Load and convert the image
const textureDataUrl = await loadImage(imageUrl);
// Create the shader program
const snow = new ShaderProgram(holder, {
depthTest: false,
texture: textureDataUrl,
uniforms: {
worldSize: { type: 'vec3', value: [0, 0, 0] },
gravity: { type: 'float', value: 100 },
wind: { type: 'float', value: 0 },
},
buffers: {
size: { size: 1, data: [] },
rotation: { size: 3, data: [] },
speed: { size: 3, data: [] },
},
vertex: `
precision highp float;
attribute vec4 a_position;
attribute vec4 a_color;
attribute vec3 a_rotation;
attribute vec3 a_speed;
attribute float a_size;
uniform float u_time;
uniform vec2 u_mousemove;
uniform vec2 u_resolution;
uniform mat4 u_projection;
uniform vec3 u_worldSize;
uniform float u_gravity;
uniform float u_wind;
varying vec4 v_color;
varying float v_rotation;
void main() {
v_color = a_color;
v_rotation = a_rotation.x + u_time * a_rotation.y;
vec3 pos = a_position.xyz;
pos.x = mod(pos.x + u_time + u_wind * a_speed.x, u_worldSize.x * 2.0) - u_worldSize.x;
pos.y = mod(pos.y - u_time * a_speed.y * u_gravity, u_worldSize.y * 2.0) - u_worldSize.y;
pos.x += sin(u_time * a_speed.z) * a_rotation.z;
pos.z += cos(u_time * a_speed.z) * a_rotation.z;
gl_Position = u_projection * vec4(pos.xyz, a_position.w);
gl_PointSize = (a_size / gl_Position.w) * 100.0;
}`,
fragment: `
precision highp float;
uniform sampler2D u_texture;
varying vec4 v_color;
varying float v_rotation;
void main() {
vec2 rotated = vec2(
cos(v_rotation) * (gl_PointCoord.x - 0.5) + sin(v_rotation) * (gl_PointCoord.y - 0.5) + 0.5,
cos(v_rotation) * (gl_PointCoord.y - 0.5) - sin(v_rotation) * (gl_PointCoord.x - 0.5) + 0.5
);
vec4 snowflake = texture2D(u_texture, rotated);
gl_FragColor = vec4(snowflake.rgb, snowflake.a * v_color.a);
}`,
onResize(w, h, dpi) {
const position = [], color = [], size = [], rotation = [], speed = [];
const height = 110;
const width = w / h * height;
const depth = 80;
Array.from({ length: w / h * count }, snowflake => {
position.push(
-width + Math.random() * width * 2,
-height + Math.random() * height * 2,
Math.random() * depth * 2
);
speed.push(
1 + Math.random(),
1 + Math.random(),
Math.random() * 10
);
rotation.push(
Math.random() * 2 * Math.PI,
Math.random() * 20,
Math.random() * 10
);
color.push(
1,
1,
1,
0.1 + Math.random() * 0.2
);
size.push(
5 * Math.random() * 5 * (h * dpi / 1000)
);
});
this.uniforms.worldSize = [width, height, depth];
this.buffers.position = position;
this.buffers.color = color;
this.buffers.rotation = rotation;
this.buffers.size = size;
this.buffers.speed = speed;
},
onUpdate(delta) {
wind.force += (wind.target - wind.force) * wind.easing;
wind.current += wind.force * (delta * 0.2);
this.uniforms.wind = wind.current;
if (Math.random() > 0.995) {
wind.target = (wind.min + Math.random() * (wind.max - wind.min)) * (Math.random() > 0.5 ? -1 : 1);
}
},
});
} catch (error) {
console.error('Failed to initialize snow effect:', error);
}
}
// Usage example:
// initSnow('https://cdn.jsdelivr.net/gh/trvswgnr/cdn/snowflake.png');
initSnow("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAQAAABuvaSwAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAE7SURBVCjPnZMvcoNAGMWhdJgGhkYkw1CBSyaCGURcTDSGih4gFwiOE0RGIivTK2C5ArKVkXE9w+tvKS2223nm8b3f7n77B0fOr1zdIU/3o7zh252ICXQHzNdMgUIU4PxhiPszYJrTgIHmWijWE4pxcypmwDj/hD4o0lKp1sqUowyXUolIRvwbNuijEq201V6FnlGB21JJSAw+wKZXn/GJNtqp1EGValThSiobkgiC3p2hhYDlVgQvOuqkRq+owR2p7EiWELRiYJ+NpCxZEp71plYdanFnKiVJCuEb2OOIFmxmz7InXcB6faAed6FyIFlDzOQ5QxMxey/osmG+Xlfd0BXXUqlIMggaMXDIqebsv6bTTu+An+iG66jUJDlE+A/Yog2rDVodndWlWF231UOyfKJWj9/yt/rjD/sF+2xJiwvIrtcAAAASdEVYdEVYSUY6T3JpZW50YXRpb24AMYRY7O8AAAAASUVORK5CYII=")
<script src="https://codepen.io/bsehovac/pen/mddZWPw.js"></script>
#snow
display: block
position: fixed
left: 0
top: 0
right: 0
bottom: 0
background-color: #000000
background-image: linear-gradient(to bottom, #000000, #050505)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment