How to access automatic mipmap level in GLSL fragment shader texture?
Asked Answered
G

2

18

How do I determine what mipmap level was used when sampling a texture in a GLSL fragment shader?

I understand that I can manually sample a particular mipmap level of a texture using the textureLod(...) method:

uniform sampler2D myTexture;

void main()
{
    float mipmapLevel = 1;
    vec2 textureCoord = vec2(0.5, 0.5);
    gl_FragColor = textureLod(myTexture, textureCoord, mipmapLevel);
}

Or I could allow the mipmap level to be selected automatically using texture(...) like

uniform sampler2D myTexture;

void main()
{
    vec2 textureCoord = vec2(0.5, 0.5);
    gl_FragColor = texture(myTexture, textureCoord);
}

I prefer the latter, because I trust the driver's judgment about appropriate mipmap level more than I do my own.

But I'd like to know what mipmap level was used in the automatic sampling process, to help me rationally sample nearby pixels. Is there a way in GLSL to access the information about what mipmap level was used for an automatic texture sample?

Granville answered 24/6, 2014 at 13:47 Comment(1)
What version of GLSL are you targeting? GLSL 4.00 supports textureQueryLod (...), which does exactly what you want.Sociability
G
35

Below are three distinct approaches to this problem, depending on which OpenGL features are available to you:

  1. As pointed out by Andon M. Coleman in the comments, the solution in OpenGL version 4.00 and above is simple; just use the textureQueryLod function:

    #version 400
    
    uniform sampler2D myTexture;
    in vec2 textureCoord; // in normalized units
    out vec4 fragColor;
    
    void main()
    {
        float mipmapLevel = textureQueryLod(myTexture, textureCoord).x;
        fragColor = textureLod(myTexture, textureCoord, mipmapLevel);
    }
    
  2. In earlier versions of OpenGL (2.0+?), you might be able to load an extension, to similar effect. This approach worked for my case. NOTE: the method call is capitalized differently in the extension, vs. the built-in (queryTextureLod vs queryTextureLOD).

    #version 330
    
    #extension GL_ARB_texture_query_lod : enable
    
    uniform sampler2D myTexture;
    in vec2 textureCoord; // in normalized units
    out vec4 fragColor;
    
    void main()
    {
        float mipmapLevel = 3; // default in case extension is unavailable...
    #ifdef GL_ARB_texture_query_lod
        mipmapLevel = textureQueryLOD(myTexture, textureCoord).x; // NOTE CAPITALIZATION
    #endif
        fragColor = textureLod(myTexture, textureCoord, mipmapLevel);
    }
    
  3. If loading the extension does not work, you could estimate the automatic level of detail using the approach contributed by genpfault:

    #version 330
    
    uniform sampler2D myTexture;
    in vec2 textureCoord; // in normalized units
    out vec4 fragColor;
    
    // Does not take into account GL_TEXTURE_MIN_LOD/GL_TEXTURE_MAX_LOD/GL_TEXTURE_LOD_BIAS,
    // nor implementation-specific flexibility allowed by OpenGL spec
    float mip_map_level(in vec2 texture_coordinate) // in texel units
    {
        vec2  dx_vtc        = dFdx(texture_coordinate);
        vec2  dy_vtc        = dFdy(texture_coordinate);
        float delta_max_sqr = max(dot(dx_vtc, dx_vtc), dot(dy_vtc, dy_vtc));
        float mml = 0.5 * log2(delta_max_sqr);
        return max( 0, mml ); // Thanks @Nims
    }
    
    void main()
    {
        // convert normalized texture coordinates to texel units before calling mip_map_level
        float mipmapLevel = mip_map_level(textureCoord * textureSize(myTexture, 0));
        fragColor = textureLod(myTexture, textureCoord, mipmapLevel);
    }
    

In any case, for my particular application, I ended up just computing the mipmap level on the host side, and passing it to the shader, because the automatic level-of-detail turned out to be not exactly what I needed.

Granville answered 2/7, 2014 at 12:33 Comment(6)
A few questions if I may: Could this be done using dFdx and dFdy values from the normalized UV texture coordinates alone? Why do I need the results for dFdx and dFdy to be 2 dimension variables? (If all I need is the derivative/change along the U or V coordinate - wouldn't a regular float suffice?) What kind of values (Codomain) do I get from dFdx and dFdy when applying these functions to the normalized UV texture coordinates? - Would these be something like 0, 1, 1/2, 1/4 ,1/8......1/4096?Basal
@Basal The normalized UV coordinates are inadequate because the lack information about how finely sampled the texture is; information critical for selecting the optimal mipmap level. dFdx and dFdy are two dimensional because the texture coordinate is two dimensional. Like you said, all you need is the derivative along the "U OR V" coordinate. So it gives you both. Does this answer any of your questions?Granville
Thanks for the answer but I still don't get why the derivatives are 2 dimensions, say for example ddx is just measuring the change along the U coordinate, there would be no change along the V coordinate, so for dx_vtc - the second value would be zero. Otherwise what's the point in having both ddx and ddy?Basal
There are 4 distinct partial derivatives here: du/dx, dv/dx, du/dy, and dv/dy. None of those four values need be zero, unless the texture image coordinates happen to be perfectly aligned to the display screen axes.Granville
I have implemented the proposed method (in unity3d - using Cg) - the method for computing the mipmap level returns negative numbers when you are very close the surface, which mean that in such cases delta_max_sqr is smaller than 1.Basal
I there for propose this change to the return value of the mip_map_level method: return max( 0, 0.5 * log2(delta_max_sqr));Basal
P
14

From here:

take a look at the OpenGL 4.2 spec chapter 3.9.11 equation 3.21. The mip map level is calculated based on the lengths of the derivative vectors:

float mip_map_level(in vec2 texture_coordinate)
{
    vec2  dx_vtc        = dFdx(texture_coordinate);
    vec2  dy_vtc        = dFdy(texture_coordinate);
    float delta_max_sqr = max(dot(dx_vtc, dx_vtc), dot(dy_vtc, dy_vtc));
    return 0.5 * log2(delta_max_sqr);
}
Pinnacle answered 24/6, 2014 at 15:5 Comment(4)
Thanks for finding this. I looked at the spec, and sadly it seems to leave a bit of wiggle room in the actual implementation, so this method might not be precisely what's used. Also, it does not account for GL_TEXTURE_MIN_LOD/GL_TEXTURE_MAX_LOD settings. Note that the texture_coordinate in your snippet should be unnormalized, i.e. in pixels, not just (0,1). This is a good start for me to manually specify LOD myself. But I wish I had a way to access the internal automated LOD value.Granville
@ChristopherBruns: You could do something really cheesy: populate a dummy texture with different solid colors/values for each mip level, sample it, and convert the color/value back to the mip level :)Pinnacle
Actually, if we are talking about GL 4.x suddenly, textureQueryLod (...) was created for precisely this reason. Like most implicit LOD texture functions it only works in the fragment shader because it needs a screen-space gradient (partial derivative) to compute the appropriate mipmap LOD.Sociability
@AndonM.Coleman Oh yeah textureQueryLod() looks like exactly what I want. I've been using GLSL 3.30, but this might cause me to consider GLSL 4.0. Or at least cause me to attempt to load an extension. Someone should incorporate this into a proper answer...Granville

© 2022 - 2024 — McMap. All rights reserved.