使用mipmap streaming来优化GPU的纹理内存

Table of Contents

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

本文所涉及到的API和Unity3D编辑器都是基于6000.0.25f1c1版本

1 mipmap streaming 简介

使用 mipmap 流来限制 GPU 内存中纹理的大小。

1.1 mipmap streaming的工作原理

  1. 对于每个被摄像机观察到的纹理,Unity会自动计算并加载其特定级别的mipmap,而不是加载所有级别。这意味着 Unity 只会将每个纹理的指定mipmap level的内容从磁盘传输到 CPU 和 GPU。
  2. Unity 会以尽可能高的分辨率加载 mipmap level,但如果较高分辨率的 mipmap level不符合您设置的内存限制,则使用较低的 mipmap level。mipmap可预配置
  3. Unity 在 GPU 上缓存指定mipmap level的纹理内容,以避免重复加载。

1.2 mipmap streaming的限制

  1. Unity不支持对地形纹理使用mipmap streaming,因为 Unity 始终需要最高分辨率的 mipmap level。
  2. Unity不支持对纹理数组cubemap3D纹理使用mipmap streaming。
  3. 如果使用Graphics.DrawMeshNow等 API来渲染纹理,Unity 将无法获得计算 mipmap level所需的信息。使用Texture2D.requestedMipmapLevel API 手动设置纹理的 mipmap level,或者禁用mipmap streaming。
  4. 如果纹理有tiling和offset属性,但又没指定使用_ST属性,Unity可能无法正确计算这种纹理的mipmap level。



2 启用 mipmap streaming

要启用 mipmap streaming,按照以下步骤操作:

  1. 点击【Edit|Project Settings】菜单项,弹出【Project Settings】面板,选中【Quality】项
  2. 在【Texture】部分中,启用【Mipmap Streaming

在默认情况下,这时候为场景中的所有摄像机都打开了mipmap streaming。在编辑器中,mipmap streaming在edit mode或者play mode下都处于开启状态。

2.1 让texture和mipmap streaming一起工作

要让texture和mipmap streaming一起工作,需按以下步骤操作:

  1. 在项目窗口中选择一个纹理文件。
  2. 在【Texture Import Settings】窗口中,选择【Advanced】选项下的子选项:
  3. 确保【Advanced】选项中的【Generate Mipmap】子选项被勾选上
  4. 然后勾选【Advanced】选项中的【Streaming Mipmap Levels】子选项被

如果为 Android 构建项目,还必须遵循以下步骤:

  1. 打开【Build Profiles】窗口。
  2. 设置【Compression Method】为LZ4LZ4HC

2.2 光照贴图

要对光照贴图启用 mipmap streaming,需要按照下列步骤操作:

  1. 点击【Edit|Project Settings】菜单项,弹出【Project Settings】面板,选中【Player】项
  2. 在【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 流式传输

按以下步骤操作:

  1. 选择场景中的一台带有Camera组件的GameObject
  2. 在【inspector】面板中,给这GameObject增加Streaming Controller组件
  3. 然后禁用掉Streaming Controller组件

3.3 在单个摄像头上启用 mipmap 流式传输

按以下步骤操作:

  1. 点击【Edit|Project Settings】菜单项,弹出【Project Settings】面板,选中【Quality】项
  2. 在【Texture】部分中,启用【Mipmap Streaming】选项下,禁用【Add All Cameras
  3. 给带有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内存,请按照以下步骤操作:

  1. 点击【Edit|Project Settings】菜单项,弹出【Project Settings】面板,选中【Quality】项
  2. 在【Texture】部分中,以 MB 为单位设置【Memory Budget】项,即指定内存预算值。

内存预算适用于所有带mipmap的纹理,而不管这些纹理是否启用了mipmap steaming。例如,如果将内存预算设置为 100 MB,并且有 90 MB 的纹理不使用 mipmap streaming,则 mipmap 流的内存预算为 10 MB。

为了避免超出内存预算,Unity 会执行以下操作:

  1. 为纹理加载较低分辨率的 mipmap level。这可能会导致纹理弹出或加载缓慢。
  2. 从 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 估计内存预算

要估算项目的内存预算,按照以下步骤操作:

  1. 在项目运行时,使用Texture.desiredTextureMemory函数获取 Unity 加载的纹理的总大小。
  2. 将内存预算设置为略高于该值。

