UE4:关于Texture Streaming的一部分总结

前言

Leader告诉我可以看下Texture Streaming相关,这里总结一下下

日常费解.jpg

UE4的Texture Streaming

纹理流送系统或流送器是引擎的一部分,负责增大和减小每个纹理的分辨率。该系统使您可以拥有良好的视觉质量, 同时有效地管理可用内存。(来自UE4官方文档机翻)
看完这句话我的脑子里冒出来的问题大概有:

  1. 为什么要有流送系统?
  2. 它流送的是啥?
  3. 流送系统怎么管理内存了?
  4. 流送系统如何影响画质了?

大概是这样的:

依然是LOD的思想,在绘制远距离物体的时候,最终在屏幕上该物体占据的像素数量极小的情况下,如果依然使用较大的贴图(比如在屏幕上占了3232像素但是用了一个10241024大小的贴图),采样会产生锯齿或者摩尔纹,显示质量是有问题的,而且显然很浪费显存。再考虑到硬件算理有限,不能当场进行卷积来消除这些画面问题,最好的办法就是在这种情况下直接用小点的接近屏幕上显示大小的贴图。

于是多级渐进纹理Mipmap出现了,这样可以在远处的时候直接用小尺寸贴图,解决掉画面的锯齿之类的问题,但是他消耗的显存就更大了,多出来几份Mips占用了更多的显存。

解决了一个问题使显存问题稍微严重了一点,那么再去想办法解决内存的问题,这种过程算是开发的常见情况了……- -

为了解决内存占用过大的问题,UE4这里做了一个纹理流送系统,通过一些计算获取当前最合适的Mips,将其加载,可以节约很多内存

这样基本就解决了两个问题,只不多最后是多带来了一点点的CPU开销罢了……

其余的相关概述可以看官方文档

比较需要注意的是,小于等于Mip7也就是64*64的Mip是会常驻内存的

Stat Streaming

UE4提供了一个关于Streaming的控制台调试命令,可以分析内存使用状况

官网偷的图

按照官方的解释,Pool指的是理想情况预计使用的内存,并不是当前实际占用的内存,Mips则是当前已经占用的内存

逐个解释一下图上的Memory Counters参数:

Safety Pool

这个值是在Engine的配置文件中指定的,比如BaseEngine.ini,属于TextureStreaming模块,是一个预留内存,主要是用在AsyncTextureStreaming计算可分配内存

Temporary Pool

这个值可以通过r.Streaming.MaxTempMemoryAllowed控制,PrepareAsyncTask中会使用这个值,在处理所有纹理时让纹理都处于其所需的Mips

Streaming Pool

流送池大小,在移动端是PoolSize - Safety Pool - Temporary Pool - NonStreaming Mips剩余的空间

在PC端是PoolSize给定的大小

其中存放了Visible Mips,Hidden Mips, Forced Mips, Cached Mips

这个值在移动端会因为NonStreaming Mips的增或减少而进行波动

Required Pool

计算出来的理想状态下需要的池子的大小,详细的后面一起说

Visible Mips

可见纹理当前占用的所需内存,不包括Forced Mips

Hidden Mips

非可见纹理,不包括Forced Mips

Forced Mips

强制流入纹理当前占据的所需内存,不包括被明确指定为不可流送的纹理

Cached Mips

不再需要的纹理所占据的内存,只要有空间,不会立即从内存中释放

Wanted Mips

Visible Mips + Hidden Mips + Forced Mips

上面这些值,假设PoolSize给了一个足够的值,Required Pool会等于Wanted Mips,符合理论,预计需要的和实际需要的相同,因为有足够大的空间

并且在这种情况下,会有一部分暂时不再需要的纹理被缓存在Pool里,实际目前占用的内存是Wanted+Cached

如果我们减小PoolSize,向Required Pool的值逼近,Streaming Pool的大小越来越小的时候,Cached Mips就会逐渐从内存里释放,给其他的腾地方

如果继续减小PoolSize,导致Streaming Pool的大小小于Required Pool的大小,会发现画面中的贴图质量随着Pool的变小慢慢下降,为了实际占用的内存不超过我们给的限制,引擎会降低自己对纹理大小的要求,降低1级、2级、3级……

直到给了一个1M的大小,所有的流送的贴图都变成了最小的,没有可以下降的余地了,这个时候Wanted Mips会明显小于Required Pool,但是会大于Streaming Pool(给的可用太小了)

这里懒的去截图了,写得也已经很清楚了,几个参数的关系就如上所说

相关控制台指令

r.Streaming.PoolSize

运行时可以动态修改流送池大小

0 表示不受任何限制
100 设置为100M大小

r.Streaming.FramesForFullUpdate

重新计算Texture需求的Mip等级的时间间隔,单位是帧

r.Streaming.HLODStrategy

0 流送所有Mip
1 仅流送最后一级Mip,其他等级的始终加载到内存里

r.Streaming.DropMips

会立即清理内存
0 不清理任何Mips
1 只会清理Cached Mip
2 会清理掉Cached Mips和Hidden Mips,使用后可以看到Stat上关于这两个所占内存的变化,程序所占用的RAM也会发生变化

r.Streaming.UseAllMips

1 会使用所有的可用Mips,在FStreamingRenderAsset::UpdateDynamicData函数里,会计算LODBias,如果是1则会跳过计算直接为0,没有偏移

r.MobileMaxLoadedMipsr.MobileReduceLoadedMips

简称为Max和Reduce

这两个在同一个函数中被调用,

1
2
3
4
5
6
7
8
9
10
11
12
13
static int32 MobileReduceLoadedMips(int32 NumTotalMips)
{
int32 NumReduceMips = FMath::Max(0, CVarMobileReduceLoadedMips.GetValueOnAnyThread());
int32 MaxLoadedMips = FMath::Clamp(CVarMobileMaxLoadedMips.GetValueOnAnyThread(), 1, GMaxTextureMipCount);

int32 NumMips = NumTotalMips;
// Reduce number of mips as requested
NumMips = FMath::Max(NumMips - NumReduceMips, 1);
// Clamp number of mips as requested
NumMips = FMath::Min(NumMips, MaxLoadedMips);

return NumMips;
}

其中Max会被Clamp到1和GMaxTextureMipCount之间(默认值15)
Reduce最低只能是0
函数参数Num,先减去NumReduceMips,最小取到1,然后和Max之间取较大的一个
该函数只有一个地方调用,就是

1
2
3
#if !PLATFORM_SUPPORTS_TEXTURE_STREAMING // eg, Android
NumMips = MobileReduceLoadedMips(NumMips);
#endif

平台不支持Streaming
NumMips在Texture2D的CreateResource函数中用来检查MipLevel
目前不会还有不支持的吧!= =不必在考虑这个了吧

r.Streaming.MipBias

调试时非常有用的一个指令,在r.Streaming.UsePerTextureBias设置为0的情况下,MipBias会作为全局的Mips向下偏移的参数,比如等于1的时候,最后使用的Mip会是计算出的理想Mip的向下一级,可以用来调试确认合适的Mip

这个指令不仅是贴图,静态模型也会受影响

总结

学习了一下UE4纹理流送相关的知识,debug用的相关参数的意义,有助于排查游戏中的Texture使用和打包相关的问题

可以通过r.Streaming.PoolSize进行调试,结合Stat Streaming信息来寻找游戏比较合适的一个Pool的大小


UE4:关于Texture Streaming的一部分总结
http://muchenhen.com/posts/21321/
作者
木尘痕
发布于
2022年7月1日
许可协议