Zoomable Texturing

When performing “deep zoom” into simple textured geometry (no fancy proceduralism or raymarching), it doesn’t take long before seeing big fat blurry texels. This post presents some thoughts on simple tileable texturing, inspired by slippy maps.

First, a demo. Press the zoom button to zoom to the east coast of Corsica in this quick-and-dirty map of Europe. This is not a slippy map. It’s just a simple WebGL app with two draw calls: one for the landmass mesh, and one for the water.

Loading...

The demo draws inspiration from slippy maps to figure out how to scale and translate the texture coordinates. The shader that applies the water texture doubles the frequency of the texture coordinates every time the zoom level crosses a new integer threshold. Most of the math is done on the CPU, so the fragment shader is really simple:

varying vec2 v_texcoord;
uniform vec4 u_slippybox;

void main()
{
    vec2 tex_offset = v_texcoord - u_slippybox.xy;
    vec2 uv = tex_offset * u_slippybox.zw;
    gl_FragColor = sample(uv);
}

The landmass shader works similarly, except that it cross-fades between two texture frequencies, thus avoiding the noticeable pop seen with the water texture:

uniform vec4 u_slippybox;
uniform float u_slippyfract;

void main()
{
    vec2 tex_offset = gl_FragCoord.xy - u_slippybox.xy;
    vec2 uv = tex_offset * u_slippybox.zw;
    vec4 texel0 = sample(uv);
    vec4 texel1 = sample(uv * 2.0);
    gl_FragColor = mix(texel0, texel1, u_slippyfract);
}

When the demo is done zooming, you might see some precision problems with the water texture, but the landmass looks fine. That’s because the landmass shader uses gl_FragCoord rather than baked-in mesh coordinates. Try toggling the FragCoord button to see the difference.

This demo was created with emscripten, and the source code can be found below. Apologies if it’s a bit funky; it uses one of my tiny graphics engines that are forever works in progress.