.scl.csv的格式
scl其实是"Shader Code Library"的缩写。下面是一个示例:

|----------------------------------------------------------------------------------------------|-----------------------------------------------|--------------|--------------------------|--------------|--------------|-----------------|----------------|--------------------------------|-------------|------------------------------------------|--------------|------------------|--------------|------------------------------|---|---|
| ClassNameAndObjectPath | ShaderType | ShaderClass | MaterialDomain | FeatureLevel | QualityLevel | TargetFrequency | TargetPlatform | VFType | Permutation | OutputHash | PipelineHash | AftermathHash | StaticSwitch | ParentClassNameAndObjectPath | ShaderMapHash ||
| Material /Engine/EngineMaterials/DefaultVirtualTextureMaterial.DefaultVirtualTextureMaterial | TMyBaseColorNormalRoughnessSpecularMaskHeight | MeshMaterial | MD_RuntimeVirtualTexture | SM5 | High | SF_Vertex | PCD3D_SM5 | TGPUSkinMorphVertexFactorytrue | Perm_0 | E12BAEE52214242489BEA6ED027735FAB11D31A8 | 0 | 6e9ee21f5b5f5b99 | | | 0 | |
| Material /Engine/EngineMaterials/DefaultVirtualTextureMaterial.DefaultVirtualTextureMaterial | TMyTextureVSWorldHeight | MeshMaterial | MD_RuntimeVirtualTexture | SM5 | High | SF_Vertex | PCD3D_SM5 | TGPUSkinMorphVertexFactorytrue | Perm_0 | E12BAEE52214242489BEA6ED027735FAB11D31A8 | 0 | 6e9ee21f5b5f5b99 | | | 0 | |
| Material /Engine/EngineMaterials/DefaultVirtualTextureMaterial.DefaultVirtualTextureMaterial | TMyPSBaseColorNormalSpecularCombined | MeshMaterial | MD_RuntimeVirtualTexture | SM5 | High | SF_Pixel | PCD3D_SM5 | TGPUSkinMorphVertexFactorytrue | Perm_0 | DA1B85E5806B237281D6633EA1AA2BAD894A37CA | 0 | 9a4e8536f45ca7a6 | | | 0 | |
材质到Shader的映射
为什么都说.scl.csv是材质到shader的映射?这一些key每个都是些什么含义?
.scl.csv文件(Stable Shader Code Library)的核心作用,是在项目打包时,为游戏中的每一个着色器变体生成一个唯一的、稳定的标识符 。可以将它理解为一本在打包时编纂的"着色器字典 "。这本字典建立的是从稳定的逻辑描述 (比如"使用材质M_Rock、在DefaultMesh顶点工厂、且开启高质量阴影的顶点着色器")到不稳定的具体实现(该着色器变体在这次构建中编译出的字节码的哈希值)之间的映射关系。
之所以说它是"材质到Shader的映射",是因为它的内容紧密围绕着材质如何生成最终的着色器代码。UE4在打包时会遍历所有材质资源,为它们可能产生的各种着色器变体(由不同的质量等级、功能开关、平台特性等组合而成)分配这些稳定标识符。
该文件中的每一行都定义了一个稳定着色器键(Stable Shader Key)到其输出哈希(Output Hash)的映射。这些键共同构成了一个稳定密钥(Stable Key),其主要构成部分如下:
| 关键字段 | 含义与作用 |
|---|---|
| MaterialPath | 材质资产的唯一路径 (如/Game/Assets/Materials/M_Rock)。这是映射的起点,指明了是哪个材质需要这个着色器。 |
| ShaderType / ShaderClass | 着色器的类型 ,例如顶点着色器(VertexShader)、像素着色器(PixelShader)等。它指定了着色器在渲染管线中的阶段。 |
| VFType (Vertex Factory Type) | 顶点工厂类型。顶点工厂定义了顶点数据的格式和来源(如静态网格、骨骼网格、实例化静态网格等)。同一材质用于不同种类的网格物体时,需要不同的顶点着色器。 |
| Quality Level | 画质等级 (如Low, Medium, High)。不同画质等级下,材质可能会使用不同的简化或复杂版本的着色器代码。 |
| Feature Level | 特性等级 。这定义了目标平台的图形能力(如SM5对应DX11/12,ES3_1对应移动端)。不同特性等级需要完全不同的着色器编译目标。 |
| 其他渲染状态标志 | 一系列开关选项,用于控制材质的不同功能,例如是否开启Tessellation(曲面细分)、是否使用Decal(贴花)渲染路径等。 |
所有这些字段组合在一起,就形成了一个全局唯一的稳定标识符 。这个标识符描述的是一种渲染需求的逻辑组合,而不是某一次具体的编译结果。在后续的PSO缓存流程中,正是通过这个稳定标识符,才能将运行时记录的PSO使用情况与打包时编译出的具体着色器代码正确地关联起来。
scl.csv文件在PSO缓存的工作流中扮演着核心的"翻译官"角色:
-
打包生成 :在项目打包时,引擎会编译所有材质,并生成
.scl.csv文件(以及对应的.shk二进制文件)。这个过程记录了当前版本所有可能的着色器变体及其稳定的唯一标识符。 -
运行时记录 :在真机上运行游戏,引擎会记录实际触发过的PSO,生成
.rec.upipelinecache文件。这个文件记录的是基于易变的着色器字节码哈希的PSO使用列表。 -
Expand(展开) :使用UE4的工具,将运行时记录(
.rec.upipelinecache)与"字典"(.scl.csv)进行匹配。工具会把记录中的不稳定哈希"翻译"回对应的稳定密钥,生成.stablepc.csv文件。这使得PSO缓存列表不再依赖于某次特定的构建。 -
Build(构建/逆展开) :在最终发布打包时,引擎再读取
.stablepc.csv文件,根据其中的稳定密钥,找到当前版本最新的着色器哈希,并预编译出最终的二进制PSO缓存文件(.stable.upipelinecache),随游戏分发给玩家。
2G的限制
提问:在UE4中,Cook时.scl.csv超过2G,导致Cook失败,该如何增加.scl.csv的数目?
回答:如果你的工程很大,需要改造引擎,使得它能构建时生产,运行时读取 多个 .scl.csv ,下面是读取的代码示例
// Engine\Source\Runtime\RenderCore\Private\ShaderCodeLibrary.cpp
void AddExistingShaderCodeLibrary(FString const& OutputDir)
{
check(LibraryName.Len() > 0);
const FString ShaderIntermediateLocation = FPaths::ProjectSavedDir() / TEXT("Shaders") / FormatName.ToString();
TArray<FString> ShaderFiles;
IFileManager::Get().FindFiles(ShaderFiles, *ShaderIntermediateLocation, *ShaderExtension);
for (const FString& ShaderFileName : ShaderFiles)
{
// 满足一些条件时
// ...... 支持多个 .scl.csv 都解析出来添加给 ShaderCodeLibrary
for (int32 fileindex = 0; fileindex < GMultiShaderFiles; fileindex++)
{
// 将满足你指定要求命名的多个 .scl.csv 都读取出来
if (FFileHelper::LoadFileToStringArray(SourceFileContents, *GetStableInfoArchivemulFilename(OutputDir, LibraryName, FormatName, FString::FromInt(fileindex))))
{
for (int32 Index = 1; Index < SourceFileContents.Num(); Index++)
{
FStableShaderKeyAndValue Item;
Item.ParseFromString(SourceFileContents[Index]);
AddShader(Item);
}
}
}
}
}