MIP level calculation
Task
Implement a fragment shader that samples a texture using a manually computed mip level based on screen-space derivatives. The goal is to reduce aliasing and shimmering artifacts on surfaces with varying scale and orientation.
iChannelSize0 provides the texture size in pixels.
Theory
In GLSL (and HLSL), the functions dFdx (ddx) and dFdy (ddy) return partial derivatives of a value with respect to screen-space x and y. When you sample texture without explicitly specifying a level of detail (LOD), the GPU automatically computes which mip level to use based on the rate of change of the texture coordinates (in pixels!) across screen pixels.
How It Works
1. For the current fragment, the GPU determines how much the texture coordinates change across the screen in pixels:
vec2 dx = dFdx(uv * TextureSizeInPixels);
vec2 dy = dFdy(uv * TextureSizeInPixels);
2. The GPU then calculates the maximum derivative length to estimate how stretched or compressed the texture is:
float deltaMax = max(length(dx), length(dy));
This deltaMax value reflects the sampling footprint (how much of the texture is being sampled) in texture space - the larger it is, the lower the needed resolution, and the higher the mip level.
3. Compute Base Mip Level using a base-2 logarithm:
float mip = log2(deltaMax);
This mip represents the level of detail (LOD). If the texture filtering mode is properly configured (e.g. LINEAR_MIPMAP_LINEAR), the GPU can blend between adjacent mip levels based on the fractional part of mip, resulting in smoother transitions and reduced aliasing.
Performance Tip
When computing the level of detail (LOD) based on texture coordinate derivatives, you often need the length of a vector - which involves a square root. However, thanks to a logarithmic identity:
$ \log_2(x) = \frac{1}{2} \cdot \log_2(x^2) $
you can avoid the square root by using the squared length instead, and then dividing the logarithm result by $2$.
This is especially useful in shaders, where sqrt() is relatively expensive, while squaring a value and applying log2() is cheaper and more GPU-friendly.