Unity3D mipmap

转载自:catlike coding和雨松momo

Mipmap和过滤


当纹理的像素(texels)与投影到的像素不完全匹配的时候,会发生什么?这会造成一定的不匹配,这必须以某种方式解决。 这就是通过过滤模式索要控制的内容。


最直接的过滤模式是点模式(无滤波器)。这意味着在对纹理进行采样的时候,某些UV坐标会使用离它最近的纹理。这将给纹理块状的外观,除非纹理的像素恰好映射到显示像素。 因此,它通常用于像素的完美渲染,或者当需要块状样式的时候。


默认是使用双线性滤波。当纹理在两个纹理像素之间的某处被采样的时候,这两个纹理像素会被进行内插值。由于纹理是二维的,这会沿着U和V轴发生内插值。 因此,双线性滤波不只是线性滤波。


当纹理像素的密度小于显示像素的密度时,这个方法会起作用,因此当你放大纹理的时候,结果会看起来模糊。在相反的情况下这个方法不会起作用,比如说当你缩小的纹理的时候。 相邻的显示像素中的空间将多于一个纹理像素。这意味着纹理的一部分将被跳过,这将导致效果不好的转换,就好像图像被锐化一样。


这个问题的解决方案是每当纹理像素的密度变得太高的时候会使用较小的纹理。 显示屏上显示的纹理越小,那么应该使用的版本就越小。这些较小的版本称为mipmaps,并且会自动为你生成。每个连续的mipmap具有上一级mipmap的宽度和高度的一半。因此,当原始的纹理大小为512x512的时候,mip映射依次是256x256,128x128,64x64,32x32,16x16,8x8,4x4和2x2。

如果你喜欢的话,你可以禁用mipmap。首先,将纹理类型设置为高级。然后你可以禁用Mipmap并进行更改。 看到差异的一个好方法是使用像四边形的平面对象,并从一个固定的角度去看它。
 

在有mipmap和没有mipmap时候的情况。


 那么在哪里使用哪个mipmap级别,它们看起来有多不同?通过在高级纹理设置中启用Fadeout Mip贴图,我们可以使过渡可见。在启用Fadeout Mip贴图的时候,渐变范围滑块将显示在检查器中。它定义了一个mipmap范围,mipmap映射范围将过渡到纯灰色。通过使这个过渡单步展现,你会得到一个尖锐的过渡一直到灰色。进一步将单步范围向右移动,后面的转换将会发生。

 

对mipmap的高级设置。


褪色到灰色的用途是什么?

 

要获得此效果的良好视图,现在将纹理的Aniso等级l设置为0。
 

连续的mipmap级别。


一旦你知道了各种mipmap的级别,你应该能够看到他们之间纹理质量的突然变化。 随着纹理投影的变小,纹理像素的密度会增加,这使得它看起来更清晰。 直到突然下一个mipmap等级切换,它就又变得模糊了。


所以在没有mipmap的情况下,你看到的视觉效果是从模糊到锐利,再到太尖锐。而在有mipmap的情况下,你看到的视觉效果是从模糊到尖锐,再到突然再次模糊,然后尖锐,再次突然模糊,等等。


那些模糊锐利是双线性滤波的特征。你可以通过将过滤器模式切换到三线性滤波来消除那些模糊锐利。这与双线性过滤的原理相同,但它也在相邻的mipmap级别之间内插。因此得名三线性滤波。这使得采样更昂贵,但它平滑了mipmap级别之间的过渡。
 

正常和灰色mipmap之间的三线性滤波。


另一个有用的技术是各向异性过滤。你可能已经注意到,当你设置为0的时候,纹理变得模糊。这与mipmap级别的选择有关。

当纹理以一个角度进行投射时,由于透视,你最终得到的结果是它的一个维度变形比另一个维度变形更大。一个很好的例子是纹理的地平面。在一定距离处,纹理的前-后尺寸将显得比左-右尺寸小得多。

而选择哪个mipmap级别是基于最差维度进行选择的。如果差异很大的话,那么你将得到在一个维度上非常模糊的结果。各向异性过滤通过对尺寸的去耦合来减轻这种影响。除了均匀地缩小纹理外,它还提供在任一维度上缩放不同比例的版本。 所以你不只是有一个大小为256x256的mipmap,也有大小为256x128、256x64的mipmap等等。

 

不使用各向异性过滤和使用各向异性过滤的对比。


注意,那些额外的mipmap不像常规的mipmap那样是预先生成的。相反,它们通过执行额外的纹理采样来进行模拟。所以他们不需要更多的空间,但是采样变得更昂贵了。

 

各向异性双线性滤波,逐步过滤为灰色。


各向异性过滤的深度是由Aniso的等级进行的控制。在Aniso的等级为0的时候,代表着禁用各向异性过滤。 在Aniso的等级为1的时候,启用各向异性过滤并提供最小的效果。在Aniso的等级为16的时候,各向异性过滤处于其最大值。但是,这些设置受项目质量设置的影响。

 

你可以通过Edit / Project Settings / Quality来访问质量设置。你将在“渲染”部分中找到各向异性纹理的设置。
 

渲染质量设置。


当禁用各向异性纹理的时候,就不会发生各向异性过滤,无论纹理的设置如何。 当它设置为”Per Texture”的时候,各向异性过滤是否起起用完全由每个单独的纹理控制。它也可以设置为强制开启,这将使得每个纹理的Aniso 等级至少设置为9。但是,Aniso 等级设置为0的纹理仍然不会使用各向异性过滤。

 

 

 

