Anisotropic Filtering

A pixel on a receding floor covers a long, thin strip of the texture. Anisotropic filtering takes several samples along that strip from a sharper mipmap level, instead of one sample from a level blurred for its length. The EXT_texture_filter_anisotropic extension provides it, and the right-hand pane of the figure shows the effect: titles legible far into the distance at 16x. Set it per texture with texParameterf(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, n), up to the value of MAX_TEXTURE_MAX_ANISOTROPY_EXT, 16 on both the GTX 1650 and SwiftShader 2,546 . It only affects mipmapped minification, costing extra reads only where pixels are stretched; GLH.texture() asks for 8x.

A receding bookshop floor sampled with 1x, 4x and the maximum anisotropic filteringHTMLLive
<!doctype html>
<style>
  body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
  canvas { display: block; width: 100%; max-width: 600px; }
  .names { display: flex; max-width: 600px; text-align: center; font-family: monospace; }
  .names div { flex: 1; padding: 4px 2px; }
</style>
<canvas id="c" width="1200" height="320"></canvas>
<div class="names" id="names"></div>
<script>
const m4 = {
  perspective(fovy, aspect, near, far) {
    const f = 1 / Math.tan(fovy * Math.PI / 360), d = near - far;
    return [f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (far + near) / d, -1, 0, 0, 2 * far * near / d, 0];
  },
  lookAt(eye, target, up = [0, 1, 0]) {
    const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
    const unit = (v) => v.map((c) => c / Math.hypot(...v));
    const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
    const z = unit(eye.map((c, i) => c - target[i])), x = unit(cross(up, z)), y = cross(z, x);
    return [x[0], y[0], z[0], 0, x[1], y[1], z[1], 0, x[2], y[2], z[2], 0, -dot(x, eye), -dot(y, eye), -dot(z, eye), 1];
  },
  multiply(a, b) {
    const out = new Array(16).fill(0);
    for (let c = 0; c < 4; c++) for (let r = 0; r < 4; r++) for (let k = 0; k < 4; k++) out[c * 4 + r] += a[k * 4 + r] * b[c * 4 + k];
    return out;
  },
};
// Floorboards with a row of price labels: long thin detail that suffers most on a receding plane.
const tile = document.createElement('canvas');
tile.width = tile.height = 256;
const g = tile.getContext('2d');
g.fillStyle = '#b9905e'; g.fillRect(0, 0, 256, 256);
for (let i = 0; i < 8; i++) { g.fillStyle = i % 2 ? '#a57d4f' : '#c49c6a'; g.fillRect(i * 32, 0, 30, 256); }
g.fillStyle = '#2d1f14';
for (let i = 0; i < 8; i++) g.fillRect(i * 32 + 30, 0, 2, 256);
g.fillStyle = '#1f5f8b'; g.fillRect(0, 110, 256, 36);
g.fillStyle = '#fff'; g.font = 'bold 22px sans-serif'; g.textAlign = 'center';
g.fillText('BOOKNEST  $12.99', 128, 136);

const gl = document.getElementById('c').getContext('webgl2');
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, `#version 300 es
layout(location = 0) in vec3 aPosition; layout(location = 1) in vec2 aUV;
uniform mat4 uViewProjection; out vec2 vUV;
void main() { vUV = aUV; gl_Position = uViewProjection * vec4(aPosition, 1.0); }`], [gl.FRAGMENT_SHADER, `#version 300 es
precision mediump float; uniform sampler2D uFloor; in vec2 vUV; out vec4 c;
void main() { c = texture(uFloor, vUV); }`]]) {
  const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s);
  if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
  gl.attachShader(program, s);
}
gl.linkProgram(program); gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-3, 0, 1, 0, 0,  3, 0, 1, 3, 0,  3, 0, -60, 3, 30,
                                                 -3, 0, 1, 0, 0,  3, 0, -60, 3, 30,  -3, 0, -60, 0, 30]), gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 20, 0); gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 20, 12); gl.enableVertexAttribArray(1);
gl.bindTexture(gl.TEXTURE_2D, gl.createTexture());
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);                 // canvas rows run top to bottom
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE, tile);
gl.generateMipmap(gl.TEXTURE_2D);                         // anisotropy only affects mipmapped minification
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);

const ext = gl.getExtension('EXT_texture_filter_anisotropic');
const max = ext ? gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT) : 1;
const levels = [1, Math.min(4, max), max];
document.getElementById('names').innerHTML = levels.map((n, i) =>
  `<div>TEXTURE_MAX_ANISOTROPY_EXT ${n}${i === 2 ? ' (the maximum)' : ''}</div>`).join('');
gl.enable(gl.SCISSOR_TEST);
function frame(ms) {
  const x = Math.sin(ms * 0.0004) * 0.8;                  // a slow sideways drift shows the shimmer
  gl.uniformMatrix4fv(gl.getUniformLocation(program, 'uViewProjection'), false,
    m4.multiply(m4.perspective(55, 400 / 320, 0.05, 100), m4.lookAt([x, 0.35, 0.8], [x, 0.05, -3])));
  levels.forEach((n, pane) => {
    gl.viewport(pane * 400, 0, 400, 320); gl.scissor(pane * 400 + 3, 0, 394, 320);
    gl.clearColor(0.93, 0.91, 0.87, 1); gl.clear(gl.COLOR_BUFFER_BIT);
    if (ext) gl.texParameterf(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, n);   // per texture
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  });
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>