Add satellite ground texture (ESRI World Imagery, Passau)
Pulled a 5x4 tile mosaic of the Passau region (zoom 10) from ESRI's free ArcGIS World Imagery service, stitched + resized to 1024x768 JPEG (277 KB). Added to Dockerfile as ground.jpg. Shader side: - New uniform uGround (texture unit 1) - horizonMask: rd.y in [-0.20, -0.02] -> ground fades in from 0 to 1 as we look further down - gUv derived from rd.xz so looking forward/down scrolls across the satellite image (parallax-like feel) - Distant ground fades toward uBg via a 'fog' factor so the horizon blends naturally with the sky gradient Loading is async — clouds render before ground texture arrives. Falls back to a black sample if ground.jpg fails to load.
This commit is contained in:
1 parent
2e745c26ad
commit
6323beccf9
4 files changed
+56
-1
No files matched your search
@@ -7,6 +7,7 @@ COPY config.js /usr/share/nginx/html/config.js
|
|||||||
COPY i18n.js /usr/share/nginx/html/i18n.js
|
COPY i18n.js /usr/share/nginx/html/i18n.js
|
||||||
COPY background.html /usr/share/nginx/html/background.html
|
COPY background.html /usr/share/nginx/html/background.html
|
||||||
COPY noise.png /usr/share/nginx/html/noise.png
|
COPY noise.png /usr/share/nginx/html/noise.png
|
||||||
|
COPY ground.jpg /usr/share/nginx/html/ground.jpg
|
||||||
COPY vendor/ /usr/share/nginx/html/vendor/
|
COPY vendor/ /usr/share/nginx/html/vendor/
|
||||||
EXPOSE 58881
|
EXPOSE 58881
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 277 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 2.7 MiB |
Vendored
+55
-1
@@ -53,6 +53,7 @@ var FRAG_SRC = [
|
|||||||
'precision highp float;',
|
'precision highp float;',
|
||||||
'uniform vec2 uResolution;',
|
'uniform vec2 uResolution;',
|
||||||
'uniform float uTime;',
|
'uniform float uTime;',
|
||||||
|
'uniform sampler2D uGround;',
|
||||||
'uniform vec3 uSky;',
|
'uniform vec3 uSky;',
|
||||||
'uniform vec3 uCloud;',
|
'uniform vec3 uCloud;',
|
||||||
'uniform vec3 uCloudShadow;',
|
'uniform vec3 uCloudShadow;',
|
||||||
@@ -194,6 +195,18 @@ var FRAG_SRC = [
|
|||||||
// remain visible against a deep but not void-black sky.
|
// remain visible against a deep but not void-black sky.
|
||||||
'float skyDarkness = nightFactor * 0.55;',
|
'float skyDarkness = nightFactor * 0.55;',
|
||||||
'col = mix(col, col * (1.0 - skyDarkness), 1.0 - cloud);',
|
'col = mix(col, col * (1.0 - skyDarkness), 1.0 - cloud);',
|
||||||
|
'// Horizon: when looking down (rd.y < 0), sample the satellite
|
||||||
|
// ground texture based on ray direction. Use the horizontal
|
||||||
|
// component as a UV — the further off-axis, the more the
|
||||||
|
// texture scrolls. Adds parallax-like feel even though the
|
||||||
|
// ground is a flat image.
|
||||||
|
'float gmask = smoothstep(-0.02, -0.20, rd.y);',
|
||||||
|
'vec2 gUv = vec2(rd.x * 0.5 + 0.5, -rd.y * 0.5 + 0.3);',
|
||||||
|
'vec3 groundCol = texture2D(uGround, gUv).rgb;',
|
||||||
|
// Distance fog: distant ground fades toward sky colour.
|
||||||
|
'float fog = clamp(-rd.y * 1.5, 0.0, 1.0);',
|
||||||
|
'groundCol = mix(groundCol, uBg, fog * 0.6);',
|
||||||
|
'col = mix(col, groundCol, gmask);',
|
||||||
'gl_FragColor = vec4(col, 1.0);',
|
'gl_FragColor = vec4(col, 1.0);',
|
||||||
'}'
|
'}'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
@@ -216,6 +229,7 @@ function init(canvas) {
|
|||||||
var aPos = gl.getAttribLocation(prog, 'aPos');
|
var aPos = gl.getAttribLocation(prog, 'aPos');
|
||||||
var uRes = gl.getUniformLocation(prog, 'uResolution');
|
var uRes = gl.getUniformLocation(prog, 'uResolution');
|
||||||
var uTime = gl.getUniformLocation(prog, 'uTime');
|
var uTime = gl.getUniformLocation(prog, 'uTime');
|
||||||
|
var uGround = gl.getUniformLocation(prog, 'uGround');
|
||||||
var uSky = gl.getUniformLocation(prog, 'uSky');
|
var uSky = gl.getUniformLocation(prog, 'uSky');
|
||||||
var uCloud = gl.getUniformLocation(prog, 'uCloud');
|
var uCloud = gl.getUniformLocation(prog, 'uCloud');
|
||||||
var uCloudShadow = gl.getUniformLocation(prog, 'uCloudShadow');
|
var uCloudShadow = gl.getUniformLocation(prog, 'uCloudShadow');
|
||||||
@@ -228,13 +242,37 @@ function init(canvas) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
gl: gl, prog: prog, buf: buf, aPos: aPos,
|
gl: gl, prog: prog, buf: buf, aPos: aPos,
|
||||||
uRes: uRes, uTime: uTime, uSky: uSky, uCloud: uCloud,
|
uRes: uRes, uTime: uTime, uGround: uGround, uSky: uSky, uCloud: uCloud,
|
||||||
uCloudShadow: uCloudShadow, uLight: uLight, uBg: uBg,
|
uCloudShadow: uCloudShadow, uLight: uLight, uBg: uBg,
|
||||||
uSpeed: uSpeed, uDensity: uDensity, uDayProgress: uDayProgress,
|
uSpeed: uSpeed, uDensity: uDensity, uDayProgress: uDayProgress,
|
||||||
uSunAlt: uSunAlt
|
uSunAlt: uSunAlt
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load an image and upload it as a CLAMP_TO_EDGE wrapped texture
|
||||||
|
// (ground shouldn't tile — it's a real photo).
|
||||||
|
function loadImageTexture(gl, src) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var img = new Image();
|
||||||
|
img.crossOrigin = 'anonymous';
|
||||||
|
img.onload = function() {
|
||||||
|
var tex = gl.createTexture();
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||||
|
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||||
|
resolve(tex);
|
||||||
|
};
|
||||||
|
img.onerror = function() {
|
||||||
|
reject(new Error('clouds3d: failed to load image: ' + src));
|
||||||
|
};
|
||||||
|
img.src = src;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function hex(rgb) {
|
function hex(rgb) {
|
||||||
return [((rgb >> 16) & 255) / 255,
|
return [((rgb >> 16) & 255) / 255,
|
||||||
((rgb >> 8) & 255) / 255,
|
((rgb >> 8) & 255) / 255,
|
||||||
@@ -272,6 +310,17 @@ window.clouds3dRender = function(target) {
|
|||||||
if (!canvas.__state) {
|
if (!canvas.__state) {
|
||||||
canvas.__state = init(canvas);
|
canvas.__state = init(canvas);
|
||||||
if (!canvas.__state) return;
|
if (!canvas.__state) return;
|
||||||
|
// Load the satellite ground texture asynchronously. The
|
||||||
|
// ground isn't strictly needed for the clouds-only view, so
|
||||||
|
// frames render normally before it loads — the shader just
|
||||||
|
// samples a black texture in the meantime.
|
||||||
|
canvas.__groundReady = false;
|
||||||
|
loadImageTexture(s.gl, 'ground.jpg').then(function(tex) {
|
||||||
|
canvas.__ground = tex;
|
||||||
|
canvas.__groundReady = true;
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.warn(err.message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var s = canvas.__state;
|
var s = canvas.__state;
|
||||||
@@ -285,6 +334,11 @@ window.clouds3dRender = function(target) {
|
|||||||
gl.enableVertexAttribArray(s.aPos);
|
gl.enableVertexAttribArray(s.aPos);
|
||||||
gl.vertexAttribPointer(s.aPos, 2, gl.FLOAT, false, 0, 0);
|
gl.vertexAttribPointer(s.aPos, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
// Bind ground texture to texture unit 1 (noise is on unit 0).
|
||||||
|
gl.activeTexture(gl.TEXTURE1);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, canvas.__ground || null);
|
||||||
|
gl.uniform1i(s.uGround, 1);
|
||||||
|
|
||||||
gl.uniform2f(s.uRes, canvas.width, canvas.height);
|
gl.uniform2f(s.uRes, canvas.width, canvas.height);
|
||||||
gl.uniform1f(s.uTime, performance.now() / 1000);
|
gl.uniform1f(s.uTime, performance.now() / 1000);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user