【转载】UE4 材质变体研究

798 阅读4分钟

版权声明:本文为CSDN博主「Papals」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:blog.csdn.net/qq_23030843…

材质变体

在编辑器打开时,会从资源包中 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)派生数据缓存

参考原文链接

编辑器偏好设置——>全局——>"共享派生数据缓存"

作用:同一个项目编译结束之后,会把缓存数据存储到共享文件夹中,后续其他团队成员打开项目的时候,就不需要再次编译了。

image.png