top of page
  • Foto do escritorYves "Jack" Albuquerque

Measuring Mipmaps in Runtime

If you are reading here, I'll consider that you know what a Mipmap is so let's skip that explanation and goes direct to what matters: How I can use mipmap to optimize texture size.


Most engines nowadays have a mipmap preview. Usually a colored render where each color represent a different level or how far the mipmap level is from the texture size. You can check more about how it works here .


That topic was already covered at GPU Gems 2, but here I show a different approach to that. As described into the mentioned article the mipmap level is actually influenced by different factors as: "screen resolution, antialiasing settings, texture-filtering options, anisotropy, clever driver optimizations, the distance to the textured polygons, and the orientations of the polygons".


There's a couple of ways to get the rough level of detail for all shader models but Shader Model 4 bring us the method CalculateLevelOfDetail described as a solution that "calculates the level of detail". And going a little bit further we can find the method CalculateLevelOfDetailUnclamped which is detailed as "This function works identically to CalculateLevelOfDetail (DirectX HLSL Texture Object), except that there is no clamping on the calculated LOD".


half4 frag(v2f i) : SV_Target
{
	float mip = _MipmapTex.CalculateLevelOfDetailUnclamped(sampler_MipmapTex,  i.uv);

	float4 color;
	if (mip > 0)
		color = float4(1, 0, 0, 1);
	if (mip < 0)
		color = float4(0, 1, 0, 1);
	if (mip == 0)
		color = float4(0, 0, 1, 1);

	return color;
}
Simple Geometries with color associated to mipmap level

The code above shows how this method can be useful to give a proper preview on how this can be useful not only to lossless downscale textures but also to upscale textures to get a better fit.


Supposing all our textures as POT (Power Of Two), we can start to get some nice gradients with mips going from 0 to texture size and from texture size to 8192. With a small math gymnastic we can get a result as the one below:


	half4 frag(v2f i) : SV_Target

	{

		float mip = _MipmapTex.CalculateLevelOfDetailUnclamped(sampler_MipmapTex,  i.uv);

		float textureSize = max(_MipmapTex_TexelSize.z, _MipmapTex_TexelSize.w);

		float mipTextureSize = _MipmapTex_TexelSize.z / pow(2, mip);

		float maxPossibleSize = 8192;

		float gradientBelowTextureSize = mipTextureSize / textureSize;

		float gradientOverTextureSize = (maxPossibleSize-mipTextureSize)/(maxPossibleSize - textureSize);

		float4 color;

		if (mip > 0)

			color = float4(gradientBelowTextureSize, 0, 0, 1);

		if (mip < 0)

			color = float4(0, gradientOverTextureSize, 0, 1);

		if (mip == 0)

			color = float4(0, 0, 1, 1);

		return color;

	}


Plane and geometries painted using rgb colors

Our result looks much more granular now but is not very useful. Let's simplify things and accumulate the max size in a structure that can get back by the CPU.



That result above is already good enough to detect the maximum texture size but we don't know if that value is only valid for a single pixel during a single frame or if it's useful during the entire gameplay. Also, if we get some more info we might also validate how useful is all mipmap grading usage for that texture is useful in general. In order to get some useful result we need to transform that info into an index. In our output, full white (1), related to texture size (mip level 0).




half4 frag(v2f i) : SV_Target

{

	float mip = _MipmapTex.CalculateLevelOfDetailUnclamped(sampler_MipmapTex,  i.uv);

	float textureSize = max(_MipmapTex_TexelSize.z, _MipmapTex_TexelSize.w);

	float indexOfTexture = log2(textureSize);

	float mipTextureSize = _MipmapTex_TexelSize.z / pow(2, mip);

	float index = log2(mipTextureSize);

	return index / indexOfTexture;

}


Same environment but in grayscale

Now that we have an Index, we can very easily convert that to size that we want. We will use a RWStructuredBuffer to store the max index for each texture and get it back on CPU and convert to texture.


Now that we have an Index, it's time to use that index. We will get the result of each pixel and store that into an array. Each field of this array represent a different texture size. Our array goes from 32- to 8192+ as the example below.


Array with 9 positions with pot from 32 to 8192

That data structure requires our index to be shifted in a way that 32- is represented by index 0 and 8192+ by index 8. An obvious caveat of our system is the limit of representation, in our case, we will use a uint so we can store up to 4294967295 samples. Initially, we will have one of those structures for each pixel. RWStructuredBuffer will be used so we can get values back to CPU when we want.


That result already works for us but it seems costly. We may improve that a little bit if we accept a smaller representation, let's say, one data structure per column instead of one per pixel.


That's it for today.


1 visualização0 comentário

Posts recentes

Ver tudo

Comments


bottom of page