如何在 GLSL 片段着色器中访问纹理的自动 Mipmap 等级?

18

在GLSL片段着色器中,如何确定采样纹理时使用了哪个mipmap级别?

我明白我可以使用textureLod(...)方法手动采样纹理的特定mipmap级别:

uniform sampler2D myTexture;

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

或者我可以使用texture(...)自动选择mipmap级别,例如

uniform sampler2D myTexture;

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

我更喜欢后者,因为我相信驱动程序对适当的mipmap级别的判断,比我自己做出的更加准确。

但我想知道在自动采样过程中使用了哪个mipmap级别,以帮助我合理地采样附近像素。在GLSL中是否有一种方法可以访问有关自动纹理采样使用了哪个mipmap级别的信息?


7
你的目标是哪个GLSL版本?GLSL 4.00支持textureQueryLod(...),可以完全满足你的需求。 - Andon M. Coleman
2个回答

34

以下是三种不同的方法来解决这个问题,具体取决于您可用的OpenGL功能:

  1. 正如Andon M. Coleman在评论中指出的那样,在OpenGL 4.00及以上版本中,解决方案很简单;只需使用textureQueryLod函数:

    #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. 在早期版本的OpenGL(2.0+?)中,您可能能够加载一个扩展来达到类似的效果。这种方法对我的情况有效。 注意:在扩展中,方法调用的大小写与内置的不同(queryTextureLodqueryTextureLOD)。

  3. #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);
    }
    
  4. 如果加载扩展程序不起作用,您可以使用 genpfault 贡献的方法估计自动详细级别:

  5. #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);
    }
    

无论如何,对于我的特定应用程序,我最终只是在主机端计算了mipmap级别,并将其传递给着色器,因为自动的细节级别并不完全符合我的需求。


如果可以的话,我有几个问题: 是否可以仅使用规范化UV纹理坐标中的dFdx和dFdy值来完成此操作? 为什么我需要dFdx和dFdy的结果是二维变量?(如果我只需要沿U或V坐标轴的导数/变化-普通浮点数是否足够?) 当将这些函数应用于规范化的UV纹理坐标时,我会得到什么样的值(值域)?-这些值是否类似于0、1、1/2、1/4、1/8......1/4096? - Nims
@Nims 标准化的UV坐标不足以提供纹理采样的精细程度信息,而这个信息对于选择最优Mipmap级别至关重要。dFdx和dFdy是二维的,因为纹理坐标是二维的。正如你所说,你只需要沿着“U或V”坐标轴的导数。因此它会给你两者。这回答了你的任何问题吗? - Christopher Bruns
谢谢您的回答,但我仍然不明白为什么导数有两个维度。例如,ddx 只是测量沿着 U 坐标的变化,沿着 V 坐标不会有任何变化,所以对于 dx_vtc 来说,第二个值将为零。否则,为什么同时存在 ddx 和 ddy 的意义是什么呢? - Nims
这里有4个不同的偏导数:du/dx,dv/dx,du/dy和dv/dy。除非纹理图像坐标恰好与显示屏坐标轴完全对齐,否则这四个值中没有一个需要为零。 - Christopher Bruns
我已经实现了建议的方法(在Unity3D中使用Cg)-计算mipmap级别的方法在接近表面时返回负数,这意味着在这种情况下delta_max_sqr小于1。 - Nims
我因此提议更改 mip_map_level 方法的返回值: 返回 max(0, 0.5 * log2(delta_max_sqr))。 - Nims

14

以下内容来自这里

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);
}

1
谢谢你找到这个。我看了一下规范,不幸的是它似乎在实际实现中留下了一些余地,所以这种方法可能不是精确使用的方法。此外,它没有考虑GL_TEXTURE_MIN_LOD/GL_TEXTURE_MAX_LOD设置。请注意,您片段中的texture_coordinate应该是未标准化的,即以像素为单位,而不仅仅是(0,1)。这对我来说是一个很好的开始,可以手动指定LOD。但我希望有一种方法可以访问内部自动LOD值。 - Christopher Bruns
@ChristopherBruns:你可以做一些非常俗套的事情:为每个mip级别填充一个虚拟纹理,其中包含不同的纯色/值,对其进行采样,并将颜色/值转换回mip级别 :) - genpfault
2
实际上,如果我们突然谈论GL 4.x,textureQueryLod(...)就是为了这个目的而创建的。像大多数隐式LOD纹理函数一样,它只能在片段着色器中工作,因为它需要屏幕空间梯度(偏导数)来计算适当的mipmap LOD。 - Andon M. Coleman
@AndonM.Coleman 哦,textureQueryLod() 看起来正是我想要的。我一直在使用 GLSL 3.30,但这可能会让我考虑 GLSL 4.0。或者至少让我尝试加载一个扩展。有人应该将其纳入到一个适当的答案中... - Christopher Bruns

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接