Add lightweight 2D WebGL cloud renderer as alternative to Vanta
This commit is contained in:
1 parent
a82d6cce3d
commit
d8e619e0c3
5 files changed
+406
-2
No files matched your search
Vendored
+178
@@ -0,0 +1,178 @@
|
||||
/* Custom 2D WebGL cloud renderer.
|
||||
* Much lighter than Vanta's volumetric raymarching while still
|
||||
* GPU-accelerated. Renders a fullscreen quad and uses a custom
|
||||
* fragment shader to produce animated cloud-like patterns.
|
||||
*
|
||||
* Drop-in replacement for Vanta when CONFIG.renderer === 'clouds2d'.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
function makeShader(gl, type, src) {
|
||||
var s = gl.createShader(type);
|
||||
gl.shaderSource(s, src);
|
||||
gl.compileShader(s);
|
||||
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
|
||||
console.error('Shader compile error:', gl.getShaderInfoLog(s));
|
||||
gl.deleteShader(s);
|
||||
return null;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function makeProgram(gl, vsSrc, fsSrc) {
|
||||
var vs = makeShader(gl, gl.VERTEX_SHADER, vsSrc);
|
||||
var fs = makeShader(gl, gl.FRAGMENT_SHADER, fsSrc);
|
||||
if (!vs || !fs) return null;
|
||||
var p = gl.createProgram();
|
||||
gl.attachShader(p, vs);
|
||||
gl.attachShader(p, fs);
|
||||
gl.linkProgram(p);
|
||||
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
|
||||
console.error('Program link error:', gl.getProgramInfoLog(p));
|
||||
gl.deleteProgram(p);
|
||||
return null;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
// Vertex shader: fullscreen triangle covering NDC -1..+1
|
||||
var VERT_SRC = [
|
||||
'attribute vec2 aPos;',
|
||||
'void main() { gl_Position = vec4(aPos, 0.0, 1.0); }'
|
||||
].join('\n');
|
||||
|
||||
// Fragment shader: animated 2D cloud field colored by time-of-day
|
||||
var FRAG_SRC = [
|
||||
'precision highp float;',
|
||||
'uniform vec2 uResolution;',
|
||||
'uniform float uTime;',
|
||||
'uniform vec3 uSky;',
|
||||
'uniform vec3 uCloud;',
|
||||
'uniform vec3 uLight;',
|
||||
'uniform float uSpeed;',
|
||||
// Simple 2D hash + value-noise
|
||||
'float hash(vec2 p) {',
|
||||
'p = fract(p * vec2(123.34, 456.21));',
|
||||
'p += dot(p, p + 45.32);',
|
||||
'return fract(p.x * p.y);',
|
||||
'}',
|
||||
'float noise(vec2 p) {',
|
||||
'vec2 i = floor(p);',
|
||||
'vec2 f = fract(p);',
|
||||
'float a = hash(i);',
|
||||
'float b = hash(i + vec2(1.0, 0.0));',
|
||||
'float c = hash(i + vec2(0.0, 1.0));',
|
||||
'float d = hash(i + vec2(1.0, 1.0));',
|
||||
'vec2 u = f * f * (3.0 - 2.0 * f);',
|
||||
'return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;',
|
||||
'}',
|
||||
'float fbm(vec2 p) {',
|
||||
'float v = 0.0;',
|
||||
'float a = 0.5;',
|
||||
'for (int i = 0; i < 5; i++) {',
|
||||
'v += a * noise(p);',
|
||||
'p *= 2.02;',
|
||||
'a *= 0.5;',
|
||||
'}',
|
||||
'return v;',
|
||||
'}',
|
||||
'void main() {',
|
||||
'vec2 uv = gl_FragCoord.xy / uResolution.xy;',
|
||||
'// Cloud field with two scrolling layers (parallax)',
|
||||
'vec2 p1 = uv * vec2(3.0, 1.7) + vec2(uTime * 0.012, uTime * 0.004);',
|
||||
'vec2 p2 = uv * vec2(5.0, 2.6) + vec2(uTime * 0.020, -uTime * 0.006);',
|
||||
'float n = fbm(p1 * uSpeed) * 0.6 + fbm(p2 * uSpeed) * 0.4;',
|
||||
'// Cloud mask: dense bottom, sparse top',
|
||||
'float cloud = smoothstep(0.35, 0.75, n);',
|
||||
'// Slight horizon darkening for sky distance',
|
||||
'float horizon = smoothstep(0.0, 0.45, uv.y);',
|
||||
'// Sky gradient (using y) in vertex direction',
|
||||
'float sky_t = clamp(uv.y, 0.0, 1.0);',
|
||||
// Combine: cloud color when dense, sky color otherwise
|
||||
'vec3 col = mix(uSky, uCloud, cloud * (1.0 - horizon * 0.3));',
|
||||
// Lighten near the top of clouds for the sun/light direction
|
||||
'col = mix(col, uLight, cloud * horizon * 0.5);',
|
||||
'gl_FragColor = vec4(col, 1.0);',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
function init(canvas) {
|
||||
var gl = canvas.getContext('webgl', { antialias: false, alpha: false });
|
||||
if (!gl) {
|
||||
console.error('clouds2d: WebGL not available');
|
||||
return null;
|
||||
}
|
||||
var prog = makeProgram(gl, VERT_SRC, FRAG_SRC);
|
||||
if (!prog) return null;
|
||||
|
||||
// Fullscreen triangle
|
||||
var buf = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
||||
gl.bufferData(gl.ARRAY_BUFFER,
|
||||
new Float32Array([ -1,-1, 3,-1, -1,3 ]),
|
||||
gl.STATIC_DRAW);
|
||||
|
||||
var aPos = gl.getAttribLocation(prog, 'aPos');
|
||||
var uRes = gl.getUniformLocation(prog, 'uResolution');
|
||||
var uTime = gl.getUniformLocation(prog, 'uTime');
|
||||
var uSky = gl.getUniformLocation(prog, 'uSky');
|
||||
var uCloud = gl.getUniformLocation(prog, 'uCloud');
|
||||
var uLight = gl.getUniformLocation(prog, 'uLight');
|
||||
var uSpeed = gl.getUniformLocation(prog, 'uSpeed');
|
||||
|
||||
return {
|
||||
gl: gl, prog: prog, buf: buf, aPos: aPos,
|
||||
uRes: uRes, uTime: uTime, uSky: uSky, uCloud: uCloud,
|
||||
uLight: uLight, uSpeed: uSpeed
|
||||
};
|
||||
}
|
||||
|
||||
function hex(r, g, b) { return [r/255, g/255, b/255]; }
|
||||
function mixRgb(a, b, t) { return [a[0]+(b[0]-a[0])*t, a[1]+(b[1]-a[1])*t, a[2]+(b[2]-a[2])*t]; }
|
||||
|
||||
window.clouds2dRender = function(canvas) {
|
||||
if (!canvas._clouds2d) {
|
||||
canvas._clouds2d = init(canvas);
|
||||
if (!canvas._clouds2d) return;
|
||||
}
|
||||
var s = canvas._clouds2d;
|
||||
var gl = s.gl;
|
||||
var w = canvas.width, h = canvas.height;
|
||||
if (gl.canvas.width !== w || gl.canvas.height !== h) {
|
||||
gl.viewport(0, 0, w, h);
|
||||
}
|
||||
|
||||
gl.clearColor(0, 0, 0, 1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
gl.useProgram(s.prog);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, s.buf);
|
||||
gl.enableVertexAttribArray(s.aPos);
|
||||
gl.vertexAttribPointer(s.aPos, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
gl.uniform2f(s.uRes, w, h);
|
||||
gl.uniform1f(s.uTime, performance.now() / 1000);
|
||||
|
||||
// Pick theme colors similar to the Vanta-based setup
|
||||
var cfg = window.CONFIG || {};
|
||||
var t = (cfg.weatherThemes && cfg.currentWeatherTheme && cfg.weatherThemes[cfg.currentWeatherTheme]) ||
|
||||
cfg.weatherThemes && cfg.weatherThemes[cfg.theme] ||
|
||||
null;
|
||||
var sky = [0.39, 0.54, 0.78];
|
||||
var cloud = [0.43, 0.43, 0.43];
|
||||
var light = [0.89, 0.87, 0.82];
|
||||
if (t) {
|
||||
sky = hex((t.skyColor >> 16) & 255, (t.skyColor >> 8) & 255, t.skyColor & 255);
|
||||
cloud = hex((t.cloudColor >> 16) & 255, (t.cloudColor >> 8) & 255, t.cloudColor & 255);
|
||||
light = hex((t.lightColor >> 16) & 255, (t.lightColor >> 8) & 255, t.lightColor & 255);
|
||||
}
|
||||
gl.uniform3fv(s.uSky, sky);
|
||||
gl.uniform3fv(s.uCloud, cloud);
|
||||
gl.uniform3fv(s.uLight, light);
|
||||
gl.uniform1f(s.uSpeed, 1.0);
|
||||
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
||||
};
|
||||
|
||||
})();
|
||||
Reference in new issue
Block a user