-
Notifications
You must be signed in to change notification settings - Fork 180
Level of detail emulation: lod per pixel calculation, tile selection and third axis interpolation factor computation.
The N64 hardware supports the use of textures whose level of detail decreases as the object moves away from the camera. The process consists of three parts.
The level of detail per pixel is calculated by evaluating the change in texture coordinates s
and t
per change in screen coordinates ,x
and y
. A standard formula would go lod = max(sqrt((ds/dx)^2 + (ds/dy)^2), sqrt((dt/dx)^2 + (dt/dy)^2))
, i.e., the maximum between the euclidean norms of the gradients of each texture coordinate. A computationally fast and good approximation would be using the L-infinity norm, or simply put, picking the maximum component of the gradient.
The current implementation goes like this, where vLodTexCoord
is a vector holding the texture coordinates and uScreenScale
is a scale factor so that non-native resolutions keep the original looks.
mediump vec2 dx = abs(dFdx(vLodTexCoord)) * uScreenScale;
mediump vec2 dy = abs(dFdy(vLodTexCoord)) * uScreenScale;
mediump float lod = max(max(dx.x, dx.y), max(dy.x, dy.y));
The lod just calculated roughly indicates how many texels correspond to each screen pixel. Since each mip-map is usually half the size of the previous texture, the base-two logarithm will show the desired tile index. However, this process reveals two difficulities: what to do if the logarithm is negative (magnification) or bigger than the existing tile indices? These can be avoided by clamping the lod between a minimum value of 1
and maximum value of 2^max_tile - eps
, where eps
represents the smallest possible value in fixed point format. When magnifying, it is possible to extrapolate using the two most detailed textures or use a detail texture. This cases will be discussed later.
mediump float min_lod = 1.0;
mediump float max_lod = pow(2.0, float(uMaxTile)) - 1.0 / 32.0;
mediump float lod_clamp = min(max(lod, min_lod), max_lod);
Finally, this logarithm is split into its integral and fractional parts. A high-level approach could simply go,
mediump float lod_log = log2(lod_clamp);
mediump int lod_tile = int(lod_log);
mediump float lod_frac = fract(lod_log);
While GLSL offers functions like log2()
, the N64 was unable to compute the this logarithm. Its integral part posed no issues as it means checking the position in which the most significant bit of the value is, so the value above can be used. The fractional part, on the other hand, had to be approximated.
When minifying (lod>=1
), the fractional part is linearly interpolated between the lower and upper powers of two.
lod_frac = (lod - 2^lod_tile) / (2^(lod_tile + 1) - 2^(lod_tile))
Using properties of powers, this expression can be simplified into,
lod_frac = (lod - 2^lod_tile) / (2^lod_tile) = lod / 2^(lod_tile) - 1
So far, we avoided magnification by forcing lod
to be greater than 1
. However the N64 offers two alternatives.
The first alternative consists of using lod_tile=0
but allowing lod_frac
to go to negative values when lod<1.0
. The approximation used is
lod_frac = lod - 1
which maps an lod
near 1 to lod_frac
near 0 and an lod
near 0 to an lod_frac
near -1. The initial lod
value is clamped to a minimum value to avoid excessive extrapolation.
The second alternative consists of using a special texture. As a result, all other texture levels have their index increased by one. The approximation used is as above, but is increased by one unit because the most detailed texture is at index 1.
lod_frac = lod
This maps an lod
near 1 to lod_frac
near 1 and an lod
near 0 to an lod_frac
near 0. The initial lod
value is also clamped to a minimum value to avoid excessive use of the detail texture in the mixture.
Normally, if sharpen and detail modes are disabled, it would be impossible to enter into magnification territory. However, if a texture has only one level, the level of detail will be clamped to 1-eps
.
lod = min(max(lod, lod_min), lod_max) = min(max(lod, 1), 1-eps) = 1-eps
Therefore, magnification can also occur in this case, so it must be considered.
The minimum level of detail can be modified as,
mediump float min_lod = uTextureDetail != 0 ? uMinLod : 1.0;
The steps mentioned above can be summed up in,
lowp int lod_tile = clamp(int(log2(lod_clamp)), 0 , max_tile);
lowp int tile0 = 0;
lowp int tile1 = 1;
if (uEnableLod != 0) {
if (lod_clamp < 1.0 && uTextureDetail == 0) {
tile0 = 0;
tile1 = 0;
} else if (lod_clamp >= 1.0 && uTextureDetail == 2) {
tile0 = lod_tile + 1;
tile1 = lod_tile + 2;
} else {
tile0 = lod_tile;
tile1 = lod_tile + 1;
}
}
mediump float lod_frac = lod_clamp / pow(2.0, float(lod_tile));
if (uTextureDetail == 1 || lod_clamp >= 1.0) {
lod_frac = clamp(lod_frac - 1.0, -1.0, 1.0);
}
At last, this values are sent to the texture engine and color combiner to fetch texels from the appropiate level and perform the third axis interpolation respectively.