这样可以确保 Unity 有足够的纹理内存可用于场景中资源最密集的区域,并防止纹理分辨率降低。如果有额外的可用内存,则可以设置更大的内存预算,以便 Unity 可以将场景中不可见的纹理数据保存在 GPU 缓存中。注意:如果在编辑器中使用Texture.desiredTextureMemory,总大小可能包括 Unity 用于渲染编辑器窗口的纹理

3.7 在运行时设置内存预算

可以使用以下 API 来设置和控制运行时的内存预算:

  1. QualitySettings.streamingMipmapsActive
  2. Texture2D.streamingTextureDiscardUnusedMips



4 重设纹理的 mipmap level

可以使用以下方法来让Unity重设纹理的mipmap level:

  1. 设置纹理的【Texture Import Settings】窗口中的【Property】属性(需先开启【Streaming Mipmap Levels】属性)
  2. 调用Texture2D.requestedMipmapLevel方法 。

4.1 使用 Priority 属性

请按以下步骤操作:

  1. 在项目窗口中选中纹理。
  2. 设置【Property】属性在-128和127之间。

当 Unity 需要降低 mipmap level以满足内存限制时,它会按优先级从低到高的顺序考虑纹理,直到达到限制。这意味着优先级值较高的纹理更有可能保留其更高分辨率的 mipmap level。

Unity 会移除优先级较低的纹理的 mipmap level。例如,如果您将一个纹理的优先级设置为1,将另一个纹理的优先级设置为5,则 Unity 可能会在考虑第二个纹理之前移除第一个纹理的四个mipmap level。

还可以使用以下 API 来设置优先级值:

  1. TextureImporter.streamingMipmapsPriority
  2. Texture2D.streamingMipmapsPriority
  3. IHVImageFormatImporter.streamingMipmapsPriority

4.2 使用 API

使用以下 API:

  1. Texture2D.requestedMipmapLevel 请求 Unity 重设纹理的 mipmap level。
  2. Texture2D.IsRequestedMipmapLevelLoaded 检查 Unity 是否加载您请求的 mipmap level。
  3. 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)来防止这种情况。执行以下操作:

  1. 为已禁用的Camera组件的添加Streaming Controller组件。
  2. 调用StreamingController.SetPreloading方法来预加载 mipmap level。

使用StreamingController.CancelPreloading方法取消预加载。

启用预加载后,可以使用以下 API:

  • StreamingController.IsPreloading 检查相机是否正在预加载。
  • Texture.streamingTextureLoadingCountTexture.streamingTexturePendingLoadCount用于检查 Unity 仍在加载多少个 mipmap level的纹理。

如果这些 API 返回的值表明 Unity 已完成预加载,您可能需要等待几帧才能启用摄像机,以确保预加载已完成。




6 分析 mipmap 流式传输

6000.0.25f1c1版本的Unity编辑器为例,编辑器中的【Scene】视图窗口中,有一个【Debug Draw Mode】可以可视化和调试场景中的mipmap streaming。要启用该模式,请按以下步骤操作:

  1. 在【Scene】视图窗口顶上的按钮,选择【Debug Draw Mode】。(就是那个虫子按钮)
  2. 在下拉列表中选择【Texture Mipmap Streaming】选项

注意上面的操作仅适用于使用Built-in渲染管线的情况

Debug Draw Mode下GameObject会以以下的几种颜色表征

  1. 如果纹理使用较少的 mipmap level,则显示为绿色。
  2. 如果纹理使用较少的 mipmap level,则显示红色,因为 mipmap 流没有足够的资源来加载所有级别。
  3. 如果纹理不使用 mipmap 流,或者没有渲染器计算 mipmap level,则为蓝色。

注意:如果使用MainTexture相关的函数设置主纹理,则该纹理将不会在Debug Draw Mode下显示。

6.1 在脚本中获取 mipmap 流信息

要获取有关纹理内存的信息,请使用以下属性:

  • Texture.currentTextureMemory
  • Texture.desiredTextureMemory
  • Texture.totalTextureMemory
  • Texture.targetTextureMemory
  • Texture.nonStreamingTextureMemory

要获取有关 mipmap streaming影响的纹理或渲染器数量的信息,请使用以下属性:

  • Texture.streamingMipmapUploadCount
  • Texture.nonStreamingTextureCount
  • Texture.streamingTextureCount
  • Texture.streamingRendererCount

要获取有关纹理的 mipmap level的信息,请使用以下属性:

  • Texture2D.desiredMipmapLevel
  • Texture2D.loadingMipmapLevel
  • Texture2D.loadedMipmapLevel
kumakoko avatar
kumakoko
pure coder
comments powered by Disqus