版权声明:本文为CSDN博主「Papals」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
材质变体
在编辑器打开时,会从资源包中 LoadPackage ,我们这里只关心的是材质问题。每一个 Obj , 都会调用自己的 Obj->ConditionalPostLoad() 这里已经是在加载的后端了,在这里面会调用子类的 PostLoad()
for (int32 i = 0; i < ObjLoaded.Num(); i++)
{
UObject *Obj = ObjLoaded[i];
check(Obj);
#if WITH_EDITOR
//一顿操作
#endif
//又一顿操作
Obj->ConditionalPostLoad();
}
我们只看 UMaterial::PostLoad,如果调用到这里,也就是说我们加载的是一个材质。这是一个非常长的函数。就不在这里展开了。
Super::PostLoad();
这里会调用他父类的 PostLoad,而 UMaterial 的父类就是 UMaterialInterface,而他们的区别,和主要的成员分析见 Unreal Material 类的关系
在 UMaterialInterface::PostLoad() 中又会调用UMaterial::CacheResourceShadersForRendering
- 缓存用于渲染的资源着色器;
- 如果在内存或 DDC 中找不到匹配的着色器映射,则会编译一个新的。(DDC,详见文末)
- 编译完成后,结果将被应用到渲染器中的
FMaterial中。 - 注意: 这修改了用于渲染的材质变量,并假设在
FMaterialUpdateContext中被调用!
RebuildShadingModelField(); // 我们首先把 ShadingModel 重新计算,
FlushResourceShaderMaps(); // 将我们最终要渲染的 MaterialResources ,进行卸载
/*
// 这里只对在 cooked build 中用于渲染到 level 质量的 shader 进行缓存,其中没有着色器编译,但仍然是需要注册加载的 shadermap
CacheShadersForResources(ShaderPlatform, ResourcesToCache, true);
CacheShadersForResources 这是最主要的函数,我们将要进去,看它是如何进行缓存的,并且看它都缓存了什么东西。*/
void UMaterial::CacheShadersForResources(……)
{
RebuildExpressionTextureReferences(); // 就按字面意思理解把
// 遍历每一个 resource
for (int32 ResourceIndex = 0; ResourceIndex < ResourcesToCache.Num(); ResourceIndex++)
{
FMaterialResource CurrentResource = ResourcesToCache[ResourceIndex];
// 最终 cache 的地方
const bool bSuccess = CurrentResource->CacheShaders(ShaderPlatform, bApplyCompletedShaderMapForRendering);
if (!bSuccess)
{
// 如果出现错误的 log
}
}
}
从上面可以看出,我们最终的 cache 还是要在回到 FMaterialResource 身上,这里只是做了保护而已,所以继续往下。
bool FMaterial::CacheShaders(……)
{
FMaterialShaderMapId NoStaticParametersId;
GetShaderMapId(Platform, NoStaticParametersId);
return CacheShaders(NoStaticParametersId, Platform, bApplyCompletedShaderMapForRendering);
}
这里我们终于来到了一个新的天地,因为我们终于不在 Umaterial 里面了,而是在FMaterialResource 的父类 FMaterial 里面,这里主要有两个重要函数
一个是 FMaterialResource::GetShaderMapId ,另一个是 CacheShaders
FMaterialResource::GetShaderMapId
首先就是调用父类的 FMaterial::GetShaderMapId(Platform, OutId); 这里最终得到的是FMaterialShaderMapId
关于这个结构体和其他相关结构体的内容可以参见 Material 相关类
GetShaderMapId
if (bLoadedCookedShaderMapId)
{
// 开始做的判断是是否已经加载了这个 material 的 shadermap,如果有的话我们还要判断是在哪个线程中,即从 GameThreadShaderMap 获取还是 RenderingThreadShaderMap 获取。
}
else
{
#if WITH_EDITOR
TArray<FShaderType> ShaderTypes;
TArray<FVertexFactoryType> VFTypes;
TArray<const FShaderPipelineType *> ShaderPipelineTypes;
GetDependentShaderAndVFTypes(Platform, ShaderTypes, ShaderPipelineTypes, VFTypes);
// 省略赋值
GetReferencedTexturesHash(Platform, OutId.TextureReferencesHash);
#else
// 省略赋值 log
#endif
}
最开始做的判断是是否已经加载了这个 material 的 ShaderMap,如果有的话我们还要判断是在哪个线程中,即从 GameThreadShaderMap 获取还是 RenderingThreadShaderMap 获取。
GetDependentShaderAndVFTypes
否则的话,我们就需要进行 ShaderMap 的填充,GetDependentShaderAndVFTypes 就是填充的关键。
- 对所有的
VertexFactoryType进行判断 - 这个顶点工厂必须是使用在 Material 里的
- 从每一个
FShaderType中得到FMeshMaterialShaderType,如果FMeshMaterialShaderType不是空 且FMeshMaterialShaderType可以 cache 这个VertexFactoryType,那么我们就允许增加这个AddUnique(ShaderType),并且在最后的时候会增加这个VertexFactoryType - 从每一个
FShaderPipelineType判断是否用于 Mesh Material ,并且要判断FShaderPipelineType里面所有的 Shader Type 的,如果它里面的所有的 shader 都通过判断,那么我们就增加这个FShaderPipelineType - 对所有的
FShaderType进行判断 - 和上面的一样,不过判断条件
VertexFactoryType改为了null - 对所有的
FShaderPipelineType进行判断 - 同上
其实我们只是对判断依据感兴趣而已。
todo: 判断条件是传进来的函数指针,还没有搞清楚来源
这样,我们就讨论完了 GetShaderMapId
FMaterialShaderMapId::SetShaderDependencies
下面的函数时 FMaterialShaderMapId::SetShaderDependencies,主要就是记录它的 hash 值,然后记录下来,这里的疑问就出来了。
当我们的 FMaterial::GetShaderMapId 分析完后,我们就要回到 FMaterialResource::GetShaderMapId 里面
// 这是把所有的材质里使用到的函数,加到 OutId.ReferencedFunctions 中
Material->AppendReferencedFunctionIdsTo(OutId.ReferencedFunctions);
// 这是把所有的材质里使用到的 parameter collections ,加到 OutId.ReferencedParameterCollections 中
Material->AppendReferencedParameterCollectionIdsTo(OutId.ReferencedParameterCollections);
// 添加 texture 的哈希值
Material->GetForceRecompileTextureIdsHash(OutId.TextureReferencesHash);
// 如果其有关联的 MaterialInstance 不为空
if(MaterialInstance)
{
MaterialInstance->GetBasePropertyOverridesHash(OutId.BasePropertyOverridesHash);
FStaticParameterSet CompositedStaticParameters;
MaterialInstance->GetStaticParameterValues(CompositedStaticParameters);
OutId.UpdateParameterSet(CompositedStaticParameters);
}
FMaterial::CacheShaders
算了(原作者就是这么任性 =_=)
DDC(DerivedDataCache)派生数据缓存
编辑器偏好设置——>全局——>"共享派生数据缓存"
作用:同一个项目编译结束之后,会把缓存数据存储到共享文件夹中,后续其他团队成员打开项目的时候,就不需要再次编译了。