GPU发热的元凶之一“带宽” 所以通常我们都会打开mipmap,如下图所示,在unity中可以拖动右上角的条来查看贴图每个等级的mipmap贴图。那么它的等级一共分成0级-9级, 0级表示最清楚,9级表示最模糊。 贴图分辨率是依次减半,如512 256 128 64 32 16 8 4 2 0

 

所以说如果带了mipmap的贴图在内存上就会多占33%左右,然而显存和纹理带宽就不一定了,如果摄像机离得比较远,就会采用更小的贴图渲染,贴图所占显存小了,那么对应纹理带宽也一并减小,从而性能就优化了。

也有团队为了减少内存,从而关闭了mipmap。这就会导致另一个问题,美术的贴图非常精细,在UI中近距离观察效果很好,可是一旦摄像机拉远,就会出现“噪点”很难看。 所以mipmap一定要打开。

总体来说mipmap是用内存换纹理带宽的一种优化方式。可是内存毕竟涨了33%,所以unity2018.2中提供了streaming流加载贴图mipmap,意思就是用到哪一级mipmap只加载这一级的,其他级的mipmap不加载。这将大量减少内存开销,如下图所示,unity提供了一个demo例子,内存大概减少25%-30%。

 

到这里大家应该都能很好理解流加载,其实本篇文章我想说的是利用流加载的特性来进一步优化纹理带宽。例如,

1.unity原本默认计算出需要加载第0级的mipmap贴图,我们能不能手动设置成别的级别mimap

2.比如打开非全屏界面,能不能强制将游戏里面所有3D物体的mipmap都降到最低

答案是可行的,有了这两个特性,我们就可以自己灵活控制mipmap按需加载了。

首先我们需要在unity中开启texture streaming,如下图所示,在Quality Setting面板中勾选Texture Streaming,请注意Max Level Reduction这个数值比较重要,它的意思就是mipmap最多可以减小几级,最大是7.默认是2,如果是2的话,那么代码里怎么设置最多只能减少2级.

 

Max Level Reduction代码中也可以动态修改。

1

2

        //mipmap最多可以减少的等级, 默认是2 ,也可以在QualitySettings面板中设置

        QualitySettings.streamingMipmapsMaxLevelReduction = 7;

还需要在Editor Settings中勾选 Enable Texture Streaming,我的测试中即使勾选在编辑器下也未必能有正确的结果,所以大家一定要打出包在真机中查看。

 

如下代码所示,首先实例化一个Prefab,并且调用requestedmipmaplevel来设置默认加载mipmap的等级。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

    void Start()

    {

        mr = Instantiate<GameObject>(prefab).GetComponent<MeshRenderer>();

        t1 = mr.material.mainTexture as Texture2D;

        t1.requestedMipmapLevel = 5; //加载第5等级mipmap

        //t1.ClearRequestedMipmapLevel();可以清空加载mipmap的等级

        

    }

    void OnGUI()

    {

        GUILayout.Label("<size=50>desiredMipmapLevel" + t1.desiredMipmapLevel + "</size>");

        GUILayout.Label("<size=50>calculatedMipmapLevel" + t1.calculatedMipmapLevel + "</size>");

        GUILayout.Label("<size=50>loadedMipmapLevel" + t1.loadedMipmapLevel + "</size>");

        GUILayout.Label("<size=50>loadingMipmapLevel" + t1.loadingMipmapLevel + "</size>");

        GUILayout.Label("<size=50>minimumMipmapLevel" + t1.minimumMipmapLevel + "</size>");

        GUILayout.Label("<size=50>mipMapBias" + t1.mipMapBias + "</size>");

    }

如下图所示,我们可以看到贴图已经被加载到第5级的mipmap了。此时你可能会有个疑问,如果摄像机拉远拉近是否会影响mipmap,答案是肯定的,只是摄像机拉到最近mipmap也只能5,但是摄像机如果拉远超过了5,那么就会自动适应6 、7级。

 

这里有几个数值的含义我需要解释一下,

calculatedMipmapLevel:表示当前摄像机与物体的距离计算出需要加载的mipmap等级。

desiredMipmapLevel:表示当前实际加载的mipmap等级,因为我们可能强制设置了加载等级,那么实际加载的可能就和calculatedMipmapLevel不一致了。

mipMapBias:表示加载加载mipmap等级的偏移量,比如原本加载的是0级的mipmap,由于mipMapBias设置了2,所以实际加载的就是2级,这个数值也可以设置成负数,比如原本需要加载2级mipmap,由于设置了-1,所以实际加载1级的mipmap,这样无疑就更浪费性能。

下面我们在代码里动态修改mipmap的等级。

1

2

3

4

5

6

7

8

        if (GUILayout.Button("<size=80>调整mipmap等级 +</size>"))

        {

            t1.mipMapBias += 1f; //更模糊

        }

        if (GUILayout.Button("<size=80>调整mipmap等级 -</size>"))

        {

            t1.mipMapBias -= 1f; //更清楚

        }

unity还提供了streaming controller组件, 需要绑定在摄像机上,其实就是对摄像机所看到的所有物体的mipmap做偏移和上面的用法类似。

 

我们既然能修改单个物体的贴图的mipmap能否统一修改所有呢?比如现在打开了一个半屏界面,可以将背景中的3D物体的贴图mipmap全部降低。因为UI并不会开mipmap所以即使降低也不会受影响。

1

2

3

4

5

6

7

8

9

        if (GUILayout.Button("<size=80>整体减低采样 -</size>"))

        {

            //0表示正常尺寸

            //1表示降低1/2

            //2表示降低1/4

            //3表示降低1/8

            //4表示降低1/16

            QualitySettings.masterTextureLimit = 4;

        }

在切回0级mipmap