Redo moon and clouds as procedural shaders in the GL engine
The baked atlas sprites (blurred-circle clouds, gradient moon with flat maria blobs) read as cheap. Clouds are now built per-instance in the particle fragment shader from fbm density with a flattened base and lit tops; the moon renders in the sky shader with sphere shading, emboss relief, maria and a halo. Stars skip the moon disc since the sky pass draws first.
This commit is contained in:
@@ -5,10 +5,11 @@
|
|||||||
// falls back to the Canvas2D engine (weather-2d.js) otherwise.
|
// falls back to the Canvas2D engine (weather-2d.js) otherwise.
|
||||||
//
|
//
|
||||||
// Shape of the engine: one sprite atlas baked once on an offscreen 2D canvas
|
// Shape of the engine: one sprite atlas baked once on an offscreen 2D canvas
|
||||||
// (blossoms, leaves, flakes, clouds, moon, hail, light beams), one instanced
|
// (blossoms, leaves, flakes, hail, light beams), one instanced quad program
|
||||||
// quad program that draws every particle in a single call, and one fullscreen
|
// that draws every particle in a single call, and one fullscreen "sky" shader
|
||||||
// "sky" shader for the volumetric-looking stuff: fog, Saharan haze, aurora,
|
// for the volumetric-looking stuff: fog, Saharan haze, aurora, sun rays, the
|
||||||
// sun rays, storm gloom and lightning flash.
|
// moon, storm gloom and lightning flash. Clouds are fully procedural in the
|
||||||
|
// particle fragment shader (fbm density, no baked sprite).
|
||||||
(function () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@@ -32,10 +33,12 @@
|
|||||||
// Everything is drawn white-on-transparent where possible so a per-instance
|
// Everything is drawn white-on-transparent where possible so a per-instance
|
||||||
// tint can recolor it (clouds, rain, glows); blossoms and leaves keep baked
|
// tint can recolor it (clouds, rain, glows); blossoms and leaves keep baked
|
||||||
// colors because they mix two hues that a single tint can't reproduce.
|
// colors because they mix two hues that a single tint can't reproduce.
|
||||||
|
// Index 8 (CLOUD) has no atlas rect: the fragment shader spots it and
|
||||||
|
// synthesizes a cumulus procedurally instead of sampling the texture.
|
||||||
var SPR = {
|
var SPR = {
|
||||||
GLOW: 0, STREAK: 1, FLAKE: 2, BLOSSOM_P: 3, BLOSSOM_J: 4,
|
GLOW: 0, STREAK: 1, FLAKE: 2, BLOSSOM_P: 3, BLOSSOM_J: 4,
|
||||||
LEAF0: 5, LEAF1: 6, LEAF2: 7, CLOUD0: 8, CLOUD1: 9, CLOUD2: 10,
|
LEAF0: 5, LEAF1: 6, LEAF2: 7, CLOUD: 8,
|
||||||
MOON: 11, HAIL: 12, BEAM: 13
|
HAIL: 12, BEAM: 13
|
||||||
};
|
};
|
||||||
var ATLAS = 1024;
|
var ATLAS = 1024;
|
||||||
var uvRects = new Float32Array(16 * 4);
|
var uvRects = new Float32Array(16 * 4);
|
||||||
@@ -94,66 +97,6 @@
|
|||||||
cx2.restore();
|
cx2.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same silhouettes as the 2D engine so clouds keep their look.
|
|
||||||
var cloudShapes = [
|
|
||||||
[[0.50, 0.50, 0.26], [0.34, 0.56, 0.20], [0.66, 0.56, 0.20], [0.42, 0.40, 0.18],
|
|
||||||
[0.58, 0.38, 0.17], [0.22, 0.60, 0.15], [0.78, 0.60, 0.15], [0.50, 0.62, 0.22]],
|
|
||||||
[[0.46, 0.52, 0.24], [0.30, 0.58, 0.18], [0.62, 0.50, 0.22], [0.74, 0.58, 0.16],
|
|
||||||
[0.40, 0.40, 0.16], [0.56, 0.36, 0.15], [0.18, 0.62, 0.13], [0.84, 0.62, 0.12]],
|
|
||||||
[[0.52, 0.50, 0.28], [0.36, 0.56, 0.19], [0.68, 0.56, 0.18], [0.48, 0.36, 0.16],
|
|
||||||
[0.26, 0.60, 0.14], [0.74, 0.60, 0.15], [0.60, 0.62, 0.18], [0.40, 0.62, 0.18]]
|
|
||||||
];
|
|
||||||
|
|
||||||
function bakeCloud(cx2, x, y, shape) {
|
|
||||||
var w = 320, h = 192;
|
|
||||||
cx2.save();
|
|
||||||
cx2.filter = "blur(16px)";
|
|
||||||
cx2.fillStyle = "rgba(255,255,255,1)";
|
|
||||||
for (var i = 0; i < shape.length; i++) {
|
|
||||||
cx2.beginPath();
|
|
||||||
cx2.arc(x + shape[i][0] * w, y + shape[i][1] * h, shape[i][2] * w * 0.92, 0, Math.PI * 2);
|
|
||||||
cx2.fill();
|
|
||||||
}
|
|
||||||
cx2.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
function bakeMoon(cx2, x, y) {
|
|
||||||
var TAU = Math.PI * 2;
|
|
||||||
var mr = 53, cx = x + 128, cy = y + 128;
|
|
||||||
// Halo first, unclipped.
|
|
||||||
var halo = cx2.createRadialGradient(cx, cy, mr, cx, cy, mr * 2.4);
|
|
||||||
halo.addColorStop(0, "rgba(220,230,255,0.28)");
|
|
||||||
halo.addColorStop(1, "rgba(220,230,255,0)");
|
|
||||||
cx2.fillStyle = halo;
|
|
||||||
cx2.beginPath();
|
|
||||||
cx2.arc(cx, cy, mr * 2.4, 0, TAU);
|
|
||||||
cx2.fill();
|
|
||||||
cx2.save();
|
|
||||||
cx2.beginPath();
|
|
||||||
cx2.arc(cx, cy, mr, 0, TAU);
|
|
||||||
cx2.clip();
|
|
||||||
var lx = cx + mr * 0.45, ly = cy - mr * 0.45;
|
|
||||||
var body = cx2.createRadialGradient(lx, ly, mr * 0.1, cx, cy, mr * 1.35);
|
|
||||||
body.addColorStop(0, "rgba(249,251,255,0.97)");
|
|
||||||
body.addColorStop(0.55, "rgba(214,222,240,0.92)");
|
|
||||||
body.addColorStop(1, "rgba(140,151,180,0.85)");
|
|
||||||
cx2.fillStyle = body;
|
|
||||||
cx2.fillRect(cx - mr, cy - mr, mr * 2, mr * 2);
|
|
||||||
var limb = cx2.createRadialGradient(cx, cy, mr * 0.62, cx, cy, mr);
|
|
||||||
limb.addColorStop(0, "rgba(18,24,46,0)");
|
|
||||||
limb.addColorStop(1, "rgba(18,24,46,0.4)");
|
|
||||||
cx2.fillStyle = limb;
|
|
||||||
cx2.fillRect(cx - mr, cy - mr, mr * 2, mr * 2);
|
|
||||||
// A few faint maria so the disc isn't a plain gradient.
|
|
||||||
cx2.fillStyle = "rgba(120,132,165,0.30)";
|
|
||||||
[[-0.25, -0.1, 0.28], [0.18, 0.22, 0.20], [0.05, -0.32, 0.14], [-0.32, 0.3, 0.12]].forEach(function (m) {
|
|
||||||
cx2.beginPath();
|
|
||||||
cx2.arc(cx + m[0] * mr, cy + m[1] * mr, m[2] * mr, 0, TAU);
|
|
||||||
cx2.fill();
|
|
||||||
});
|
|
||||||
cx2.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
function bakeAtlas() {
|
function bakeAtlas() {
|
||||||
var c = document.createElement("canvas");
|
var c = document.createElement("canvas");
|
||||||
c.width = ATLAS; c.height = ATLAS;
|
c.width = ATLAS; c.height = ATLAS;
|
||||||
@@ -224,15 +167,8 @@
|
|||||||
bakeLeaf(x, 768, 0, 35, 50); setRect(SPR.LEAF1, 768, 0, 128, 128);
|
bakeLeaf(x, 768, 0, 35, 50); setRect(SPR.LEAF1, 768, 0, 128, 128);
|
||||||
bakeLeaf(x, 896, 0, 46, 44); setRect(SPR.LEAF2, 896, 0, 128, 128);
|
bakeLeaf(x, 896, 0, 46, 44); setRect(SPR.LEAF2, 896, 0, 128, 128);
|
||||||
|
|
||||||
// Row 1: clouds, drawn white and tinted per palette at draw time.
|
// Row 2: hailstone, beam. (Clouds and the moon are procedural now, so
|
||||||
bakeCloud(x, 0, 128, cloudShapes[0]); setRect(SPR.CLOUD0, 0, 128, 320, 192);
|
// row 1 sits empty and the shading below keeps its old coordinates.)
|
||||||
bakeCloud(x, 320, 128, cloudShapes[1]); setRect(SPR.CLOUD1, 320, 128, 320, 192);
|
|
||||||
bakeCloud(x, 640, 128, cloudShapes[2]); setRect(SPR.CLOUD2, 640, 128, 320, 192);
|
|
||||||
|
|
||||||
// Row 2: moon, hailstone, beam.
|
|
||||||
bakeMoon(x, 0, 320);
|
|
||||||
setRect(SPR.MOON, 0, 320, 256, 256);
|
|
||||||
|
|
||||||
var hg = x.createRadialGradient(256 + 38, 320 + 38, 4, 256 + 48, 320 + 48, 36);
|
var hg = x.createRadialGradient(256 + 38, 320 + 38, 4, 256 + 48, 320 + 48, 36);
|
||||||
hg.addColorStop(0, "rgba(255,255,255,1)");
|
hg.addColorStop(0, "rgba(255,255,255,1)");
|
||||||
hg.addColorStop(0.7, "rgba(216,226,240,0.95)");
|
hg.addColorStop(0.7, "rgba(216,226,240,0.95)");
|
||||||
@@ -274,13 +210,16 @@
|
|||||||
"#version 300 es",
|
"#version 300 es",
|
||||||
"layout(location=0) in vec2 a_corner;",
|
"layout(location=0) in vec2 a_corner;",
|
||||||
"layout(location=1) in vec4 a_pos;", // x, y, sizeX, sizeY (CSS px)
|
"layout(location=1) in vec4 a_pos;", // x, y, sizeX, sizeY (CSS px)
|
||||||
"layout(location=2) in vec4 a_misc;", // rot, alpha, spriteIdx, unused
|
"layout(location=2) in vec4 a_misc;", // rot, alpha, spriteIdx, seed
|
||||||
"layout(location=3) in vec3 a_tint;",
|
"layout(location=3) in vec3 a_tint;",
|
||||||
"uniform vec2 u_res;",
|
"uniform vec2 u_res;",
|
||||||
"uniform vec4 u_uv[16];",
|
"uniform vec4 u_uv[16];",
|
||||||
"out vec2 v_uv;",
|
"out vec2 v_uv;",
|
||||||
|
"out vec2 v_local;",
|
||||||
"out float v_alpha;",
|
"out float v_alpha;",
|
||||||
"out vec3 v_tint;",
|
"out vec3 v_tint;",
|
||||||
|
"out float v_kind;",
|
||||||
|
"out float v_seed;",
|
||||||
"void main(){",
|
"void main(){",
|
||||||
" float c = cos(a_misc.x), s = sin(a_misc.x);",
|
" float c = cos(a_misc.x), s = sin(a_misc.x);",
|
||||||
" vec2 k = a_corner * a_pos.zw;",
|
" vec2 k = a_corner * a_pos.zw;",
|
||||||
@@ -288,21 +227,56 @@
|
|||||||
" vec2 clip = (p / u_res) * 2.0 - 1.0;",
|
" vec2 clip = (p / u_res) * 2.0 - 1.0;",
|
||||||
" gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);",
|
" gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);",
|
||||||
" vec4 r = u_uv[int(a_misc.z + 0.5)];",
|
" vec4 r = u_uv[int(a_misc.z + 0.5)];",
|
||||||
" v_uv = r.xy + (a_corner + 0.5) * r.zw;",
|
" v_local = a_corner + 0.5;",
|
||||||
|
" v_uv = r.xy + v_local * r.zw;",
|
||||||
" v_alpha = a_misc.y;",
|
" v_alpha = a_misc.y;",
|
||||||
" v_tint = a_tint;",
|
" v_tint = a_tint;",
|
||||||
|
" v_kind = a_misc.z;",
|
||||||
|
" v_seed = a_misc.w;",
|
||||||
"}"
|
"}"
|
||||||
].join("\n");
|
].join("\n");
|
||||||
|
|
||||||
|
// Sprite index 8 (CLOUD) skips the atlas and builds a cumulus in-shader:
|
||||||
|
// an fbm density field masked by an ellipse with a flattened base, lit
|
||||||
|
// from above so tops stay bright and undersides go grey-blue. The seed
|
||||||
|
// offsets the noise domain so every cloud has its own shape, and u_t
|
||||||
|
// drifts it slowly so the shape churns as it crosses the sky.
|
||||||
var PARTICLE_FS = [
|
var PARTICLE_FS = [
|
||||||
"#version 300 es",
|
"#version 300 es",
|
||||||
"precision mediump float;",
|
"precision highp float;",
|
||||||
"uniform sampler2D u_tex;",
|
"uniform sampler2D u_tex;",
|
||||||
"in vec2 v_uv; in float v_alpha; in vec3 v_tint;",
|
"uniform float u_t;",
|
||||||
|
"in vec2 v_uv; in vec2 v_local; in float v_alpha; in vec3 v_tint; in float v_kind; in float v_seed;",
|
||||||
"out vec4 o;",
|
"out vec4 o;",
|
||||||
|
"float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453123); }",
|
||||||
|
"float vnoise(vec2 p){",
|
||||||
|
" vec2 i = floor(p), f = fract(p); f = f*f*(3.0-2.0*f);",
|
||||||
|
" float a = hash(i), b = hash(i+vec2(1,0)), c = hash(i+vec2(0,1)), d = hash(i+vec2(1,1));",
|
||||||
|
" return mix(mix(a,b,f.x), mix(c,d,f.x), f.y);",
|
||||||
|
"}",
|
||||||
|
"float fbm(vec2 p){",
|
||||||
|
" float v = 0.0, a = 0.5;",
|
||||||
|
" for (int i = 0; i < 4; i++){ v += a * vnoise(p); p = p*2.03 + vec2(17.0); a *= 0.5; }",
|
||||||
|
" return v;",
|
||||||
|
"}",
|
||||||
"void main(){",
|
"void main(){",
|
||||||
" vec4 t = texture(u_tex, v_uv);", // premultiplied
|
" vec4 t = texture(u_tex, v_uv);", // premultiplied
|
||||||
|
" if (v_kind > 7.5 && v_kind < 8.5) {",
|
||||||
|
" vec2 q = v_local - 0.5;",
|
||||||
|
" q.x *= 1.667;", // undo the 320:192 quad stretch
|
||||||
|
" vec2 w = q*3.1 + vec2(v_seed*41.7, v_seed*13.9);",
|
||||||
|
" float n = fbm(w + vec2(u_t*0.018, 0.0));",
|
||||||
|
" float n2 = fbm(w*2.4 + vec2(-u_t*0.011, u_t*0.006) + 19.0);",
|
||||||
|
" float body = n*0.7 + n2*0.3;",
|
||||||
|
" float ry = q.y > 0.0 ? q.y*2.9 : q.y*1.8;", // flat base, puffy top
|
||||||
|
" float r = length(vec2(q.x*1.1, ry));",
|
||||||
|
" float dens = smoothstep(0.34, 0.80, body*0.95 + (0.74 - r));",
|
||||||
|
" float lgt = clamp(0.62 - q.y*1.6 + (body - 0.5)*0.9, 0.0, 1.0);",
|
||||||
|
" vec3 col = mix(v_tint*vec3(0.50,0.54,0.64), v_tint*1.18, lgt);",
|
||||||
|
" o = vec4(col, 1.0) * (dens * v_alpha);", // premultiplied
|
||||||
|
" } else {",
|
||||||
" o = vec4(t.rgb * v_tint, t.a) * v_alpha;",
|
" o = vec4(t.rgb * v_tint, t.a) * v_alpha;",
|
||||||
|
" }",
|
||||||
"}"
|
"}"
|
||||||
].join("\n");
|
].join("\n");
|
||||||
|
|
||||||
@@ -315,7 +289,8 @@
|
|||||||
].join("\n");
|
].join("\n");
|
||||||
|
|
||||||
// The sky shader carries every fullscreen effect behind a mode switch:
|
// The sky shader carries every fullscreen effect behind a mode switch:
|
||||||
// 1 clear day (sun glow + slow rays), 2 clear night glow, 3 fog fbm,
|
// 1 clear day (sun glow + slow rays), 2 clear night (procedural moon:
|
||||||
|
// sphere-shaded disc with noise relief, maria and a halo), 3 fog fbm,
|
||||||
// 4 Saharan haze, 5 aurora curtains, 6 storm gloom. u_flash rides on any
|
// 4 Saharan haze, 5 aurora curtains, 6 storm gloom. u_flash rides on any
|
||||||
// mode for the lightning whiteout.
|
// mode for the lightning whiteout.
|
||||||
var SKY_FS = [
|
var SKY_FS = [
|
||||||
@@ -358,10 +333,30 @@
|
|||||||
" } else if (u_mode == 2) {",
|
" } else if (u_mode == 2) {",
|
||||||
" vec2 c = vec2(0.82*aspect, 0.18);",
|
" vec2 c = vec2(0.82*aspect, 0.18);",
|
||||||
" vec2 p = vec2(uv.x*aspect, uv.y);",
|
" vec2 p = vec2(uv.x*aspect, uv.y);",
|
||||||
" float d = distance(p, c);",
|
" float mrad = 0.085 * min(aspect, 1.0);", // ~8.5% of the short side
|
||||||
|
" vec2 m = (p - c) / mrad;",
|
||||||
|
" float d = length(m);",
|
||||||
" float breathe = 0.5 + 0.5*sin(u_t*0.25);",
|
" float breathe = 0.5 + 0.5*sin(u_t*0.25);",
|
||||||
" float glow = exp(-d*d*5.0) * (0.12 + 0.05*breathe);",
|
" float halo = exp(-max(d - 1.0, 0.0) * 2.6) * (0.10 + 0.03*breathe);",
|
||||||
" rgb = vec3(0.86,0.90,1.0)*glow; a = glow;",
|
" float edge = 1.0 - smoothstep(0.97, 1.0, d);",
|
||||||
|
" vec3 mcol = vec3(0.0);",
|
||||||
|
" if (d < 1.0) {",
|
||||||
|
" float z = sqrt(max(0.0, 1.0 - d*d));",
|
||||||
|
" vec3 nrm = vec3(m, z);",
|
||||||
|
" vec2 sp = m / (z + 0.28);", // foreshorten detail at the limb
|
||||||
|
" float e1 = fbm(sp*3.4 + 11.0);",
|
||||||
|
" float e2 = fbm(sp*3.4 + 11.0 + vec2(0.05,-0.04));", // resample toward the light
|
||||||
|
" float relief = (e2 - e1) * 1.4;", // emboss: lit rims, shadowed pits
|
||||||
|
" float alb = 0.72 + relief;",
|
||||||
|
" alb -= smoothstep(0.48, 0.70, fbm(sp*1.3 + 4.0)) * 0.24;", // maria
|
||||||
|
" alb += (fbm(sp*7.0 + 27.0) - 0.5) * 0.10;", // fine grain
|
||||||
|
" vec3 L = normalize(vec3(0.55,-0.42,0.58));",
|
||||||
|
" float shade = 0.16 + 0.84*smoothstep(-0.15, 0.45, dot(nrm, L));",
|
||||||
|
" shade *= 0.75 + 0.25*z;", // limb darkening
|
||||||
|
" mcol = vec3(0.92,0.94,1.02) * clamp(alb, 0.0, 1.0) * shade;",
|
||||||
|
" }",
|
||||||
|
" rgb = vec3(0.72,0.78,0.95)*halo*(1.0 - edge) + mcol*edge;",
|
||||||
|
" a = halo*(1.0 - edge) + 0.97*edge;",
|
||||||
" } else if (u_mode == 3) {",
|
" } else if (u_mode == 3) {",
|
||||||
" vec2 q = vec2(uv.x*aspect, uv.y);",
|
" vec2 q = vec2(uv.x*aspect, uv.y);",
|
||||||
" float n1 = fbm(q*vec2(1.6,2.6) + vec2(u_t*0.020, 0.0));",
|
" float n1 = fbm(q*vec2(1.6,2.6) + vec2(u_t*0.020, 0.0));",
|
||||||
@@ -439,6 +434,7 @@
|
|||||||
U.res = gl.getUniformLocation(prog, "u_res");
|
U.res = gl.getUniformLocation(prog, "u_res");
|
||||||
U.uv = gl.getUniformLocation(prog, "u_uv");
|
U.uv = gl.getUniformLocation(prog, "u_uv");
|
||||||
U.tex = gl.getUniformLocation(prog, "u_tex");
|
U.tex = gl.getUniformLocation(prog, "u_tex");
|
||||||
|
U.t = gl.getUniformLocation(prog, "u_t");
|
||||||
SU.res = gl.getUniformLocation(skyProg, "u_res");
|
SU.res = gl.getUniformLocation(skyProg, "u_res");
|
||||||
SU.t = gl.getUniformLocation(skyProg, "u_t");
|
SU.t = gl.getUniformLocation(skyProg, "u_t");
|
||||||
SU.mode = gl.getUniformLocation(skyProg, "u_mode");
|
SU.mode = gl.getUniformLocation(skyProg, "u_mode");
|
||||||
@@ -593,7 +589,7 @@
|
|||||||
p.vx = (p.far ? rand(4, 9) : rand(10, 24));
|
p.vx = (p.far ? rand(4, 9) : rand(10, 24));
|
||||||
p.size = (p.far ? rand(60, 110) : rand(90, 180)) * p.z;
|
p.size = (p.far ? rand(60, 110) : rand(90, 180)) * p.z;
|
||||||
p.alpha = p.far ? rand(0.18, 0.30) : rand(0.32, 0.55);
|
p.alpha = p.far ? rand(0.18, 0.30) : rand(0.32, 0.55);
|
||||||
p.sprite = SPR.CLOUD0 + Math.floor(rand(0, 3));
|
p.seed = rand(0, 10);
|
||||||
p.bobPhase = rand(0, Math.PI * 2);
|
p.bobPhase = rand(0, Math.PI * 2);
|
||||||
} else if (kind === "fogband") {
|
} else if (kind === "fogband") {
|
||||||
p.y = rand(H * 0.3, H);
|
p.y = rand(H * 0.3, H);
|
||||||
@@ -601,7 +597,7 @@
|
|||||||
p.vx = rand(6, 16);
|
p.vx = rand(6, 16);
|
||||||
p.w = rand(320, 640);
|
p.w = rand(320, 640);
|
||||||
p.alpha = rand(0.04, 0.08);
|
p.alpha = rand(0.04, 0.08);
|
||||||
p.sprite = SPR.CLOUD0 + Math.floor(rand(0, 3));
|
p.seed = rand(0, 10);
|
||||||
} else if (kind === "blossom") {
|
} else if (kind === "blossom") {
|
||||||
p.vy = rand(60, 120);
|
p.vy = rand(60, 120);
|
||||||
p.swayAmp = rand(20, 50);
|
p.swayAmp = rand(20, 50);
|
||||||
@@ -663,26 +659,27 @@
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stars + moon are position-stable; only their twinkle animates.
|
// Stars are position-stable; only their twinkle animates. The moon lives
|
||||||
|
// in the sky shader (mode 2), which draws under the particles, so on clear
|
||||||
|
// nights we skip stars that would land on the disc.
|
||||||
function buildStatics() {
|
function buildStatics() {
|
||||||
statics = [];
|
statics = [];
|
||||||
var info = phaseInfo();
|
var info = phaseInfo();
|
||||||
builtNight = info.isNight;
|
builtNight = info.isNight;
|
||||||
if (!info.isNight || (variant !== "clear" && variant !== "aurora")) return;
|
if (!info.isNight || (variant !== "clear" && variant !== "aurora")) return;
|
||||||
var n = variant === "aurora" ? 90 : 70;
|
var n = variant === "aurora" ? 90 : 70;
|
||||||
|
var mx = W * 0.82, my = H * 0.18, mr = 0.085 * Math.min(W, H) * 1.25;
|
||||||
for (var i = 0; i < n; i++) {
|
for (var i = 0; i < n; i++) {
|
||||||
var sx = ((i * 73 + 11) % 100) / 100;
|
var sx = ((i * 73 + 11) % 100) / 100 * W;
|
||||||
var sy = ((i * 37 + 7) % 100) / 100;
|
var sy = ((i * 37 + 7) % 100) / 100 * 0.7 * H;
|
||||||
|
if (variant === "clear" && Math.hypot(sx - mx, sy - my) < mr) continue;
|
||||||
statics.push({
|
statics.push({
|
||||||
kind: "star",
|
kind: "star",
|
||||||
x: sx * W, y: sy * 0.7 * H,
|
x: sx, y: sy,
|
||||||
size: 2.2 + (i % 3) * 1.6,
|
size: 2.2 + (i % 3) * 1.6,
|
||||||
ph: i % 7, freq: 0.6 + (i % 5) * 0.15
|
ph: i % 7, freq: 0.6 + (i % 5) * 0.15
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (variant === "clear") {
|
|
||||||
statics.push({ kind: "moon", x: W * 0.82, y: H * 0.18, size: Math.min(W, H) * 0.5 * 0.16 * 4.8 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function build() {
|
function build() {
|
||||||
@@ -745,11 +742,11 @@
|
|||||||
|
|
||||||
// ---- frame --------------------------------------------------------------
|
// ---- frame --------------------------------------------------------------
|
||||||
var n = 0; // instances written this frame
|
var n = 0; // instances written this frame
|
||||||
function push(x, y, sx, sy, rot, alpha, sprite, r, g, b) {
|
function push(x, y, sx, sy, rot, alpha, sprite, r, g, b, seed) {
|
||||||
if (n >= MAX_INSTANCES) return;
|
if (n >= MAX_INSTANCES) return;
|
||||||
var o = n * FLOATS;
|
var o = n * FLOATS;
|
||||||
inst[o] = x; inst[o + 1] = y; inst[o + 2] = sx; inst[o + 3] = sy;
|
inst[o] = x; inst[o + 1] = y; inst[o + 2] = sx; inst[o + 3] = sy;
|
||||||
inst[o + 4] = rot; inst[o + 5] = alpha; inst[o + 6] = sprite; inst[o + 7] = 0;
|
inst[o + 4] = rot; inst[o + 5] = alpha; inst[o + 6] = sprite; inst[o + 7] = seed || 0;
|
||||||
inst[o + 8] = r; inst[o + 9] = g; inst[o + 10] = b;
|
inst[o + 8] = r; inst[o + 9] = g; inst[o + 10] = b;
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
@@ -811,15 +808,11 @@
|
|||||||
n = 0;
|
n = 0;
|
||||||
var i, p, sway, tw, a;
|
var i, p, sway, tw, a;
|
||||||
|
|
||||||
// Stars first so the moon and everything else draws over them.
|
// Stars first so everything else draws over them.
|
||||||
for (i = 0; i < statics.length; i++) {
|
for (i = 0; i < statics.length; i++) {
|
||||||
p = statics[i];
|
p = statics[i];
|
||||||
if (p.kind === "star") {
|
|
||||||
tw = 0.4 + 0.6 * (0.5 + 0.5 * Math.sin(t * p.freq + p.ph));
|
tw = 0.4 + 0.6 * (0.5 + 0.5 * Math.sin(t * p.freq + p.ph));
|
||||||
push(p.x, p.y, p.size * 2.4, p.size * 2.4, 0, 0.55 * tw, SPR.GLOW, 1, 1, 1);
|
push(p.x, p.y, p.size * 2.4, p.size * 2.4, 0, 0.55 * tw, SPR.GLOW, 1, 1, 1);
|
||||||
} else if (p.kind === "moon") {
|
|
||||||
push(p.x, p.y, p.size, p.size, 0, 1, SPR.MOON, 1, 1, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shooting stars on clear/aurora nights.
|
// Shooting stars on clear/aurora nights.
|
||||||
@@ -864,14 +857,14 @@
|
|||||||
p.x += p.vx * dt;
|
p.x += p.vx * dt;
|
||||||
if (p.x - p.size * 1.3 > W + 60) { particles[i] = spawn("cloud", false); continue; }
|
if (p.x - p.size * 1.3 > W + 60) { particles[i] = spawn("cloud", false); continue; }
|
||||||
var bob = Math.sin(t * 0.08 + p.bobPhase) * 6;
|
var bob = Math.sin(t * 0.08 + p.bobPhase) * 6;
|
||||||
var ct = night ? [0.81, 0.85, 0.93] : [0.59, 0.64, 0.75];
|
var ct = night ? [0.38, 0.42, 0.54] : [0.82, 0.85, 0.92];
|
||||||
var cw = p.size * 2.6, ch = cw * (192 / 320);
|
var cw = p.size * 2.6, ch = cw * (192 / 320);
|
||||||
push(p.x, p.y + bob, cw, ch, 0, p.alpha * (p.far ? 0.85 : 1), p.sprite, ct[0], ct[1], ct[2]);
|
push(p.x, p.y + bob, cw, ch, 0, p.alpha * (p.far ? 0.85 : 1), SPR.CLOUD, ct[0], ct[1], ct[2], p.seed);
|
||||||
} else if (p.kind === "fogband") {
|
} else if (p.kind === "fogband") {
|
||||||
p.x += p.vx * dt;
|
p.x += p.vx * dt;
|
||||||
if (p.x - p.w / 2 > W + 60) { particles[i] = spawn("fogband", false); continue; }
|
if (p.x - p.w / 2 > W + 60) { particles[i] = spawn("fogband", false); continue; }
|
||||||
var ft = night ? [0.5, 0.55, 0.65] : [0.9, 0.89, 0.87];
|
var ft = night ? [0.5, 0.55, 0.65] : [0.9, 0.89, 0.87];
|
||||||
push(p.x, p.y, p.w, p.w * 0.3, 0, p.alpha, p.sprite, ft[0], ft[1], ft[2]);
|
push(p.x, p.y, p.w, p.w * 0.3, 0, p.alpha, SPR.CLOUD, ft[0], ft[1], ft[2], p.seed);
|
||||||
} else if (p.kind === "blossom") {
|
} else if (p.kind === "blossom") {
|
||||||
p.y += p.vy * dt;
|
p.y += p.vy * dt;
|
||||||
p.x += windNow * 0.4 * dt;
|
p.x += windNow * 0.4 * dt;
|
||||||
@@ -968,6 +961,7 @@
|
|||||||
gl.useProgram(prog);
|
gl.useProgram(prog);
|
||||||
gl.bindVertexArray(vao);
|
gl.bindVertexArray(vao);
|
||||||
gl.uniform2f(U.res, W, H);
|
gl.uniform2f(U.res, W, H);
|
||||||
|
gl.uniform1f(U.t, t);
|
||||||
gl.uniform4fv(U.uv, uvRects);
|
gl.uniform4fv(U.uv, uvRects);
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||||
|
|||||||
Reference in New Issue
Block a user