使用mipmap streaming来优化GPU的纹理内存
Table of Contents
请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com
本文所涉及到的API和Unity3D编辑器都是基于6000.0.25f1c1版本
1 mipmap streaming 简介
使用 mipmap 流来限制 GPU 内存中纹理的大小。
1.1 mipmap streaming的工作原理
- 对于每个被摄像机观察到的纹理,Unity会自动计算并加载其特定级别的mipmap,而不是加载所有级别。这意味着 Unity 只会将每个纹理的指定mipmap level的内容从磁盘传输到 CPU 和 GPU。
- Unity 会以尽可能高的分辨率加载 mipmap level,但如果较高分辨率的 mipmap level不符合您设置的内存限制,则使用较低的 mipmap level。mipmap可预配置
- Unity 在 GPU 上缓存指定mipmap level的纹理内容,以避免重复加载。
1.2 mipmap streaming的限制
- Unity不支持对地形纹理使用mipmap streaming,因为 Unity 始终需要最高分辨率的 mipmap level。
- Unity不支持对纹理数组、cubemap,3D纹理使用mipmap streaming。
- 如果使用
Graphics.DrawMeshNow等 API来渲染纹理,Unity 将无法获得计算 mipmap level所需的信息。使用Texture2D.requestedMipmapLevelAPI 手动设置纹理的 mipmap level,或者禁用mipmap streaming。 - 如果纹理有tiling和offset属性,但又没指定使用_ST属性,Unity可能无法正确计算这种纹理的mipmap level。
2 启用 mipmap streaming
要启用 mipmap streaming,按照以下步骤操作:
- 点击【Edit|Project Settings】菜单项,弹出【Project Settings】面板,选中【Quality】项
- 在【Texture】部分中,启用【Mipmap Streaming】
在默认情况下,这时候为场景中的所有摄像机都打开了mipmap streaming。在编辑器中,mipmap streaming在edit mode或者play mode下都处于开启状态。
2.1 让texture和mipmap streaming一起工作
要让texture和mipmap streaming一起工作,需按以下步骤操作:
- 在项目窗口中选择一个纹理文件。
- 在【Texture Import Settings】窗口中,选择【Advanced】选项下的子选项:
- 确保【Advanced】选项中的【Generate Mipmap】子选项被勾选上
- 然后勾选【Advanced】选项中的【Streaming Mipmap Levels】子选项被
如果为 Android 构建项目,还必须遵循以下步骤:
- 打开【Build Profiles】窗口。
- 设置【Compression Method】为
LZ4或LZ4HC。
2.2 光照贴图
要对光照贴图启用 mipmap streaming,需要按照下列步骤操作:
- 点击【Edit|Project Settings】菜单项,弹出【Project Settings】面板,选中【Player】项
- 在【other settings】项中,启用【Ligtmap Streaming】项。
可以像上面介绍的设置普通纹理mipmap streaming的方式,去设置光照贴图的mipmap streaming,但当Unity重新生成光照贴图时,它们会重置为默认值。要解决这个问题,可以告诉Unity在生成光照贴图时应用这些值。即启用【Player】面板中的【Lightmap Setting】值,和指定【Streaming Priority】值。
2.3 自定义网格
如果在代码中创建自定义的mesh,Unity不会自动计算网格的UV密度(uv density)的话。这是因为Unity加载了错误的 mipmap level。为了防止这种情况,需要执行以下操作之一:
- 创建网格后,使用
Mesh.RecalculateUVDistributionMetrics函数,手动计算网格的UV密度。 - 将网格作为模型文件导入。
2.4 没有使用Renderer组件的Object
Unity 无法为以下不使用Renderer组件的Object计算正确的mipmap level,因为它们不使用标准Renderer组件:
- 贴花纹理(decal texture)
- 反射探针(reflection probe)纹理,因为它们使用 mipmap level来存储材质粗糙度(material roughness)的查找表。
- 精灵(sprite),因为它们使用的是精灵渲染器(sprite)
- 着色器使用
Mesh.uv(也称为 UV0)以外的通道中的 UV 纹理坐标。 - 除缩放和平移之外,改变了了纹理坐标的shader。
对于这些object,使用Texture2D.requestedMipmapLevel函数将手动设置 mipmap level。如果不这样做,Unity将使用低分辨率 mipmap level。
3 配置 mipmap streaming
3.1 设置哪些相机使用 mipmap 流
默认情况下,所有的摄像机在场景启用时都使用mipmap streaming
3.2 在单个摄像头上禁用 mipmap 流式传输
按以下步骤操作:
- 选择场景中的一台带有
Camera组件的GameObject - 在【inspector】面板中,给这GameObject增加
Streaming Controller组件 - 然后禁用掉
Streaming Controller组件
3.3 在单个摄像头上启用 mipmap 流式传输
按以下步骤操作:
- 点击【Edit|Project Settings】菜单项,弹出【Project Settings】面板,选中【Quality】项
- 在【Texture】部分中,启用【Mipmap Streaming】选项下,禁用【Add All Cameras】
- 给带有
Camera组件的GameObject增加Streaming Controller组件,并确保其已启用
3.4 设置相机使用的 mipmap level
使用Streaming Controller组件中的Mipmap Bias设置强制 Unity 加载比 mipmap 流自动选择的 mipmap level更小或更大的 mipmap level。
在从纹理到摄像机对应的mipmap level之上,Unity会把Streaming Controller组件指定的Mipmap Bias值添加上去。。例如,如果Mipmap Bias值设置为2,并且mipmap streaming为纹理选择的mipmap level为1,则Unity加载的mipmap level为3(1 + 2)。
还可以使用StreamingController.streamingMipmapBias API 来控制此Mipmap Bias值。
3.5 设置纹理的内存预算(memory budget)
要设置Unity用于纹理的最大GPU内存,请按照以下步骤操作:
- 点击【Edit|Project Settings】菜单项,弹出【Project Settings】面板,选中【Quality】项
- 在【Texture】部分中,以 MB 为单位设置【Memory Budget】项,即指定内存预算值。
内存预算适用于所有带mipmap的纹理,而不管这些纹理是否启用了mipmap steaming。例如,如果将内存预算设置为 100 MB,并且有 90 MB 的纹理不使用 mipmap streaming,则 mipmap 流的内存预算为 10 MB。
为了避免超出内存预算,Unity 会执行以下操作:
- 为纹理加载较低分辨率的 mipmap level。这可能会导致纹理弹出或加载缓慢。
- 从 GPU 内存中删除未使用的 mipmap level,为所需的 mipmap level腾出空间。
可以使用【Max Level Reduction】属性来阻止 Unity 删除小于特定级别的 mipmap level。此值也是 Unity 在首次启动时加载的 mipmap level。例如,如果您将【Max Level Reduction】设置为 2,则 Unity 仅加载级别 2 及以上的 mipmap level,并将它们保留在内存中。
Unity 必须将 mipmap level保持在 GPU 内存中的最大级别减少值以上。这可能意味着 Unity 超出了内存预算。
3.6 估计内存预算
要估算项目的内存预算,按照以下步骤操作:
- 在项目运行时,使用
Texture.desiredTextureMemory函数获取 Unity 加载的纹理的总大小。 - 将内存预算设置为略高于该值。
这样可以确保 Unity 有足够的纹理内存可用于场景中资源最密集的区域,并防止纹理分辨率降低。如果有额外的可用内存,则可以设置更大的内存预算,以便 Unity 可以将场景中不可见的纹理数据保存在 GPU 缓存中。注意:如果在编辑器中使用Texture.desiredTextureMemory,总大小可能包括 Unity 用于渲染编辑器窗口的纹理。
3.7 在运行时设置内存预算
可以使用以下 API 来设置和控制运行时的内存预算:
QualitySettings.streamingMipmapsActiveTexture2D.streamingTextureDiscardUnusedMips
4 重设纹理的 mipmap level
可以使用以下方法来让Unity重设纹理的mipmap level:
- 设置纹理的【Texture Import Settings】窗口中的【Property】属性(需先开启【Streaming Mipmap Levels】属性)
- 调用
Texture2D.requestedMipmapLevel方法 。
4.1 使用 Priority 属性
请按以下步骤操作:
- 在项目窗口中选中纹理。
- 设置【Property】属性在-128和127之间。
当 Unity 需要降低 mipmap level以满足内存限制时,它会按优先级从低到高的顺序考虑纹理,直到达到限制。这意味着优先级值较高的纹理更有可能保留其更高分辨率的 mipmap level。
Unity 会移除优先级较低的纹理的 mipmap level。例如,如果您将一个纹理的优先级设置为1,将另一个纹理的优先级设置为5,则 Unity 可能会在考虑第二个纹理之前移除第一个纹理的四个mipmap level。
还可以使用以下 API 来设置优先级值:
TextureImporter.streamingMipmapsPriorityTexture2D.streamingMipmapsPriorityIHVImageFormatImporter.streamingMipmapsPriority
4.2 使用 API
使用以下 API:
Texture2D.requestedMipmapLevel请求 Unity 重设纹理的 mipmap level。Texture2D.IsRequestedMipmapLevelLoaded检查 Unity 是否加载您请求的 mipmap level。Texture2D.ClearRequestedMipmapLevel。不再重设 mipmap level,
您可以使用Mesh.GetUVDistributionMetricAPI 估算网格的UV density 。这可以帮助您根据位置计算所需的 mipmap level相机 . 有关示例代码,请参阅Mesh.GetUVDistributionMetric。
4.3 重设所有纹理的 mipmap level
使用Texture.streamingTextureForceLoadAll加载所有纹理的所有 mipmap level。
5 预加载 mipmap level
如果启用摄像机。在运行时,mipmap steaming需要时间将mipmap level流式传输到内存(stream the mipmap levels into memory)中。
可以使用纹理预加载(texture preloading)来防止这种情况。执行以下操作:
- 为已禁用的
Camera组件的添加Streaming Controller组件。 - 调用
StreamingController.SetPreloading方法来预加载 mipmap level。
使用StreamingController.CancelPreloading方法取消预加载。
启用预加载后,可以使用以下 API:
StreamingController.IsPreloading检查相机是否正在预加载。Texture.streamingTextureLoadingCount和Texture.streamingTexturePendingLoadCount用于检查 Unity 仍在加载多少个 mipmap level的纹理。
如果这些 API 返回的值表明 Unity 已完成预加载,您可能需要等待几帧才能启用摄像机,以确保预加载已完成。
6 分析 mipmap 流式传输
以6000.0.25f1c1版本的Unity编辑器为例,编辑器中的【Scene】视图窗口中,有一个【Debug Draw Mode】可以可视化和调试场景中的mipmap streaming。要启用该模式,请按以下步骤操作:
- 在【Scene】视图窗口顶上的按钮,选择【Debug Draw Mode】。(就是那个虫子按钮)
- 在下拉列表中选择【Texture Mipmap Streaming】选项
注意上面的操作仅适用于使用Built-in渲染管线的情况
Debug Draw Mode下GameObject会以以下的几种颜色表征
- 如果纹理使用较少的 mipmap level,则显示为绿色。
- 如果纹理使用较少的 mipmap level,则显示红色,因为 mipmap 流没有足够的资源来加载所有级别。
- 如果纹理不使用 mipmap 流,或者没有渲染器计算 mipmap level,则为蓝色。
注意:如果使用MainTexture相关的函数设置主纹理,则该纹理将不会在Debug Draw Mode下显示。
6.1 在脚本中获取 mipmap 流信息
要获取有关纹理内存的信息,请使用以下属性:
Texture.currentTextureMemoryTexture.desiredTextureMemoryTexture.totalTextureMemoryTexture.targetTextureMemoryTexture.nonStreamingTextureMemory
要获取有关 mipmap streaming影响的纹理或渲染器数量的信息,请使用以下属性:
Texture.streamingMipmapUploadCountTexture.nonStreamingTextureCountTexture.streamingTextureCountTexture.streamingRendererCount
要获取有关纹理的 mipmap level的信息,请使用以下属性:
Texture2D.desiredMipmapLevelTexture2D.loadingMipmapLevelTexture2D.loadedMipmapLevel