UE4:修改Shader压缩格式减小包体

前言

在今年三月份的时候为了减少安卓更新包的大小,尝试了将ShaderArchive文件进行拆分,仅更新有变动的文件的方案。

UE4:ShadecodeLibrary拆分

随着项目铺开,原有的方案中,我们使用最多的一个母材质所拆出来的文件的大小越来越大,希望还是能减少一下文件的大小,于是考虑到修改Shader压缩格式来实现这一目标。

本文仅适用于4.27版本

压缩格式

在ShaderResource.cpp文件中:

1
static const FName ShaderCompressionFormat = NAME_LZ4;

在ShaderCodeArchive.cpp文件中:

1
static const FName ShaderCompressionFormat = NAME_LZ4;

这里原本使用的压缩格式是LZ4,是UE4原生使用的对Shader的压缩格式

而在4.27版本中,UE4新增了一个压缩格式:Oodle

Oodle现可通过GitHub在虚幻引擎中免费使用

位于:UE_4.27\Engine\Plugins\Compression\OodleData

按照官方的说法,该压缩格式可以为游戏数据提供更快和更高比率的压缩功能。

Oodle压缩格式提供了不同的压缩器和压缩等级,其压缩率和解压缩速度会有差异,可以根据需要进行选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
switch (Compressor)
{
case OodleLZ_Compressor_Selkie:
return TEXT("Selkie");
case OodleLZ_Compressor_Mermaid:
return TEXT("Mermaid");
case OodleLZ_Compressor_Kraken:
return TEXT("Kraken");
case OodleLZ_Compressor_Leviathan:
return TEXT("Leviathan");
case OodleLZ_Compressor_Hydra:
return TEXT("Hydra");
default:
break;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
switch (CompressionLevel)
{
case OodleLZ_CompressionLevel_HyperFast4:
return TEXT("HyperFast4");
case OodleLZ_CompressionLevel_HyperFast3:
return TEXT("HyperFast3");
case OodleLZ_CompressionLevel_HyperFast2:
return TEXT("HyperFast2");
case OodleLZ_CompressionLevel_HyperFast1:
return TEXT("HyperFast1");
case OodleLZ_CompressionLevel_None:
return TEXT("None");
case OodleLZ_CompressionLevel_SuperFast:
return TEXT("SuperFast");
case OodleLZ_CompressionLevel_VeryFast:
return TEXT("VeryFast");
case OodleLZ_CompressionLevel_Fast:
return TEXT("Fast");
case OodleLZ_CompressionLevel_Normal:
return TEXT("Normal");
case OodleLZ_CompressionLevel_Optimal1:
return TEXT("Optimal1");
case OodleLZ_CompressionLevel_Optimal2:
return TEXT("Optimal2");
case OodleLZ_CompressionLevel_Optimal3:
return TEXT("Optimal3");
case OodleLZ_CompressionLevel_Optimal4:
return TEXT("Optimal4");
case OodleLZ_CompressionLevel_Optimal5:
return TEXT("Optimal5");
default: break;
}

可以看到压缩等级越低,解压缩速度越快,但是相对的压缩率也会越低。

经过简单测试,使用Selkie压缩器,压缩率和LZ4相比也有优势,且解压缩速度也大于LZ4。

这里我没有做详细的测试,但是经过在网上搜索发现早在16年就有相关数据比对:

LZ4 vs LZSSE vs Oodle

在UE4 Shader中使用Oodle

可以简单看一下ShaderCodeArchive.cpp

在定义了ShaderLibraryCompressionFormat之后,FSerializedShaderArchive::DecompressShader函数中调用FCompression::UncompressMemory函数,会根据ShaderLibraryCompressionFormat来进行解压缩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void FSerializedShaderArchive::DecompressShader(int32 Index, const TArray<TArray<uint8>>& ShaderCode, TArray<uint8>& OutDecompressedShader) const
{
const FShaderCodeEntry& Entry = ShaderEntries[Index];
OutDecompressedShader.SetNum(Entry.UncompressedSize, false);
if (Entry.Size == Entry.UncompressedSize)
{
FMemory::Memcpy(OutDecompressedShader.GetData(), ShaderCode[Index].GetData(), Entry.UncompressedSize);
}
else
{
bool bSucceed = FCompression::UncompressMemory(ShaderLibraryCompressionFormat, OutDecompressedShader.GetData(), Entry.UncompressedSize, ShaderCode[Index].GetData(), Entry.Size);
check(bSucceed);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bool bUncompressSucceeded = false;

if (FormatName == NAME_Zlib)
{
// hardcoded zlib
bUncompressSucceeded = appUncompressMemoryZLIB(UncompressedBuffer, UncompressedSize, CompressedBuffer, CompressedSize, CompressionData);
}
else if (FormatName == NAME_Gzip)
{
// hardcoded gzip
bUncompressSucceeded = appUncompressMemoryGZIP(UncompressedBuffer, UncompressedSize, CompressedBuffer, CompressedSize);
}
else if (FormatName == NAME_LZ4)
{
// hardcoded lz4
bUncompressSucceeded = LZ4_decompress_safe((const char*)CompressedBuffer, (char*)UncompressedBuffer, CompressedSize, UncompressedSize) > 0;
}
else
{
// let the format module compress it
ICompressionFormat* Format = GetCompressionFormat(FormatName);
if (Format)
{
bUncompressSucceeded = Format->Uncompress(UncompressedBuffer, UncompressedSize, CompressedBuffer, CompressedSize, CompressionData);
}
}

可以看出其实只要修改一下ShaderLibraryCompressionFormat就可以了,但是原有的Material就会因为压缩格式不同而无法解压缩,所以需要进行一下简单的修改,简单的if-else,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void FSerializedShaderArchive::DecompressShader(int32 Index, const TArray<TArray<uint8>>& ShaderCode, TArray<uint8>& OutDecompressedShader) const
{
const FShaderCodeEntry& Entry = ShaderEntries[Index];
OutDecompressedShader.SetNum(Entry.UncompressedSize, false);
if (Entry.Size == Entry.UncompressedSize)
{
FMemory::Memcpy(OutDecompressedShader.GetData(), ShaderCode[Index].GetData(), Entry.UncompressedSize);
}
else
{
bool bSucceed = FCompression::UncompressMemory(ShaderLibraryCompressionFormat, OutDecompressedShader.GetData(), Entry.UncompressedSize, ShaderCode[Index].GetData(), Entry.Size);
// 原压缩格式不是Oodle会失败
#pragma region Muchenhen (Oodle uncompress failed)
if (!bSucceed)
{
bSucceed = FCompression::UncompressMemory(NAME_LZ4, OutDecompressedShader.GetData(), Entry.UncompressedSize, ShaderCode[Index].GetData(), Entry.Size);
}
#pragma endregion
//
check(bSucceed);
}
}

在ShaderResource和ShaderCodeArchive中的三处解压缩函数都需要修改。


UE4:修改Shader压缩格式减小包体
http://muchenhen.com/posts/11838/
作者
木尘痕
发布于
2022年10月18日
许可协议