feat: estiliza Via Láctea como astrofoto de longa exposição
This commit is contained in:
parent
d8d99903d6
commit
e59fe5a27a
1 changed files with 67 additions and 35 deletions
|
|
@ -1275,7 +1275,8 @@ const G2E = [
|
||||||
A textura é calculada no espaço galáctico em uma camada de baixa resolução.
|
A textura é calculada no espaço galáctico em uma camada de baixa resolução.
|
||||||
Assim a faixa, o bojo e a poeira acompanham a projeção real sem denunciar
|
Assim a faixa, o bojo e a poeira acompanham a projeção real sem denunciar
|
||||||
sprites circulares ou custar um passe em resolução cheia. */
|
sprites circulares ou custar um passe em resolução cheia. */
|
||||||
const MW_RENDER_SCALE = 0.25;
|
const MW_RENDER_SCALE = 0.50;
|
||||||
|
const MW_NOISE_PERIODS = [5, 10, 20, 40, 80];
|
||||||
const mwLayer = document.createElement('canvas');
|
const mwLayer = document.createElement('canvas');
|
||||||
|
|
||||||
function mwHash(ix, iy) {
|
function mwHash(ix, iy) {
|
||||||
|
|
@ -1289,11 +1290,12 @@ function mwValueNoise(x, y, periodX) {
|
||||||
const tx0 = x - fx, ty0 = y - fy;
|
const tx0 = x - fx, ty0 = y - fy;
|
||||||
const tx = tx0 * tx0 * (3 - 2 * tx0);
|
const tx = tx0 * tx0 * (3 - 2 * tx0);
|
||||||
const ty = ty0 * ty0 * (3 - 2 * ty0);
|
const ty = ty0 * ty0 * (3 - 2 * ty0);
|
||||||
const wrap = n => ((n % periodX) + periodX) % periodX;
|
const x0 = ((fx % periodX) + periodX) % periodX;
|
||||||
const a = mwHash(wrap(fx), fy);
|
const x1 = (x0 + 1) % periodX;
|
||||||
const b = mwHash(wrap(fx + 1), fy);
|
const a = mwHash(x0, fy);
|
||||||
const c = mwHash(wrap(fx), fy + 1);
|
const b = mwHash(x1, fy);
|
||||||
const d = mwHash(wrap(fx + 1), fy + 1);
|
const c = mwHash(x0, fy + 1);
|
||||||
|
const d = mwHash(x1, fy + 1);
|
||||||
const top = a + (b - a) * tx;
|
const top = a + (b - a) * tx;
|
||||||
const bot = c + (d - c) * tx;
|
const bot = c + (d - c) * tx;
|
||||||
return top + (bot - top) * ty;
|
return top + (bot - top) * ty;
|
||||||
|
|
@ -1302,44 +1304,64 @@ function mwValueNoise(x, y, periodX) {
|
||||||
/* Ruído periódico em longitude: não deixa emenda em l=0°/360°. */
|
/* Ruído periódico em longitude: não deixa emenda em l=0°/360°. */
|
||||||
function mwFbm(l, b) {
|
function mwFbm(l, b) {
|
||||||
let value = 0, norm = 0, amp = 1;
|
let value = 0, norm = 0, amp = 1;
|
||||||
for (const period of [4, 8, 16, 32]) {
|
for (const period of MW_NOISE_PERIODS) {
|
||||||
value += amp * mwValueNoise(l / 360 * period, (b + 90) / 360 * period, period);
|
value += amp * mwValueNoise(l / 360 * period, (b + 90) / 360 * period, period);
|
||||||
norm += amp;
|
norm += amp;
|
||||||
amp *= 0.52;
|
amp *= 0.50;
|
||||||
}
|
}
|
||||||
return value / norm;
|
return value / norm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mwFineNoise(l, b) {
|
||||||
|
const n1 = mwValueNoise(l / 360 * 160, (b + 90) / 360 * 160, 160);
|
||||||
|
const n2 = mwValueNoise(l / 360 * 320, (b + 90) / 360 * 320, 320);
|
||||||
|
return n1 * 0.68 + n2 * 0.32;
|
||||||
|
}
|
||||||
|
|
||||||
function mwAngularDistance(l, target) {
|
function mwAngularDistance(l, target) {
|
||||||
const d = Math.abs(l - target);
|
const d = Math.abs(l - target);
|
||||||
return Math.min(d, 360 - d);
|
return Math.min(d, 360 - d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mwCloud(l, b, cl, cb, wl, wb, strength) {
|
||||||
|
const x = mwAngularDistance(l, cl) / wl;
|
||||||
|
const y = (b - cb) / wb;
|
||||||
|
return strength * Math.exp(-0.5 * (x * x + y * y));
|
||||||
|
}
|
||||||
|
|
||||||
/* Densidade luminosa em coordenadas galácticas. O detalhe vem do ruído,
|
/* Densidade luminosa em coordenadas galácticas. O detalhe vem do ruído,
|
||||||
enquanto as formas grandes continuam astronomicamente reconhecíveis. */
|
enquanto as formas grandes continuam astronomicamente reconhecíveis. */
|
||||||
function mwDensity(l, b) {
|
function mwDensity(l, b) {
|
||||||
const dl = Math.min(l, 360 - l);
|
const dl = Math.min(l, 360 - l);
|
||||||
const core = Math.exp(-((dl / 72) ** 2));
|
const core = Math.exp(-((dl / 72) ** 2));
|
||||||
const width = 4.2 + 5.2 * core;
|
|
||||||
const cloud = mwFbm(l, b);
|
const cloud = mwFbm(l, b);
|
||||||
const fine = mwFbm((l * 2) % 360, b * 2.2);
|
const fine = mwFineNoise(l, b);
|
||||||
|
const warpedB = b - (cloud - 0.5) * (2.2 + 1.8 * core);
|
||||||
|
const width = 3.7 + 5.8 * core + 1.3 * cloud;
|
||||||
|
const plane = Math.exp(-0.5 * (warpedB / width) ** 2);
|
||||||
|
|
||||||
let density = Math.exp(-0.5 * (b / width) ** 2) *
|
/* Luz integrada: halo largo, nuvens irregulares e grão fino de estrelas. */
|
||||||
(0.34 + 1.05 * cloud ** 1.7);
|
let density = plane * (0.20 + 1.45 * cloud ** 2.1);
|
||||||
density += 0.16 * Math.exp(-0.5 * (b / (width * 2.6)) ** 2) *
|
density += 0.12 * Math.exp(-0.5 * (warpedB / (width * 2.9)) ** 2) *
|
||||||
(0.45 + 0.55 * cloud);
|
(0.35 + 0.65 * cloud);
|
||||||
density += 1.05 * Math.exp(-0.5 * ((dl / 19) ** 2 + (b / 11) ** 2)) *
|
density += plane * Math.max(0, fine - 0.46) ** 2 * 3.8;
|
||||||
(0.65 + 0.45 * cloud);
|
|
||||||
|
/* Grandes nuvens estelares reais, exageradas como numa longa exposição. */
|
||||||
|
density += mwCloud(l, b, 0, 0.0, 20, 11, 1.55); // bojo de Sagitário
|
||||||
|
density += mwCloud(l, b, 25, 0.5, 10, 4, 0.62); // Scutum
|
||||||
|
density += mwCloud(l, b, 330, -0.7, 11, 5, 0.52);
|
||||||
|
density += mwCloud(l, b, 285, -0.4, 13, 5, 0.55); // Carina
|
||||||
|
density += mwCloud(l, b, 80, 0.8, 12, 4, 0.38); // Cygnus
|
||||||
|
|
||||||
/* Grande Fenda: duas trilhas estreitas, deformadas e com força irregular. */
|
/* Grande Fenda: duas trilhas estreitas, deformadas e com força irregular. */
|
||||||
const riftReach = 1 - Math.max(0, Math.min(1, (dl - 70) / 55));
|
const riftReach = 1 - Math.max(0, Math.min(1, (dl - 70) / 55));
|
||||||
const lr = l * DEG;
|
const lr = l * DEG;
|
||||||
const riftA = 0.7 + 1.45 * Math.sin(lr * 2.1 + 0.5) + 0.45 * Math.sin(lr * 7.0);
|
const riftA = 0.6 + 1.55 * Math.sin(lr * 2.1 + 0.5) + 0.55 * Math.sin(lr * 7.0);
|
||||||
const riftB = -2.3 + 0.75 * Math.sin(lr * 1.4 + 2.2);
|
const riftB = -2.5 + 0.85 * Math.sin(lr * 1.4 + 2.2);
|
||||||
const darkA = Math.exp(-0.5 * ((b - riftA) / (1.0 + 0.7 * fine)) ** 2) *
|
const darkA = Math.exp(-0.5 * ((b - riftA) / (0.72 + 0.72 * fine)) ** 2) *
|
||||||
(0.58 + 0.28 * fine);
|
(0.66 + 0.30 * fine);
|
||||||
const darkB = Math.exp(-0.5 * ((b - riftB) / 1.15) ** 2) * 0.48;
|
const darkB = Math.exp(-0.5 * ((b - riftB) / 0.95) ** 2) * 0.58;
|
||||||
density *= 1 - Math.min(0.90, (darkA + darkB) * riftReach);
|
density *= 1 - Math.min(0.94, (darkA + darkB) * riftReach);
|
||||||
|
|
||||||
/* Saco de Carvão junto ao Cruzeiro do Sul. */
|
/* Saco de Carvão junto ao Cruzeiro do Sul. */
|
||||||
const coal = Math.exp(-0.5 * ((mwAngularDistance(l, 303) / 3.8) ** 2 +
|
const coal = Math.exp(-0.5 * ((mwAngularDistance(l, 303) / 3.8) ** 2 +
|
||||||
|
|
@ -1349,15 +1371,18 @@ function mwDensity(l, b) {
|
||||||
return Math.max(0, density * (0.24 + 0.76 * Math.exp(-((dl / 105) ** 2))));
|
return Math.max(0, density * (0.24 + 0.76 * Math.exp(-((dl / 105) ** 2))));
|
||||||
}
|
}
|
||||||
|
|
||||||
function mwRgb(dl) {
|
function mwWriteRgb(pixels, offset, dl) {
|
||||||
const t = Math.min(1, dl / 145);
|
const t = Math.min(1, dl / 150);
|
||||||
const warm = [255, 220, 181], neutral = [222, 226, 235], cool = [177, 207, 244];
|
|
||||||
const a = t < 0.5 ? warm : neutral;
|
|
||||||
const z = t < 0.5 ? neutral : cool;
|
|
||||||
const u = t < 0.5 ? t * 2 : (t - 0.5) * 2;
|
const u = t < 0.5 ? t * 2 : (t - 0.5) * 2;
|
||||||
return [a[0] + (z[0] - a[0]) * u,
|
if (t < 0.5) {
|
||||||
a[1] + (z[1] - a[1]) * u,
|
pixels[offset] = 255 + (226 - 255) * u;
|
||||||
a[2] + (z[2] - a[2]) * u];
|
pixels[offset + 1] = 199 + (218 - 199) * u;
|
||||||
|
pixels[offset + 2] = 145 + (239 - 145) * u;
|
||||||
|
} else {
|
||||||
|
pixels[offset] = 226 + (148 - 226) * u;
|
||||||
|
pixels[offset + 1] = 218 + (201 - 218) * u;
|
||||||
|
pixels[offset + 2] = 239 + (255 - 239) * u;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMilkyWay(W, H, horizonPx, lst, glareOn) {
|
function renderMilkyWay(W, H, horizonPx, lst, glareOn) {
|
||||||
|
|
@ -1405,12 +1430,11 @@ function renderMilkyWay(W, H, horizonPx, lst, glareOn) {
|
||||||
if (density < 0.012) continue;
|
if (density < 0.012) continue;
|
||||||
const screenX = (x + 0.5) / rw * W;
|
const screenX = (x + 0.5) / rw * W;
|
||||||
const glare = glareFactor(screenX, screenY, W, H, glareOn);
|
const glare = glareFactor(screenX, screenY, W, H, glareOn);
|
||||||
const alpha = Math.min(0.26, density * 0.145) * ext * glare;
|
/* Curva de exposição comprime o núcleo sem apagar estruturas tênues. */
|
||||||
|
const exposure = 1 - Math.exp(-density * 0.72);
|
||||||
|
const alpha = Math.min(0.46, exposure * 0.42) * ext * glare;
|
||||||
if (alpha < 0.002) continue;
|
if (alpha < 0.002) continue;
|
||||||
const rgb = mwRgb(Math.min(l, 360 - l));
|
mwWriteRgb(pixels, offset, Math.min(l, 360 - l));
|
||||||
pixels[offset] = rgb[0];
|
|
||||||
pixels[offset + 1] = rgb[1];
|
|
||||||
pixels[offset + 2] = rgb[2];
|
|
||||||
pixels[offset + 3] = alpha * 255;
|
pixels[offset + 3] = alpha * 255;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1439,7 +1463,15 @@ function projectSky() {
|
||||||
renderMilkyWay(W, H, horizonPx, lst, glareOn);
|
renderMilkyWay(W, H, horizonPx, lst, glareOn);
|
||||||
bctx.imageSmoothingEnabled = true;
|
bctx.imageSmoothingEnabled = true;
|
||||||
bctx.imageSmoothingQuality = 'high';
|
bctx.imageSmoothingQuality = 'high';
|
||||||
|
bctx.save();
|
||||||
|
bctx.globalCompositeOperation = 'lighter';
|
||||||
|
bctx.globalAlpha = 0.48;
|
||||||
|
bctx.filter = `blur(${Math.max(10, H * 0.014)}px) saturate(155%)`;
|
||||||
bctx.drawImage(mwLayer, 0, 0, W, horizonPx);
|
bctx.drawImage(mwLayer, 0, 0, W, horizonPx);
|
||||||
|
bctx.globalAlpha = 0.92;
|
||||||
|
bctx.filter = 'contrast(118%) saturate(128%)';
|
||||||
|
bctx.drawImage(mwLayer, 0, 0, W, horizonPx);
|
||||||
|
bctx.restore();
|
||||||
|
|
||||||
for (let i = 0; i < STAR_DATA.length; i++) {
|
for (let i = 0; i < STAR_DATA.length; i++) {
|
||||||
const [ra, dec, mag, ci] = STAR_DATA[i];
|
const [ra, dec, mag, ci] = STAR_DATA[i];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue