Spaces:
Running
Running
File size: 5,152 Bytes
a97ef0b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | // ♈ 白羊座 —— WebGL2 背景:域扭曲分形噪声星云 + 上升的火焰流 + 程序化星点
// 不支持 WebGL2 时返回 null,由调用方降级到 Canvas 渐变。
const VERT = `#version 300 es
in vec2 p; out vec2 uv;
void main(){ uv = p * .5 + .5; gl_Position = vec4(p, 0., 1.); }`;
const FRAG = `#version 300 es
precision highp float;
in vec2 uv; out vec4 o;
uniform vec2 res; uniform float t;
uniform vec3 a0, a1, a2, b0, b1, b2; // 两套调色板(深/中/亮)
uniform float mixk, heat, stars, bright;
float h21(vec2 p){ p = fract(p * vec2(123.34, 456.21)); p += dot(p, p + 45.32); return fract(p.x * p.y); }
float vnoise(vec2 p){
vec2 i = floor(p), f = fract(p); vec2 u = f * f * (3. - 2. * f);
return mix(mix(h21(i), h21(i + vec2(1,0)), u.x), mix(h21(i + vec2(0,1)), h21(i + vec2(1,1)), u.x), u.y);
}
float fbm(vec2 p){ float v = 0., a = .5; mat2 m = mat2(1.6, 1.2, -1.2, 1.6); for (int k = 0; k < 5; k++){ v += a * vnoise(p); p = m * p; a *= .5; } return v; }
void main(){
vec2 q = (uv - .5) * vec2(res.x / res.y, 1.);
float asp = res.x / res.y;
// 域扭曲星云
vec2 w = q * 1.6;
vec2 d1 = vec2(fbm(w + vec2(0., t * .02)), fbm(w + vec2(5.2, 1.3) - t * .015));
float n = fbm(w + 2.2 * d1 + vec2(t * .01, 0.));
// 向上流动的火焰丝
vec2 fq = vec2(q.x * 2.2, q.y * 1.4 - t * .12);
float flame = fbm(fq + fbm(fq * 1.7 + t * .05));
flame = smoothstep(.45, 1., flame) * smoothstep(.75, -.6, q.y) * heat;
vec3 c0 = mix(a0, b0, mixk), c1 = mix(a1, b1, mixk), c2 = mix(a2, b2, mixk);
vec3 col = mix(c0, c1, smoothstep(.25, .75, n));
col = mix(col, c2, smoothstep(.62, .95, n) * .85);
col += c2 * flame * 1.1;
// 中心体积光
float glow = exp(-dot(q * vec2(.8, 1.1), q * vec2(.8, 1.1)) * 2.2);
col += c1 * glow * .35 * bright;
// 程序化星点(三层)
float s = 0.;
for (int L = 0; L < 3; L++){
float sc = 90. + float(L) * 70.;
vec2 g = (uv * vec2(asp, 1.) + float(L) * 7.13 + vec2(t * .002 * float(L + 1), 0.)) * sc;
vec2 id = floor(g), f = fract(g) - .5;
float r = h21(id);
if (r > .965){
vec2 o2 = vec2(h21(id + 3.1), h21(id + 7.7)) - .5;
float dd = length(f - o2 * .6);
float tw = .6 + .4 * sin(t * (1.5 + r * 4.) + r * 50.);
s += smoothstep(.09, 0., dd) * tw * (1.3 - float(L) * .3);
}
}
col += vec3(1., .95, .88) * s * stars;
// 暗角 + 胶片颗粒
float vig = smoothstep(1.25, .25, length(q * vec2(.9, 1.15)));
col *= mix(.35, 1., vig);
col += (h21(uv * res + fract(t)) - .5) * .025;
col = 1. - exp(-col * 1.25); // 柔和色调映射
o = vec4(col, 1.);
}`;
// 场景色调(深 / 中 / 亮)
export const TONES = {
ember: [[.06, .01, .02], [.55, .10, .05], [1.0, .62, .22]],
dawn: [[.05, .03, .10], [.48, .16, .26], [1.0, .74, .42]],
night: [[.01, .02, .07], [.12, .12, .34], [.55, .62, 1.0]],
sea: [[.00, .03, .08], [.04, .24, .40], [.55, .90, 1.0]],
grove: [[.01, .04, .03], [.06, .26, .16], [.95, .80, .38]],
royal: [[.05, .01, .07], [.38, .06, .22], [1.0, .78, .38]],
};
export function createSky() {
const cv = document.createElement('canvas');
const gl = cv.getContext('webgl2', { preserveDrawingBuffer: true, antialias: false, premultipliedAlpha: false });
if (!gl) return null;
const sh = (type, src) => { const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s)); return s; };
const prog = gl.createProgram();
gl.attachShader(prog, sh(gl.VERTEX_SHADER, VERT)); gl.attachShader(prog, sh(gl.FRAGMENT_SHADER, FRAG)); gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(prog));
gl.useProgram(prog);
const buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
const loc = gl.getAttribLocation(prog, 'p'); gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
const U = {}; for (const n of ['res', 't', 'a0', 'a1', 'a2', 'b0', 'b1', 'b2', 'mixk', 'heat', 'stars', 'bright']) U[n] = gl.getUniformLocation(prog, n);
return {
canvas: cv,
// scale:背景以较低分辨率渲染再放大(星云本身很柔和)
render(t, W, H, from, to, k, opt = {}) {
const sc = opt.scale ?? .5;
const w = Math.max(64, Math.round(W * sc)), h = Math.max(64, Math.round(H * sc));
if (cv.width !== w || cv.height !== h) { cv.width = w; cv.height = h; }
gl.viewport(0, 0, w, h);
gl.uniform2f(U.res, w, h); gl.uniform1f(U.t, t);
const A = TONES[from] || TONES.ember, B = TONES[to] || A;
gl.uniform3fv(U.a0, A[0]); gl.uniform3fv(U.a1, A[1]); gl.uniform3fv(U.a2, A[2]);
gl.uniform3fv(U.b0, B[0]); gl.uniform3fv(U.b1, B[1]); gl.uniform3fv(U.b2, B[2]);
gl.uniform1f(U.mixk, k); gl.uniform1f(U.heat, opt.heat ?? .6); gl.uniform1f(U.stars, opt.stars ?? 1); gl.uniform1f(U.bright, opt.bright ?? 1);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
return cv;
},
};
}
|