Texture LOD calculation (useful for atlasing)

I am writing a shader that does texture atlasing without edge filter issues - and I just though I would share. (emulating array textures on hardware that does not have array textures)

Cg code for mip level calculation:
(copied from http://linedef.com/personal/demos/?p=virtual-texturing)



#define SUB_TEXTURE_SIZE 512.0
#define SUB_TEXTURE_MIPCOUNT 10

float MipLevel( float2 uv )
{
  float2 dx = ddx( uv * SUB_TEXTURE_SIZE );
  float2 dy = ddy( uv * SUB_TEXTURE_SIZE );
  float d = max( dot( dx, dx ), dot( dy, dy ) );

  // Clamp the value to the max mip level counts
  const float rangeClamp = pow(2, (SUB_TEXTURE_MIPCOUNT - 1) * 2);
  d = clamp(d, 1.0, rangeClamp);
      
  float mipLevel = 0.5 * log2(d);
  mipLevel = floor(mipLevel);   
  
  return mipLevel;
}

Calculating half texel offset for selected mip level also



#define SUB_TEXTURE_SIZE 512.0
#define SUB_TEXTURE_MIPCOUNT 10

float MipLevel( float2 uv, out float a_halfOffset )
{
  float2 dx = ddx( uv * SUB_TEXTURE_SIZE );
  float2 dy = ddy( uv * SUB_TEXTURE_SIZE );
  float d = max( dot( dx, dx ), dot( dy, dy ) );
  
  // Clamp the value to the max mip level counts
  const float rangeClamp = pow(2, SUB_TEXTURE_MIPCOUNT - 1);
  d = clamp(sqrt(d), 1.0, rangeClamp);
      
  float mipLevel = log2(d);
  mipLevel = floor(mipLevel);   
  
  a_halfOffset = d * (1.0 / pow(2.0, SUB_TEXTURE_MIPCOUNT));
  
  return mipLevel;
}

You can then use tex2Dlod to lookup the texture (with adjusted UV’s to clamp the edge filtering - clamp to edge or mirror repeating only)

Edit:
Just realised that you can calculate the half pixel size by simply


  a_halfOffset = pow(2.0, mipLevel - SUB_TEXTURE_MIPCOUNT);

Thanks! Good stuff.

// Clamp the value to the max mip level counts

Just curious. Do you find that this yields any edge-on aliasing due to not having all the MIP levels around?

Do you implement blending between MIPs?

Hav you tried the brilinear cheat?

Have you implemented any aniso with manual sampling?

And how’s the perf compare stacking up compared to using ordinary MIP textures and GPU trilinear filtering?

Just curious. Do you find that this yields any edge-on aliasing due to not having all the MIP levels around?

Do you implement blending between MIPs?

I still generate mip maps. Eg. if the Top mip is a grid of 8x1 512x512 size textures, the next mip is a grid of 8x1 256x256 textures. All the way down to 8x1 grid of 1x1 textures. (and I clamp the mip level so that the 4x1, 2x1 and 1x1 texture sizes are not accessed)

Essentially, the hardware still does the filtering between the mip maps, I just clamp the texture coordinates so that there is no filtering between sub-texture edges.

Hav you tried the brilinear cheat?

I am not familiar with this. Can you explain?

Have you implemented any aniso with manual sampling?

No.

And how’s the perf compare stacking up compared to using ordinary MIP textures and GPU trilinear filtering?

I am not that happy with performance- it seems that it just performs on par with a generic 6 texture lookup and blend. (at least at the hardware level that does not support array textures)

I have noticed in my previous post that I floor the mip map level - this essentially removes tri-linear filtering.
Taking this out means that the filtering works, but the half pixel offset value is incorrect - as two mip levels are sampled in tri-linear filtering. Should probably just take the largest half offset of the size in this case.

Ah!, ok. When you said atlas, I presumed you meant non-MIPmapped texture for the atlas – something like iDs Virtual Texturing. And the floor() on the MIP level made it look like you weren’t doing any manual cross-MIP level filtering. Was envisioning this MIP level being used to offset the real texcoords to access MIPs of a particular level.

[quote]Have you tried the brilinear cheat?

I am not familiar with this. Can you explain?[/QUOTE]
You’re not doing your own texture filtering in the shader, so it’s not really applicable. But this is an ancient NVidia trick to save bandwidth when doing trilinear interpolation. Basically, instead of always sampling 2 MIPs and blending between them, you only sample 2 MIPs for like the middle 33% between two MIPmaps and do a fast blend into and out of that region. When you’re less than 33% away from a MIPmap, you just clamp to the result that MIPmap gives you and don’t even sample the other MIP. Results in lower texture filtering quality (blurring/aliasing), but saves bandwidth. See the pictures here:

This is the default on NVidia AFAIK, but you can override it IIRC by pushing “Image Settings” up to “High Quality” in nvidia-settings.

Thanks for the feedback.

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.