function makeTexture(draw, w = 512, h = 256) {
const c = document.createElement('canvas');
c.width = w; c.height = h;
draw(c.getContext('2d'), w, h);
const t = new THREE.CanvasTexture(c);
t.colorSpace = THREE.SRGBColorSpace;
return t;
}
// Procedural Earth Texture generated in memory
T.earth = makeTexture((g, w, h) => {
const grad = g.createLinearGradient(0, 0, 0, h);
grad.addColorStop(0, '#123a6d'); grad.addColorStop(.5, '#1a5fa8'); grad.addColorStop(1, '#123a6d');
g.fillStyle = grad; g.fillRect(0, 0, w, h);
for (let i = 0; i < 26; i++) { // Continents
g.fillStyle = 'rgba(' + ((40 + rand(0, 30)) | 0) + ',120,' + ((50 + rand(0, 30)) | 0) + ',.95)';
g.beginPath(); g.ellipse(rand(0, w), h * rand(0.2, 0.8), rand(15, 55), rand(8, 26), rand(0, 3), 0, 7); g.fill();
}
for (let i = 0; i < 60; i++) { // Clouds
g.fillStyle = 'rgba(255,255,255,' + rand(0.12, 0.3).toFixed(2) + ')';
g.beginPath(); g.ellipse(rand(0, w), rand(0, h), rand(8, 40), rand(3, 9), 0, 0, 7); g.fill();
}
g.fillStyle = 'rgba(255,255,255,.85)'; // Polar Ice Caps
g.fillRect(0, 0, w, h * 0.06); g.fillRect(0, h * 0.94, w, h * 0.06);
});
/* Seamless 3-D value noise + fBm sampled on sphere (zero pole seam) */
class Noise {
constructor(seed) {
const rng = mulberry32(seed);
const perm = [...Array(256).keys()];
for(let i = 255; i > 0; i--) {
const j = Math.floor(rng() * (i + 1));
[perm[i], perm[j]] = [perm[j], perm[i]];
}
this.p = new Uint8Array(512);
for(let i = 0; i < 512; i++) this.p[i] = perm[i & 255];
}
noise3(x, y, z) {
const xi = Math.floor(x), yi = Math.floor(y), zi = Math.floor(z);
const xf = x - xi, yf = y - yi, zf = z - zi;
const u = xf*xf*xf*(xf*(xf*6-15)+10);
const v = yf*yf*yf*(yf*(yf*6-15)+10);
const w = zf*zf*zf*(zf*(zf*6-15)+10);
const n = (i,j,k) => this.lat(xi+i, yi+j, zi+k);
const L = (a,b,t) => a + (b-a)*t;
return L(L(L(n(0,0,0), n(1,0,0), u), L(n(0,1,0), n(1,1,0), u), v),
L(L(n(0,0,1), n(1,0,1), u), L(n(0,1,1), n(1,1,1), u), v), w);
}
fbm(x, y, z, oct = 5, lac = 2, gain = .5) {
let amp = .5, f = 1, sum = 0, norm = 0;
for(let o = 0; o < oct; o++) {
sum += amp * this.noise3(x*f, y*f, z*f);
norm += amp; amp *= gain; f *= lac;
}
return sum / norm;
}
}
/* MSAA + HDR render target so bloom maintains crisp planetary edges */
const rt = new THREE.WebGLRenderTarget(innerWidth, innerHeight, {
samples: 4,
type: THREE.HalfFloatType
});
const composer = new EffectComposer(renderer, rt);
composer.setPixelRatio(Math.min(devicePixelRatio, 2));
composer.addPass(new RenderPass(scene, camera));
const bloom = new UnrealBloomPass(
new THREE.Vector2(innerWidth, innerHeight),
0.45, // Bloom Strength
0.38, // Bloom Radius
1.05 // Bloom Threshold
);
composer.addPass(bloom);
composer.addPass(new OutputPass());
/* Custom animated grain + subtle chromatic aberration shader pass */
const gradePass = new ShaderPass({
uniforms: { tDiffuse: { value: null }, uTime: { value: 0 } },
vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0); }`,
fragmentShader: `/* Dynamic noise + edge chromatic dispersion */`
});
composer.addPass(gradePass);