From 13abd3f21cfb6128da8845d7d0814d0d04b8ae34 Mon Sep 17 00:00:00 2001 From: BlueRose <378100977@qq.com> Date: Wed, 7 Feb 2024 19:34:16 +0800 Subject: [PATCH] vault backup: 2024-02-07 19:34:16 --- .../plugins/various-complements/data.json | 8 + .../剖析虚幻渲染体系(09)- 材质体系.md | 1619 ++++++++++++++++- .../Images/ImageBag/Images/UE_UMaterial.png | Bin 0 -> 85150 bytes 3 files changed, 1626 insertions(+), 1 deletion(-) create mode 100644 08-Assets/Images/ImageBag/Images/UE_UMaterial.png diff --git a/.obsidian/plugins/various-complements/data.json b/.obsidian/plugins/various-complements/data.json index 91de3a3..0f72d01 100644 --- a/.obsidian/plugins/various-complements/data.json +++ b/.obsidian/plugins/various-complements/data.json @@ -134,6 +134,14 @@ "lastUpdated": 1707223827335 } } + }, + "此外,UMaterialInstance的部分接口也会触发FMaterialResource实例的创建,此文不继续追踪了。": { + "此外,UMaterialInstance的部分接口也会触发FMaterialResource实例的创建,此文不继续追踪了。": { + "currentFile": { + "count": 1, + "lastUpdated": 1707302668655 + } + } } } } \ No newline at end of file diff --git a/03-UnrealEngine/Rendering/RenderingPipeline/向往渲染系列文章阅读笔记/剖析虚幻渲染体系(09)- 材质体系.md b/03-UnrealEngine/Rendering/RenderingPipeline/向往渲染系列文章阅读笔记/剖析虚幻渲染体系(09)- 材质体系.md index 7376699..46e68ff 100644 --- a/03-UnrealEngine/Rendering/RenderingPipeline/向往渲染系列文章阅读笔记/剖析虚幻渲染体系(09)- 材质体系.md +++ b/03-UnrealEngine/Rendering/RenderingPipeline/向往渲染系列文章阅读笔记/剖析虚幻渲染体系(09)- 材质体系.md @@ -8,4 +8,1621 @@ rating: ⭐ # 前言 https://www.cnblogs.com/timlly/p/15109132.html -# UMaterial \ No newline at end of file +# 概念 +- UMaterialInterface:材质的基础接口类, 定义了大量材质相关的数据和接口, 部分接口是空实现或未实现的接口。 + - USubsurfaceProfile* SubsurfaceProfile:次表面散射轮廓,常用于皮肤、玉石等材质。 + - TArray AssetUserData:用户数据,可以存多个数据。 + - FLightmassMaterialInterfaceSettings LightmassSettings:离线GI数据。 + - TArray TextureStreamingData:纹理流数据。 +- UMaterial:材质类,继承自 UMaterialInterface,对应着一个材质资源文件。定义了材质所需的所有数据和操作接口,并负责打通其它关联类型的链接。 + - TArray MaterialResources:材质渲染资源,一个材质可以拥有多个渲染资源实例。 + - FDefaultMaterialInstance* DefaultMaterialInstance:默认的材质渲染代理,继承自FMaterialRenderProxy。 + - UPhysicalMaterial* PhysMaterial:物理材质。 + - TArray LoadedMaterialResources:已经加载的材质资源,通常由游戏线程从磁盘加载并序列化而来。 + - 材质的各种属性和标记。 +- UMaterialInstance:材质实例,**不能单独存在**,而**需要依赖UMaterialInterface类型的父类**,意味着父类可以是UMaterialInterface的任意一个子类,但最上层的父类必须是UMaterial。 + - UMaterialInstanceConstant:为了**避免运行时因修改材质参数而引起重新编译**,它内部有限的数据覆盖也是因为此。如果不重新编译,就无法支持对材质的常规修改,因此实例只能更改预定义的材质参数的值。 这里的参数就是在材质编辑器内定义的唯一的名称、类型和默认值静态定义。另外,需要明确注意的是,在运行时的代码(非编辑器代码)中,我们是无法更改UMaterialInstanceConstant实例的材质属性。UMaterialInstanceConstant还有一个专用于渲染地貌的ULandscapeMaterialInstanceConstant的子类。 + - UMaterialInstanceDynamic:提供了可以在**运行时代码动态创建和修改材质属性的功能**,并且同样**不会引起材质重新编译**。 +- **FMaterialRenderProxy**:FMaterialRenderProxy负责**接收游戏线程代表的数据,然后传递给渲染器去处理和渲染**。FMaterialRenderProxy是个抽象类,定义了一个静态全局的材质渲染代理映射表和获取FMaterial渲染实例的接口。具体的逻辑由子类完成。 + - **FDefaultMaterialInstance**:渲染UMaterial的默认代表实例。 + - **FMaterialInstanceResource**:渲染UMaterialInstance实例的代表。 + - FColoredMaterialRenderProxy:覆盖材质颜色向量参数的材质渲染代表。 + - FLandscapeMaskMaterialRenderProxy:地貌遮罩材质渲染代表。 + - FLightmassMaterialProxy:Lightmass材质渲染代理。 + - ...... +- **FMaterial**: + - **描述材质的编译过程**,并提供可扩展性钩子(CompileProperty等) 。 + - **将材质属性传递到渲染器**,并使用函数访问材质属性。 + - 存储缓存的**ShaderMap**(GameThread & RenderingThread),和其他来自编译的瞬态输出,这对异步着色器编译是必要的。 +- **FMaterialResource**:继承自FMaterial,实现**材质接口**。 + - 拥有对应的UMaterial、UMaterialInstance指针。 + +![[UE_UMaterial.png]] +UE的材质为何会有如此多的概念和类型,它们的关系到底怎么样?本节尝试阐述它们的关联和作用。 + +首先阐述**UMaterialInterface和它的子类们,它们是引擎模块在游戏线程的代表**。UMaterialInterface继承UOjbect,提供了材质的抽象接口,为子类提供了一致的行为和规范,也好统一不同类型的子类之间的差异。子类UMaterial则对应着用材质编辑器生成的材质蓝图的资源,保存了各种表达式节点及各种参数。另一个子类UMaterialInstance则抽象了材质实例的接口,是为了支持修改材质参数后不引发材质重新编译而存在的,同时统一和规范固定实例(UMaterialInstanceConstant)和动态实例(UMaterialInstanceDynamic)两种子类的数据和行为。UMaterialInstanceConstant在编辑器期间创建和修改好材质参数,运行时不可修改,提升数据更新和渲染的性能;UMaterialInstanceDynamic则可以运行时创建实例和修改数据,提升材质的扩展性和可定制性,但性能较UMaterialInstanceConstant差一些。UMaterialInstance需要指定一个父类,最顶层的父类要求是UMaterial实例。 + +**FMaterialRenderProxy是UMaterialInterface的渲染线程的代表**,类似于UPrimitiveComponent和FPrimitiveSceneProxy的关系。**FMaterialRenderProxy将UMaterialInterface实例的数据搬运(拷贝)到渲染线程,但同时也会在游戏线程被访问到,是两个线程的耦合类型,需要谨慎处理它们的数据和接口调用**。**FMaterialRenderProxy的子类对应着UMaterialInterface的子类,以便将UMaterialInterface的子类数据被精准地搬运(拷贝)到渲染线程,避免游戏线程和渲染线程的竞争**。FMaterialRenderProxy及其子类都是引擎模块的类型。 + +既然已经有了FMaterialRenderProxy的渲染线程代表,为什么还要存在FMaterial和FMaterialResource呢?答案有两点: +- FMaterialRenderProxy及其子类是引擎模块的类型,是游戏线程和渲染线程的胶囊类,需要谨慎处理两个线程的数据和接口调用,渲染模块无法真正完全拥有它的管辖权。 +- FMaterialRenderProxy的数据由UMaterialInterface传递而来,意味着FMaterialRenderProxy的信息有限,无法包含使用了材质的网格的其它信息,如顶点工厂、ShaderMap、ShaderPipelineline、FShader及各种着色器参数等。 + +所以,FMaterial应运而生。FMaterial同是引擎模块的类型,但存储了游戏线程和渲染线程的两个ShaderMap,意味着渲染模块可以自由地访问渲染线程的ShaderMap,而又不影响游戏线程的访问。而且FMaterial包含了渲染材质所需的所有数据,渲染器的其它地方,只要拿到网格的FMaterial,便可以正常地获取材质数据,从而提交绘制指令。比如FBasePassMeshProcessor::AddMeshBatch的代码: +```c++ +// Engine\Source\Runtime\Renderer\Private\BasePassRendering.cpp + +void FBasePassMeshProcessor::AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId) +{ + if (MeshBatch.bUseForMaterial) + { + const FMaterialRenderProxy* FallbackMaterialRenderProxyPtr = nullptr; + // 获取FMaterial实例. + const FMaterial& Material = MeshBatch.MaterialRenderProxy->GetMaterialWithFallback(FeatureLevel, FallbackMaterialRenderProxyPtr); + const FMaterialRenderProxy& MaterialRenderProxy = FallbackMaterialRenderProxyPtr ? *FallbackMaterialRenderProxyPtr : *MeshBatch.MaterialRenderProxy; + + // 通过FMaterial接口获取材质数据. + const EBlendMode BlendMode = Material.GetBlendMode(); + const FMaterialShadingModelField ShadingModels = Material.GetShadingModels(); + const bool bIsTranslucent = IsTranslucentBlendMode(BlendMode); + const FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(MeshBatch); + const ERasterizerFillMode MeshFillMode = ComputeMeshFillMode(MeshBatch, Material, OverrideSettings); + const ERasterizerCullMode MeshCullMode = ComputeMeshCullMode(MeshBatch, Material, OverrideSettings); + + (......) +} +``` + +>其实应该说**FMaterial**与**FMaterialResource**从FMaterialRenderProxy中将**有多线程竞争风险的渲染所需数据进行剥离**。渲染器只需要拿到FMaterial就可以获取Shader数据并进行MeshBatch提交。 +>在FMaterialRenderProxy中通过**GetMaterialXXX()** 系列函数获取FMaterial。如果Shader没有编译好会进行等待,并在等待完之后返回。GetMaterialFallbackXXX()系列函数会不断递归获取有效的FMaterial直到默认UMaterial::GetDefaultMaterial()。 +>从UMaterial调用GetRenderProxy()获取**FDefaultMaterialInstance DefaultMaterialInstance**,而这个变量在**UMaterial::PostInitProperties()** 通过**DefaultMaterialInstance = new FDefaultMaterialInstance(this);** 初始化。 +# UMaterial +UMaterial是属于引擎层的概念,对应着我们在材质编辑器编辑的uasset资源文件,可以被应用到网格上,以便控制它在场景中的视觉效果。它继承自UMaterialInterface。 +```c++ +// Engine\Source\Runtime\Engine\Classes\Materials\MaterialInterface.h + +// 材质的基础接口类, 定义了大量材质相关的数据和接口, 部分接口是空实现或未实现的接口. +class UMaterialInterface : public UObject, public IBlendableInterface, public IInterface_AssetUserData +{ + // 次表面散射轮廓(配置) + class USubsurfaceProfile* SubsurfaceProfile; + // 当图元不再被用作父元素时进行跟踪的栅栏. + FRenderCommandFence ParentRefFence; + +protected: + // Lightmass离线的GI设置. + struct FLightmassMaterialInterfaceSettings LightmassSettings; + // 纹理流数据. + TArray TextureStreamingData; + // 存于此材质资源内的用户数据列表. + TArray AssetUserData; + +private: + // 强制编译的目标Feature Level. + uint32 FeatureLevelsToForceCompile; + +public: + //---- IInterface_AssetUserData接口 ---- + virtual void AddAssetUserData(UAssetUserData* InUserData) override; + virtual void RemoveUserDataOfClass(TSubclassOf InUserDataClass) override; + virtual UAssetUserData* GetAssetUserDataOfClass(TSubclassOf InUserDataClass) override; + //---- IInterface_AssetUserData接口 ---- + + // 为所有材质编译的Featurelevel位域. + static uint32 FeatureLevelsForAllMaterials; + void SetFeatureLevelToCompile(ERHIFeatureLevel::Type FeatureLevel, bool bShouldCompile); + static void SetGlobalRequiredFeatureLevel(ERHIFeatureLevel::Type FeatureLevel, bool bShouldCompile); + + //---- UObject接口 ---- + virtual void BeginDestroy() override; + virtual bool IsReadyForFinishDestroy() override; + virtual void PostLoad() override; + virtual void PostDuplicate(bool bDuplicateForPIE) override; + virtual void PostCDOContruct() override; + //---- UObject接口 ---- + + //---- IBlendableInterface接口 ---- + virtual void OverrideBlendableSettings(class FSceneView& View, float Weight) const override; + //---- IBlendableInterface接口 ---- + + // 沿着父链查找这个实例所在的基础材质. + UMaterial* GetBaseMaterial(); + // 获取正在实例化的材质 + virtual class UMaterial* GetMaterial() PURE_VIRTUAL(UMaterialInterface::GetMaterial,return NULL;); + virtual const class UMaterial* GetMaterial() const PURE_VIRTUAL(UMaterialInterface::GetMaterial,return NULL;); + // 获取正在实例化的材质(并发模式) + virtual const class UMaterial* GetMaterial_Concurrent(TMicRecursionGuard RecursionGuard = TMicRecursionGuard()) const PURE_VIRTUAL(UMaterialInterface::GetMaterial_Concurrent,return NULL;); + + // 测试该材质是否依赖指定的材料. + virtual bool IsDependent(UMaterialInterface* TestDependency); + virtual bool IsDependent_Concurrent(UMaterialInterface* TestDependency, TMicRecursionGuard RecursionGuard = TMicRecursionGuard()); + + // 获取此材质对应的用于渲染的FMaterialRenderProxy实例. + virtual class FMaterialRenderProxy* GetRenderProxy() const PURE_VIRTUAL(UMaterialInterface::GetRenderProxy,return NULL;); + + (......) + + // 获取用于渲染此材质的纹理列表. + virtual void GetUsedTextures(TArray& OutTextures, EMaterialQualityLevel::Type QualityLevel, bool bAllQualityLevels, ERHIFeatureLevel::Type FeatureLevel, bool bAllFeatureLevels) const + PURE_VIRTUAL(UMaterialInterface::GetUsedTextures,); + // 获取用于渲染此材质的纹理列表和索引. + virtual void GetUsedTexturesAndIndices(TArray& OutTextures, TArray< TArray >& OutIndices, EMaterialQualityLevel::Type QualityLevel, ERHIFeatureLevel::Type FeatureLevel) const; + // 覆盖指定的纹理. + virtual void OverrideTexture(const UTexture* InTextureToOverride, UTexture* OverrideTexture, ERHIFeatureLevel::Type InFeatureLevel) PURE_VIRTUAL(UMaterialInterface::OverrideTexture, return;); + + // 覆盖给定参数的默认值(短暂的) + virtual void OverrideVectorParameterDefault(const FHashedMaterialParameterInfo& ParameterInfo, const FLinearColor& Value, bool bOverride, ERHIFeatureLevel::Type FeatureLevel) PURE_VIRTUAL(UMaterialInterface::OverrideTexture, return;); + + // 检测指定的材质标记, 如果存在就返回true. + virtual bool CheckMaterialUsage(const EMaterialUsage Usage) PURE_VIRTUAL(UMaterialInterface::CheckMaterialUsage,return false;); + virtual bool CheckMaterialUsage_Concurrent(const EMaterialUsage Usage) const PURE_VIRTUAL(UMaterialInterface::CheckMaterialUsage,return false;); + + // 获取材质的渲染纹理, 需要指定FeatureLevel/QualityLevel. + virtual FMaterialResource* GetMaterialResource(ERHIFeatureLevel::Type InFeatureLevel, EMaterialQualityLevel::Type QualityLevel = EMaterialQualityLevel::Num); + + // 获取组排序优先级. + virtual bool GetGroupSortPriority(const FString& InGroupName, int32& OutSortPriority) const + PURE_VIRTUAL(UMaterialInterface::GetGroupSortPriority, return false;); + + // 获取材质的各种类型的所有数据. + virtual void GetAllScalarParameterInfo(TArray& OutParameterInfo, TArray& OutParameterIds) const + PURE_VIRTUAL(UMaterialInterface::GetAllScalarParameterInfo,return;); + virtual void GetAllVectorParameterInfo(TArray& OutParameterInfo, TArray& OutParameterIds) const + PURE_VIRTUAL(UMaterialInterface::GetAllVectorParameterInfo,return;); + virtual void GetAllTextureParameterInfo(TArray& OutParameterInfo, TArray& OutParameterIds) const + PURE_VIRTUAL(UMaterialInterface::GetAllTextureParameterInfo,return;); + virtual void GetAllRuntimeVirtualTextureParameterInfo(TArray& OutParameterInfo, TArray& OutParameterIds) const + PURE_VIRTUAL(UMaterialInterface::GetAllRuntimeVirtualTextureParameterInfo, return;); + virtual void GetAllFontParameterInfo(TArray& OutParameterInfo, TArray& OutParameterIds) const + PURE_VIRTUAL(UMaterialInterface::GetAllFontParameterInfo,return;); + + // 获取材质的各种类型的数据. + virtual bool GetScalarParameterDefaultValue(const FHashedMaterialParameterInfo& ParameterInfo, float& OutValue, bool bOveriddenOnly = false, bool bCheckOwnedGlobalOverrides = false) const + PURE_VIRTUAL(UMaterialInterface::GetScalarParameterDefaultValue,return false;); + virtual bool GetVectorParameterDefaultValue(const FHashedMaterialParameterInfo& ParameterInfo, FLinearColor& OutValue, bool bOveriddenOnly = false, bool bCheckOwnedGlobalOverrides = false) const + PURE_VIRTUAL(UMaterialInterface::GetVectorParameterDefaultValue,return false;); + virtual bool GetTextureParameterDefaultValue(const FHashedMaterialParameterInfo& ParameterInfo, class UTexture*& OutValue, bool bCheckOwnedGlobalOverrides = false) const + PURE_VIRTUAL(UMaterialInterface::GetTextureParameterDefaultValue,return false;); + virtual bool GetRuntimeVirtualTextureParameterDefaultValue(const FHashedMaterialParameterInfo& ParameterInfo, class URuntimeVirtualTexture*& OutValue, bool bCheckOwnedGlobalOverrides = false) const + PURE_VIRTUAL(UMaterialInterface::GetRuntimeVirtualTextureParameterDefaultValue, return false;); + virtual bool GetFontParameterDefaultValue(const FHashedMaterialParameterInfo& ParameterInfo, class UFont*& OutFontValue, int32& OutFontPage, bool bCheckOwnedGlobalOverrides = false) const + PURE_VIRTUAL(UMaterialInterface::GetFontParameterDefaultValue,return false;); + + // 获取分层数据. + virtual int32 GetLayerParameterIndex(EMaterialParameterAssociation Association, UMaterialFunctionInterface * LayerFunction) const + PURE_VIRTUAL(UMaterialInterface::GetLayerParameterIndex, return INDEX_NONE;); + + // 获取由表达式引用的纹理,包括嵌套函数. + virtual TArrayView GetReferencedTextures() const + PURE_VIRTUAL(UMaterialInterface::GetReferencedTextures,return TArrayView();); + + // 保存shader稳定的键值. + virtual void SaveShaderStableKeysInner(const class ITargetPlatform* TP, const struct FStableShaderKeyAndValue& SaveKeyVal) + PURE_VIRTUAL(UMaterialInterface::SaveShaderStableKeysInner, ); + + // 获取材质参数信息. + FMaterialParameterInfo GetParameterInfo(EMaterialParameterAssociation Association, FName ParameterName, UMaterialFunctionInterface* LayerFunction) const; + // 获取材质关联标记. + ENGINE_API FMaterialRelevance GetRelevance(ERHIFeatureLevel::Type InFeatureLevel) const; + ENGINE_API FMaterialRelevance GetRelevance_Concurrent(ERHIFeatureLevel::Type InFeatureLevel) const; + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + // 记录材质和纹理. + ENGINE_API virtual void LogMaterialsAndTextures(FOutputDevice& Ar, int32 Indent) const {} +#endif + +private: + int32 GetWidth() const; + int32 GetHeight() const; + + // Lightmass接口. + const FGuid& GetLightingGuid() const; + void SetLightingGuid(); + virtual void GetLightingGuidChain(bool bIncludeTextures, TArray& OutGuids) const; + virtual bool UpdateLightmassTextureTracking(); + inline bool GetOverrideCastShadowAsMasked() const; + inline bool GetOverrideEmissiveBoost() const; + (......) + + // 数据获取接口. + virtual bool GetScalarParameterValue(const FHashedMaterialParameterInfo& ParameterInfo, float& OutValue, bool bOveriddenOnly = false) const; + virtual bool GetScalarCurveParameterValue(const FHashedMaterialParameterInfo& ParameterInfo, FInterpCurveFloat& OutValue) const; + virtual bool GetVectorParameterValue(const FHashedMaterialParameterInfo& ParameterInfo, FLinearColor& OutValue, bool bOveriddenOnly = false) const; + virtual bool GetVectorCurveParameterValue(const FHashedMaterialParameterInfo& ParameterInfo, FInterpCurveVector& OutValue) const; + virtual bool GetLinearColorParameterValue(const FHashedMaterialParameterInfo& ParameterInfo, FLinearColor& OutValue) const; + virtual bool GetLinearColorCurveParameterValue(const FHashedMaterialParameterInfo& ParameterInfo, FInterpCurveLinearColor& OutValue) const; + virtual bool GetTextureParameterValue(const FHashedMaterialParameterInfo& ParameterInfo, class UTexture*& OutValue, bool bOveriddenOnly = false) const; + virtual bool GetRuntimeVirtualTextureParameterValue(const FHashedMaterialParameterInfo& ParameterInfo, class URuntimeVirtualTexture*& OutValue, bool bOveriddenOnly = false) const; + virtual bool GetFontParameterValue(const FHashedMaterialParameterInfo& ParameterInfo,class UFont*& OutFontValue, int32& OutFontPage, bool bOveriddenOnly = false) const; + virtual bool GetRefractionSettings(float& OutBiasValue) const; + + // 访问基础材质的覆盖属性. + virtual float GetOpacityMaskClipValue() const; + virtual bool GetCastDynamicShadowAsMasked() const; + virtual EBlendMode GetBlendMode() const; + virtual FMaterialShadingModelField GetShadingModels() const; + virtual bool IsShadingModelFromMaterialExpression() const; + virtual bool IsTwoSided() const; + virtual bool IsDitheredLODTransition() const; + virtual bool IsTranslucencyWritingCustomDepth() const; + virtual bool IsTranslucencyWritingVelocity() const; + virtual bool IsMasked() const; + virtual bool IsDeferredDecal() const; + virtual USubsurfaceProfile* GetSubsurfaceProfile_Internal() const; + virtual bool CastsRayTracedShadows() const; + + // 强制流系统忽略指定持续时间的正常逻辑,而总是加载此材质使用的所有纹理的所有mip级别. + virtual void SetForceMipLevelsToBeResident( bool OverrideForceMiplevelsToBeResident, bool bForceMiplevelsToBeResidentValue, float ForceDuration, int32 CinematicTextureGroups = 0, bool bFastResponse = false ); + + // 重新缓存所有材材质接口的统一表达式. + static void RecacheAllMaterialUniformExpressions(bool bRecreateUniformBuffer); + virtual void RecacheUniformExpressions(bool bRecreateUniformBuffer) const; + // 初始化所有的系统默认材质. + static void InitDefaultMaterials(); + virtual bool IsPropertyActive(EMaterialProperty InProperty) const; + static uint32 GetFeatureLevelsToCompileForAllMaterials() + + // 返回使用纹理坐标的数量,以及顶点数据是否在着色器图中使用. + void AnalyzeMaterialProperty(EMaterialProperty InProperty, int32& OutNumTextureCoordinates, bool& bOutRequiresVertexData); + + // 遍历所有的FeatureLevel, 可以指定回调. + template + static void IterateOverActiveFeatureLevels(FunctionType InHandler); + + // 访问材质采样器类型的缓存的UEnum类型信息. + static UEnum* GetSamplerTypeEnum(); + + bool UseAnyStreamingTexture() const; + bool HasTextureStreamingData() const; + const TArray& GetTextureStreamingData() const; + FORCEINLINE TArray& GetTextureStreamingData(); + // 纹理流接口. + bool FindTextureStreamingDataIndexRange(FName TextureName, int32& LowerIndex, int32& HigherIndex) const; + void SetTextureStreamingData(const TArray& InTextureStreamingData); + // 返回纹理的比例(LocalSpace Unit / Texture), 用于纹理流矩阵. + virtual float GetTextureDensity(FName TextureName, const struct FMeshUVChannelInfo& UVChannelData) const; + // 预保存. + virtual void PreSave(const class ITargetPlatform* TargetPlatform) override; + // 按名称对纹理流数据进行排序,以加速搜索. 只在需要时排序. + void SortTextureStreamingData(bool bForceSort, bool bFinalSort); + +protected: + uint32 GetFeatureLevelsToCompileForRendering() const; + void UpdateMaterialRenderProxy(FMaterialRenderProxy& Proxy); + +private: + static void PostLoadDefaultMaterials(); + // 材质采样器类型的缓存的UEnum类型信息. + static UEnum* SamplerTypeEnum; +}; + + +// Engine\Source\Runtime\Engine\Classes\Materials\Material.h + +// 材质类, 对应着一个材质资源文件. +class UMaterial : public UMaterialInterface +{ + // 物理材质. + class UPhysicalMaterial* PhysMaterial; + class UPhysicalMaterialMask* PhysMaterialMask; + class UPhysicalMaterial* PhysicalMaterialMap[EPhysicalMaterialMaskColor::MAX]; + + // 材质属性. + FScalarMaterialInput Metallic; + FScalarMaterialInput Specular; + FScalarMaterialInput Anisotropy; + FVectorMaterialInput Normal; + FVectorMaterialInput Tangent; + FColorMaterialInput EmissiveColor; + +#if WITH_EDITORONLY_DATA + // 透射. + FScalarMaterialInput Opacity; + FScalarMaterialInput OpacityMask; +#endif + + TEnumAsByte MaterialDomain; + TEnumAsByte BlendMode; + TEnumAsByte DecalBlendMode; + TEnumAsByte MaterialDecalResponse; + TEnumAsByte ShadingModel; + UPROPERTY(AssetRegistrySearchable) + FMaterialShadingModelField ShadingModels; + +public: + // 材质属性. + float OpacityMaskClipValue; + FVectorMaterialInput WorldPositionOffset; + FScalarMaterialInput Refraction; + FMaterialAttributesInput MaterialAttributes; + FScalarMaterialInput PixelDepthOffset; + FShadingModelMaterialInput ShadingModelFromMaterialExpression; + +#if WITH_EDITORONLY_DATA + FVectorMaterialInput WorldDisplacement; + FScalarMaterialInput TessellationMultiplier; + FColorMaterialInput SubsurfaceColor; + FScalarMaterialInput ClearCoat; + FScalarMaterialInput ClearCoatRoughness; + FScalarMaterialInput AmbientOcclusion; + FVector2MaterialInput CustomizedUVs[8]; +#endif + int32 NumCustomizedUVs; + + // 材质标记. + uint8 bCastDynamicShadowAsMasked : 1; + uint8 bEnableSeparateTranslucency : 1; + uint8 bEnableResponsiveAA : 1; + uint8 bScreenSpaceReflections : 1; + uint8 bContactShadows : 1; + uint8 TwoSided : 1; + uint8 DitheredLODTransition : 1; + uint8 DitherOpacityMask : 1; + uint8 bAllowNegativeEmissiveColor : 1; + + // 透明相关. + TEnumAsByte TranslucencyLightingMode; + uint8 bEnableMobileSeparateTranslucency : 1; + float TranslucencyDirectionalLightingIntensity; + float TranslucentShadowDensityScale; + float TranslucentSelfShadowDensityScale; + float TranslucentSelfShadowSecondDensityScale; + float TranslucentSelfShadowSecondOpacity; + float TranslucentBackscatteringExponent; + FLinearColor TranslucentMultipleScatteringExtinction; + float TranslucentShadowStartOffset; + + // 使用标记. + uint8 bDisableDepthTest : 1; + uint8 bWriteOnlyAlpha : 1; + uint8 bGenerateSphericalParticleNormals : 1; + uint8 bTangentSpaceNormal : 1; + uint8 bUseEmissiveForDynamicAreaLighting : 1; + uint8 bBlockGI : 1; + uint8 bUsedAsSpecialEngineMaterial : 1; + uint8 bUsedWithSkeletalMesh : 1; + uint8 bUsedWithEditorCompositing : 1; + uint8 bUsedWithParticleSprites : 1; + uint8 bUsedWithBeamTrails : 1; + uint8 bUsedWithMeshParticles : 1; + uint8 bUsedWithNiagaraSprites : 1; + uint8 bUsedWithNiagaraRibbons : 1; + uint8 bUsedWithNiagaraMeshParticles : 1; + uint8 bUsedWithGeometryCache : 1; + uint8 bUsedWithStaticLighting : 1; + uint8 bUsedWithMorphTargets : 1; + uint8 bUsedWithSplineMeshes : 1; + uint8 bUsedWithInstancedStaticMeshes : 1; + uint8 bUsedWithGeometryCollections : 1; + uint8 bUsesDistortion : 1; + uint8 bUsedWithClothing : 1; + uint32 bUsedWithWater : 1; + uint32 bUsedWithHairStrands : 1; + uint32 bUsedWithLidarPointCloud : 1; + uint32 bUsedWithVirtualHeightfieldMesh : 1; + uint8 bAutomaticallySetUsageInEditor : 1; + uint8 bFullyRough : 1; + uint8 bUseFullPrecision : 1; + uint8 bUseLightmapDirectionality : 1; + uint8 bUseAlphaToCoverage : 1; + uint32 bForwardRenderUsePreintegratedGFForSimpleIBL : 1; + uint8 bUseHQForwardReflections : 1; + uint8 bUsePlanarForwardReflections : 1; + + // 根据屏幕空间法向变化降低粗糙度. + uint8 bNormalCurvatureToRoughness : 1; + uint8 AllowTranslucentCustomDepthWrites : 1; + uint8 Wireframe : 1; + // 着色频率. + TEnumAsByte ShadingRate; + uint8 bCanMaskedBeAssumedOpaque : 1; + uint8 bIsPreviewMaterial : 1; + uint8 bIsFunctionPreviewMaterial : 1; + uint8 bUseMaterialAttributes : 1; + uint8 bCastRayTracedShadows : 1; + uint8 bUseTranslucencyVertexFog : 1; + uint8 bApplyCloudFogging : 1; + uint8 bIsSky : 1; + uint8 bComputeFogPerPixel : 1; + uint8 bOutputTranslucentVelocity : 1; + uint8 bAllowDevelopmentShaderCompile : 1; + uint8 bIsMaterialEditorStatsMaterial : 1; + TEnumAsByte BlendableLocation; + uint8 BlendableOutputAlpha : 1; + uint8 bEnableStencilTest : 1; + TEnumAsByte StencilCompare; + uint8 StencilRefValue = 0; + TEnumAsByte RefractionMode; + int32 BlendablePriority; + uint8 bIsBlendable : 1; + uint32 UsageFlagWarnings; + float RefractionDepthBias; + FGuid StateId; + float MaxDisplacement; + + // 当渲染器需要获取参数值时,代表这个材质到渲染器的FMaterialRenderProxy衍生物. + class FDefaultMaterialInstance* DefaultMaterialInstance; + +#if WITH_EDITORONLY_DATA + // 编辑器参数. + TMap > EditorParameters; + // 材质图. 编辑器模型下的数据. + class UMaterialGraph* MaterialGraph; +#endif + +private: + // 从地盘序列化而来的内联材质资源. 由游戏线程的PostLoad处理. + TArray LoadedMaterialResources; + // 用于渲染材质的资源列表. + TArray MaterialResources; +#if WITH_EDITOR + // 正在缓存或烘焙的材质资源. + TMap> CachedMaterialResourcesForCooking; +#endif + + // 用于保证在清理之前使用此UMaterial中的各种资源完成RT的标志. + FThreadSafeBool ReleasedByRT; + FMaterialCachedExpressionData CachedExpressionData; + +public: + //~ Begin UMaterialInterface Interface. + virtual UMaterial* GetMaterial() override; + virtual const UMaterial* GetMaterial() const override; + virtual const UMaterial* GetMaterial_Concurrent(TMicRecursionGuard RecursionGuard = TMicRecursionGuard()) const override; + virtual bool GetScalarParameterValue(...) const override; + (......) + void SetShadingModel(EMaterialShadingModel NewModel); + virtual bool IsPropertyActive(EMaterialProperty InProperty) const override; + //~ End UMaterialInterface Interface. + + //~ Begin UObject Interface + virtual void PreSave(const class ITargetPlatform* TargetPlatform) override; + virtual void PostInitProperties() override; + virtual void Serialize(FArchive& Ar) override; + virtual void PostDuplicate(bool bDuplicateForPIE) override; + virtual void PostLoad() override; + virtual void BeginDestroy() override; + virtual bool IsReadyForFinishDestroy() override; + virtual void FinishDestroy() override; + virtual void GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize) override; + static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector); + virtual bool CanBeClusterRoot() const override; + virtual void GetAssetRegistryTags(TArray& OutTags) const override; + //~ End UObject Interface + + // 数据获取接口. + bool IsDefaultMaterial() const; + void ReleaseResources(); + bool IsUsageFlagDirty(EMaterialUsage Usage); + bool IsCompilingOrHadCompileError(ERHIFeatureLevel::Type InFeatureLevel); + + (......) + +private: + // 新版的获取材质数据接口. + bool GetScalarParameterValue_New(...) const; + bool GetVectorParameterValue_New(...) const; + bool GetTextureParameterValue_New(...) const; + bool GetRuntimeVirtualTextureParameterValue_New(...) const; + bool GetFontParameterValue_New(...) const; + + FString GetUsageName(const EMaterialUsage Usage) const; + bool GetUsageByFlag(const EMaterialUsage Usage) const; + bool SetMaterialUsage(bool &bNeedsRecompile, const EMaterialUsage Usage); + + (......) + +private: + virtual void FlushResourceShaderMaps(); + // 缓冲资源或数据. + void CacheResourceShadersForRendering(bool bRegenerateId); + void CacheResourceShadersForCooking(...); + void CacheShadersForResources(...); + +public: + // 静态工具箱或操作接口. + static UMaterial* GetDefaultMaterial(EMaterialDomain Domain); + static void UpdateMaterialShaders(...); + static void BackupMaterialShadersToMemory(...); + static void RestoreMaterialShadersFromMemory(...); + static void CompileMaterialsForRemoteRecompile(...); + static bool GetExpressionParameterName(const UMaterialExpression* Expression, FName& OutName); + static bool CopyExpressionParameters(UMaterialExpression* Source, UMaterialExpression* Destination); + static bool IsParameter(const UMaterialExpression* Expression); + static bool IsDynamicParameter(const UMaterialExpression* Expression); + static const TCHAR* GetMaterialShadingModelString(EMaterialShadingModel InMaterialShadingModel); + static EMaterialShadingModel GetMaterialShadingModelFromString(const TCHAR* InMaterialShadingModelStr); + static const TCHAR* GetBlendModeString(EBlendMode InBlendMode); + static EBlendMode GetBlendModeFromString(const TCHAR* InBlendModeStr); + virtual TArrayView GetReferencedTextures() const override final; + + (......) +}; +``` +# UMaterialInstance +UMaterialInstance是材质实例,不能单独存在,而需要依赖UMaterialInterface类型的父类,意味着父类可以是UMaterialInterface的任意一个子类,但最上层的父类必须是UMaterial。 +```c++ +class UMaterialInstance : public UMaterialInterface +{ + // 物理材质. + class UPhysicalMaterial* PhysMaterial; + class UPhysicalMaterial* PhysicalMaterialMap[EPhysicalMaterialMaskColor::MAX]; + // 材质父亲. + class UMaterialInterface* Parent; + // 当渲染器需要获取参数值时,代表这个材质实例的FMaterialRenderProxy的子类. + class FMaterialInstanceResource* Resource; + + // 可以部分覆盖Parent的属性, 和UMaterial相比, 只是一小部分. + uint8 bHasStaticPermutationResource:1; + uint8 bOverrideSubsurfaceProfile:1; + uint8 TwoSided : 1; + uint8 DitheredLODTransition : 1; + uint8 bCastDynamicShadowAsMasked : 1; + uint8 bIsShadingModelFromMaterialExpression : 1; + TEnumAsByte BlendMode; + float OpacityMaskClipValue; + FMaterialShadingModelField ShadingModels; + + // 覆盖Parent的各种类型的数据. + TArray ScalarParameterValues; + TArray VectorParameterValues; + TArray TextureParameterValues; + TArray RuntimeVirtualTextureParameterValues; + TArray FontParameterValues; + struct FMaterialInstanceBasePropertyOverrides BasePropertyOverrides; + (......) +private: + FStaticParameterSet StaticParameters; + FMaterialCachedParameters CachedLayerParameters; + TArray CachedReferencedTextures; + // 已加载的材质资源. + TArray LoadedMaterialResources; + TArray StaticPermutationMaterialResources; + FThreadSafeBool ReleasedByRT; +public: + // Begin UMaterialInterface interface. + virtual ENGINE_API UMaterial* GetMaterial() override; + virtual ENGINE_API const UMaterial* GetMaterial() const override; + virtual ENGINE_API const UMaterial* GetMaterial_Concurrent(TMicRecursionGuard RecursionGuard = TMicRecursionGuard()) const override; + virtual ENGINE_API FMaterialResource* AllocatePermutationResource(); + (......) + //~ End UMaterialInterface Interface. + //~ Begin UObject Interface. + virtual ENGINE_API void GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize) override; + virtual ENGINE_API void PostInitProperties() override; + virtual ENGINE_API void Serialize(FArchive& Ar) override; + virtual ENGINE_API void PostLoad() override; + virtual ENGINE_API void BeginDestroy() override; + virtual ENGINE_API bool IsReadyForFinishDestroy() override; + virtual ENGINE_API void FinishDestroy() override; + ENGINE_API static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector); + //~ End UObject Interface. + + void GetAllShaderMaps(TArray& OutShaderMaps); + void GetAllParametersOfType(EMaterialParameterType Type, TArray& OutParameterInfo, TArray& OutParameterIds) const; + + (......) +protected: + void CopyMaterialUniformParametersInternal(UMaterialInterface* Source); + bool UpdateParameters(); + ENGINE_API void SetParentInternal(class UMaterialInterface* NewParent, bool RecacheShaders); + + (......) + // 初始化材质实例的资源. + ENGINE_API void InitResources(); + + // 缓存资源. + void CacheResourceShadersForRendering(); + void CacheResourceShadersForRendering(FMaterialResourceDeferredDeletionArray& OutResourcesToFree); + void CacheShadersForResources(...); + void DeleteDeferredResources(FMaterialResourceDeferredDeletionArray& ResourcesToFree); + + ENGINE_API void CopyMaterialInstanceParameters(UMaterialInterface* Source); + (......) +}; +``` + +UMaterialInstance和UMaterial不一样,它需要依附于父亲实例,而且最顶层的父亲必然是UMaterial实例。它**只能覆盖UMaterial的一小部分参数**,通常不会被单独创建,而是以它的两个子类**UMaterialInstanceConstant**和**UMaterialInstanceDynamic**被创建。 + +# FMaterialRenderProxy +FMaterialRenderProxy负责接收游戏线程代表的数据,然后传递给渲染器去处理和渲染。 +```c++ +// Engine\Source\Runtime\Engine\Public\MaterialShared.h + +class FMaterialRenderProxy : public FRenderResource +{ +public: + // 缓存数据. + mutable FUniformExpressionCacheContainer UniformExpressionCache; + mutable FImmutableSamplerState ImmutableSamplerState; + + // 构造/析构函数. + ENGINE_API FMaterialRenderProxy(); + ENGINE_API virtual ~FMaterialRenderProxy(); + + // 计算表达式并存储到OutUniformExpressionCache. + void ENGINE_API EvaluateUniformExpressions(FUniformExpressionCache& OutUniformExpressionCache, const FMaterialRenderContext& Context, class FRHICommandList* CommandListIfLocalMode = nullptr) const; + + // UniformExpression接口. + void ENGINE_API CacheUniformExpressions(bool bRecreateUniformBuffer); + void ENGINE_API CacheUniformExpressions_GameThread(bool bRecreateUniformBuffer); + void ENGINE_API InvalidateUniformExpressionCache(bool bRecreateUniformBuffer); + void ENGINE_API UpdateUniformExpressionCacheIfNeeded(ERHIFeatureLevel::Type InFeatureLevel) const; + + // 返回有效的FMaterial实例. + const class FMaterial* GetMaterial(ERHIFeatureLevel::Type InFeatureLevel) const; + // 查找用于渲染此FMaterialRenderProxy的FMaterial实例. + virtual const FMaterial& GetMaterialWithFallback(ERHIFeatureLevel::Type InFeatureLevel, const FMaterialRenderProxy*& OutFallbackMaterialRenderProxy) const = 0; + virtual FMaterial* GetMaterialNoFallback(ERHIFeatureLevel::Type InFeatureLevel) const { return NULL; } + // 获取对应的UMaterialInterface实例. + virtual UMaterialInterface* GetMaterialInterface() const { return NULL; } + + // 获取材质属性的值. + virtual bool GetVectorValue(const FHashedMaterialParameterInfo& ParameterInfo, FLinearColor* OutValue, const FMaterialRenderContext& Context) const = 0; + virtual bool GetScalarValue(const FHashedMaterialParameterInfo& ParameterInfo, float* OutValue, const FMaterialRenderContext& Context) const = 0; + virtual bool GetTextureValue(const FHashedMaterialParameterInfo& ParameterInfo,const UTexture** OutValue, const FMaterialRenderContext& Context) const = 0; + virtual bool GetTextureValue(const FHashedMaterialParameterInfo& ParameterInfo, const URuntimeVirtualTexture** OutValue, const FMaterialRenderContext& Context) const = 0; + + bool IsDeleted() const; + void MarkForGarbageCollection(); + bool IsMarkedForGarbageCollection() const; + + // FRenderResource interface. + ENGINE_API virtual void InitDynamicRHI() override; + ENGINE_API virtual void ReleaseDynamicRHI() override; + ENGINE_API virtual void ReleaseResource() override; + + // 获取静态的材质渲染代表的映射表. + ENGINE_API static const TSet& GetMaterialRenderProxyMap(); + + void SetSubsurfaceProfileRT(const USubsurfaceProfile* Ptr); + const USubsurfaceProfile* GetSubsurfaceProfileRT() const; + + ENGINE_API static void UpdateDeferredCachedUniformExpressions(); + static inline bool HasDeferredUniformExpressionCacheRequests(); + + int32 GetExpressionCacheSerialNumber() const { return UniformExpressionCacheSerialNumber; } + +private: + const USubsurfaceProfile* SubsurfaceProfileRT; + mutable int32 UniformExpressionCacheSerialNumber = 0; + + // 材质标记. + mutable int8 MarkedForGarbageCollection : 1; + mutable int8 DeletedFlag : 1; + mutable int8 ReleaseResourceFlag : 1; + mutable int8 HasVirtualTextureCallbacks : 1; + + // 追踪在所有场景的所有材质渲染代表. 只可在渲染线程访问. 用来传播新的着色器映射到渲染所用的材质. + ENGINE_API static TSet MaterialRenderProxyMap; + ENGINE_API static TSet DeferredUniformExpressionCacheRequests; +}; +``` +我们将注意力放到两个重要的子类:FDefaultMaterialInstance和FMaterialInstanceResource,它们的定义如下: +```c++ +// Engine\Source\Runtime\Engine\Private\Materials\Material.cpp + +// 用于渲染UMaterial的默认渲染代表, 默认的参数值已经存储于FMaterialUniformExpressionXxxParameter对象, 此资源值用来存储选中的颜色. +class FDefaultMaterialInstance : public FMaterialRenderProxy +{ +public: + + // 游戏线程销毁接口. + void GameThread_Destroy() + { + FDefaultMaterialInstance* Resource = this; + ENQUEUE_RENDER_COMMAND(FDestroyDefaultMaterialInstanceCommand)( + [Resource](FRHICommandList& RHICmdList) + { + delete Resource; + }); + } + + // FMaterialRenderProxy interface. + // 获取材质接口. + virtual const FMaterial& GetMaterialWithFallback(ERHIFeatureLevel::Type InFeatureLevel, const FMaterialRenderProxy*& OutFallbackMaterialRenderProxy) const + { + const FMaterialResource* MaterialResource = Material->GetMaterialResource(InFeatureLevel); + if (MaterialResource && MaterialResource->GetRenderingThreadShaderMap()) + { + return *MaterialResource; + } + + OutFallbackMaterialRenderProxy = &GetFallbackRenderProxy(); + return OutFallbackMaterialRenderProxy->GetMaterialWithFallback(InFeatureLevel, OutFallbackMaterialRenderProxy); + } + virtual FMaterial* GetMaterialNoFallback(ERHIFeatureLevel::Type InFeatureLevel) const + { + return Material->GetMaterialResource(InFeatureLevel); + } + + // 获取对应的UMaterialInterface接口. + virtual UMaterialInterface* GetMaterialInterface() const override + { + return Material; + } + + // 获取向量的参数值. + virtual bool GetVectorValue(const FHashedMaterialParameterInfo& ParameterInfo, FLinearColor* OutValue, const FMaterialRenderContext& Context) const + { + const FMaterialResource* MaterialResource = Material->GetMaterialResource(Context.Material.GetFeatureLevel()); + if(MaterialResource && MaterialResource->GetRenderingThreadShaderMap()) + { + return false; + } + else + { + return GetFallbackRenderProxy().GetVectorValue(ParameterInfo, OutValue, Context); + } + } + // 获取标量的参数值. + virtual bool GetScalarValue(const FHashedMaterialParameterInfo& ParameterInfo, float* OutValue, const FMaterialRenderContext& Context) const + { + const FMaterialResource* MaterialResource = Material->GetMaterialResource(Context.Material.GetFeatureLevel()); + if(MaterialResource && MaterialResource->GetRenderingThreadShaderMap()) + { + static FName NameSubsurfaceProfile(TEXT("__SubsurfaceProfile")); + if (ParameterInfo.Name == NameSubsurfaceProfile) + { + const USubsurfaceProfile* MySubsurfaceProfileRT = GetSubsurfaceProfileRT(); + + int32 AllocationId = 0; + if(MySubsurfaceProfileRT) + { + // can be optimized (cached) + AllocationId = GSubsurfaceProfileTextureObject.FindAllocationId(MySubsurfaceProfileRT); + } + else + { + // no profile specified means we use the default one stored at [0] which is human skin + AllocationId = 0; + } + + *OutValue = AllocationId / 255.0f; + + return true; + } + + return false; + } + else + { + return GetFallbackRenderProxy().GetScalarValue(ParameterInfo, OutValue, Context); + } + } + // 获取纹理的参数值. + virtual bool GetTextureValue(const FHashedMaterialParameterInfo& ParameterInfo,const UTexture** OutValue, const FMaterialRenderContext& Context) const + { + const FMaterialResource* MaterialResource = Material->GetMaterialResource(Context.Material.GetFeatureLevel()); + if(MaterialResource && MaterialResource->GetRenderingThreadShaderMap()) + { + return false; + } + else + { + return GetFallbackRenderProxy().GetTextureValue(ParameterInfo,OutValue,Context); + } + } + virtual bool GetTextureValue(const FHashedMaterialParameterInfo& ParameterInfo, const URuntimeVirtualTexture** OutValue, const FMaterialRenderContext& Context) const + { + const FMaterialResource* MaterialResource = Material->GetMaterialResource(Context.Material.GetFeatureLevel()); + if (MaterialResource && MaterialResource->GetRenderingThreadShaderMap()) + { + return false; + } + else + { + return GetFallbackRenderProxy().GetTextureValue(ParameterInfo, OutValue, Context); + } + } + + // FRenderResource interface. + virtual FString GetFriendlyName() const { return Material->GetName(); } + + // Constructor. + FDefaultMaterialInstance(UMaterial* InMaterial); + +private: + // 获取备份的材质渲染代理. + FMaterialRenderProxy& GetFallbackRenderProxy() const + { + return *(UMaterial::GetDefaultMaterial(Material->MaterialDomain)->GetRenderProxy()); + } + + // 对应的材质实例. + UMaterial* Material; +}; + +// Engine\Source\Runtime\Engine\Private\Materials\MaterialInstanceSupport.h +// 渲染UMaterialInstance的材质资源. +class FMaterialInstanceResource: public FMaterialRenderProxy +{ +public: + // 存储材质实例的名称和值的配对. + template + struct TNamedParameter + { + FHashedMaterialParameterInfo Info; + ValueType Value; + }; + + FMaterialInstanceResource(UMaterialInstance* InOwner); + + void GameThread_Destroy() + { + FMaterialInstanceResource* Resource = this; + ENQUEUE_RENDER_COMMAND(FDestroyMaterialInstanceResourceCommand)( + [Resource](FRHICommandList& RHICmdList) + { + delete Resource; + }); + } + + // FRenderResource interface. + virtual FString GetFriendlyName() const override { return Owner->GetName(); } + + // FMaterialRenderProxy interface. + // 获取材质渲染资源. + virtual const FMaterial& GetMaterialWithFallback(ERHIFeatureLevel::Type FeatureLevel, const FMaterialRenderProxy*& OutFallbackMaterialRenderProxy) const override; + virtual FMaterial* GetMaterialNoFallback(ERHIFeatureLevel::Type FeatureLevel) const override; + virtual UMaterialInterface* GetMaterialInterface() const override; + + // 获取材质的值. + virtual bool GetVectorValue(const FHashedMaterialParameterInfo& ParameterInfo, FLinearColor* OutValue, const FMaterialRenderContext& Context) const override; + virtual bool GetScalarValue(const FHashedMaterialParameterInfo& ParameterInfo, float* OutValue, const FMaterialRenderContext& Context) const override; + virtual bool GetTextureValue(const FHashedMaterialParameterInfo& ParameterInfo, const UTexture** OutValue, const FMaterialRenderContext& Context) const override; + virtual bool GetTextureValue(const FHashedMaterialParameterInfo& ParameterInfo, const URuntimeVirtualTexture** OutValue, const FMaterialRenderContext& Context) const override; + + void GameThread_SetParent(UMaterialInterface* ParentMaterialInterface); + void InitMIParameters(struct FMaterialInstanceParameterSet& ParameterSet); + void RenderThread_ClearParameters() + { + VectorParameterArray.Empty(); + ScalarParameterArray.Empty(); + TextureParameterArray.Empty(); + RuntimeVirtualTextureParameterArray.Empty(); + InvalidateUniformExpressionCache(false); + } + + // 更新参数. + template + void RenderThread_UpdateParameter(const FHashedMaterialParameterInfo& ParameterInfo, const ValueType& Value ) + { + LLM_SCOPE(ELLMTag::MaterialInstance); + + InvalidateUniformExpressionCache(false); + TArray >& ValueArray = GetValueArray(); + const int32 ParameterCount = ValueArray.Num(); + for (int32 ParameterIndex = 0; ParameterIndex < ParameterCount; ++ParameterIndex) + { + TNamedParameter& Parameter = ValueArray[ParameterIndex]; + if (Parameter.Info == ParameterInfo) + { + Parameter.Value = Value; + return; + } + } + TNamedParameter NewParameter; + NewParameter.Info = ParameterInfo; + NewParameter.Value = Value; + ValueArray.Add(NewParameter); + } + + // 查找指定名字的参数值. + template + const ValueType* RenderThread_FindParameterByName(const FHashedMaterialParameterInfo& ParameterInfo) const + { + const TArray >& ValueArray = GetValueArray(); + const int32 ParameterCount = ValueArray.Num(); + for (int32 ParameterIndex = 0; ParameterIndex < ParameterCount; ++ParameterIndex) + { + const TNamedParameter& Parameter = ValueArray[ParameterIndex]; + if (Parameter.Info == ParameterInfo) + { + return &Parameter.Value; + } + } + return NULL; + } +private: + template TArray >& GetValueArray(); + + // 材质实例的父亲. + UMaterialInterface* Parent; + // 游戏线程的父亲. + UMaterialInterface* GameThreadParent; + // 所属的材质实例. + UMaterialInstance* Owner; + + // 各种类型的参数值列表. + TArray > VectorParameterArray; + TArray > ScalarParameterArray; + TArray > TextureParameterArray; + TArray > RuntimeVirtualTextureParameterArray; +}; +``` +需要格外注意的是,FMaterialRenderProxy既会被游戏线程处理,又会被渲染线程处理,需要小心注意它们之间的数据访问和接口调用。带有GameThread的是专用于游戏线程,带有RenderThread的专用于渲染线程,如果没有特别说明,一般(非绝对)用于渲染线程。 + +# FMaterial & FMaterialResource +FMaterial有3个功能: +- 表示材质到材质的编译过程,并提供可扩展性钩子(CompileProperty等) 。 +- 将材质数据传递到渲染器,并使用函数访问材质属性。 +- 存储缓存的shader map和其他来自编译的瞬态输出,这对异步着色器编译是必要的。 + +下面是FMaterial的定义: +```c++ +// Engine\Source\Runtime\Engine\Public\MaterialShared.h + +class FMaterial +{ +public: +#if UE_CHECK_FMATERIAL_LIFETIME + uint32 AddRef() const; + uint32 Release() const; + inline uint32 GetRefCount() const { return uint32(NumDebugRefs.GetValue()); } + + mutable FThreadSafeCounter NumDebugRefs; +#else + FORCEINLINE uint32 AddRef() const { return 0u; } + FORCEINLINE uint32 Release() const { return 0u; } + FORCEINLINE uint32 GetRefCount() const { return 0u; } +#endif + + FMaterial(); + ENGINE_API virtual ~FMaterial(); + + // 缓存shader. + ENGINE_API bool CacheShaders(EShaderPlatform Platform, const ITargetPlatform* TargetPlatform = nullptr); + ENGINE_API bool CacheShaders(const FMaterialShaderMapId& ShaderMapId, EShaderPlatform Platform, const ITargetPlatform* TargetPlatform = nullptr); + + // 是否需要缓存指定shader type的数据. + ENGINE_API virtual bool ShouldCache(EShaderPlatform Platform, const FShaderType* ShaderType, const FVertexFactoryType* VertexFactoryType) const; + ENGINE_API bool ShouldCachePipeline(EShaderPlatform Platform, const FShaderPipelineType* PipelineType, const FVertexFactoryType* VertexFactoryType) const; + + // 序列化. + ENGINE_API virtual void LegacySerialize(FArchive& Ar); + void SerializeInlineShaderMap(FArchive& Ar); + + // ShaderMap接口. + void RegisterInlineShaderMap(bool bLoadedByCookedMaterial); + void ReleaseShaderMap(); + void DiscardShaderMap(); + + // 材质属性. + ENGINE_API virtual void GetShaderMapId(EShaderPlatform Platform, const ITargetPlatform* TargetPlatform, FMaterialShaderMapId& OutId) const; + virtual EMaterialDomain GetMaterialDomain() const = 0; // See EMaterialDomain. + virtual bool IsTwoSided() const = 0; + virtual bool IsDitheredLODTransition() const = 0; + virtual bool IsTranslucencyWritingCustomDepth() const { return false; } + virtual bool IsTranslucencyWritingVelocity() const { return false; } + virtual bool IsTangentSpaceNormal() const { return false; } + + (......) + + // 是否需要保存到磁盘. + virtual bool IsPersistent() const = 0; + // 获取材质实例. + virtual UMaterialInterface* GetMaterialInterface() const { return NULL; } + + ENGINE_API bool HasValidGameThreadShaderMap() const; + inline bool ShouldCastDynamicShadows() const; + EMaterialQualityLevel::Type GetQualityLevel() const + + // 数据访问接口. + ENGINE_API const FUniformExpressionSet& GetUniformExpressions() const; + ENGINE_API TArrayView GetUniformTextureExpressions(EMaterialTextureParameterType Type) const; + ENGINE_API TArrayView GetUniformVectorParameterExpressions() const; + ENGINE_API TArrayView GetUniformScalarParameterExpressions() const; + inline TArrayView GetUniform2DTextureExpressions() const { return GetUniformTextureExpressions(EMaterialTextureParameterType::Standard2D); } + inline TArrayView GetUniformCubeTextureExpressions() const { return GetUniformTextureExpressions(EMaterialTextureParameterType::Cube); } + inline TArrayView GetUniform2DArrayTextureExpressions() const { return GetUniformTextureExpressions(EMaterialTextureParameterType::Array2D); } + inline TArrayView GetUniformVolumeTextureExpressions() const { return GetUniformTextureExpressions(EMaterialTextureParameterType::Volume); } + inline TArrayView GetUniformVirtualTextureExpressions() const { return GetUniformTextureExpressions(EMaterialTextureParameterType::Virtual); } + + const FStaticFeatureLevel GetFeatureLevel() const { return FeatureLevel; } + bool GetUsesDynamicParameter() const; + ENGINE_API bool RequiresSceneColorCopy_GameThread() const; + ENGINE_API bool RequiresSceneColorCopy_RenderThread() const; + ENGINE_API bool NeedsSceneTextures() const; + ENGINE_API bool NeedsGBuffer() const; + ENGINE_API bool UsesEyeAdaptation() const; + ENGINE_API bool UsesGlobalDistanceField_GameThread() const; + ENGINE_API bool UsesWorldPositionOffset_GameThread() const; + + // 材质标记. + ENGINE_API bool MaterialModifiesMeshPosition_RenderThread() const; + ENGINE_API bool MaterialModifiesMeshPosition_GameThread() const; + ENGINE_API bool MaterialUsesPixelDepthOffset() const; + ENGINE_API bool MaterialUsesDistanceCullFade_GameThread() const; + ENGINE_API bool MaterialUsesSceneDepthLookup_RenderThread() const; + ENGINE_API bool MaterialUsesSceneDepthLookup_GameThread() const; + ENGINE_API bool UsesCustomDepthStencil_GameThread() const; + ENGINE_API bool MaterialMayModifyMeshPosition() const; + ENGINE_API bool MaterialUsesAnisotropy_GameThread() const; + ENGINE_API bool MaterialUsesAnisotropy_RenderThread() const; + + // shader map接口. + class FMaterialShaderMap* GetGameThreadShaderMap() const + { + return GameThreadShaderMap; + } + void SetGameThreadShaderMap(FMaterialShaderMap* InMaterialShaderMap) + { + GameThreadShaderMap = InMaterialShaderMap; + + TRefCountPtr ShaderMap = GameThreadShaderMap; + TRefCountPtr Material = this; + + // 将游戏线程的shader map设置到渲染线程. + ENQUEUE_RENDER_COMMAND(SetGameThreadShaderMap)([Material = MoveTemp(Material), ShaderMap = MoveTemp(ShaderMap)](FRHICommandListImmediate& RHICmdList) mutable + { + Material->RenderingThreadShaderMap = MoveTemp(ShaderMap); + }); + } + void SetInlineShaderMap(FMaterialShaderMap* InMaterialShaderMap); + ENGINE_API class FMaterialShaderMap* GetRenderingThreadShaderMap() const; + ENGINE_API void SetRenderingThreadShaderMap(const TRefCountPtr& InMaterialShaderMap); + + ENGINE_API virtual void AddReferencedObjects(FReferenceCollector& Collector); + + virtual TArrayView GetReferencedTextures() const = 0; + + // 获取shader/shader pipeline. + template + TShaderRef GetShader(FVertexFactoryType* VertexFactoryType, const typename ShaderType::FPermutationDomain& PermutationVector, bool bFatalIfMissing = true) const; + template + TShaderRef GetShader(FVertexFactoryType* VertexFactoryType, int32 PermutationId = 0, bool bFatalIfMissing = true) const; + ENGINE_API FShaderPipelineRef GetShaderPipeline(class FShaderPipelineType* ShaderPipelineType, FVertexFactoryType* VertexFactoryType, bool bFatalIfNotFound = true) const; + + // 材质接口. + virtual FString GetMaterialUsageDescription() const = 0; + virtual bool GetAllowDevelopmentShaderCompile()const{ return true; } + virtual EMaterialShaderMapUsage::Type GetMaterialShaderMapUsage() const { return EMaterialShaderMapUsage::Default; } + ENGINE_API bool GetMaterialExpressionSource(FString& OutSource); + ENGINE_API bool WritesEveryPixel(bool bShadowPass = false) const; + virtual void SetupExtaCompilationSettings(const EShaderPlatform Platform, FExtraShaderCompilerSettings& Settings) const; + + (......) + +protected: + const FMaterialShaderMap* GetShaderMapToUse() const; + + virtual int32 CompilePropertyAndSetMaterialProperty(EMaterialProperty Property, class FMaterialCompiler* Compiler, EShaderFrequency OverrideShaderFrequency = SF_NumFrequencies, bool bUsePreviousFrameTime = false) const = 0; + + void SetQualityLevelProperties(ERHIFeatureLevel::Type InFeatureLevel, EMaterialQualityLevel::Type InQualityLevel = EMaterialQualityLevel::Num); + virtual EMaterialShaderMapUsage::Type GetShaderMapUsage() const; + virtual FGuid GetMaterialId() const = 0; + ENGINE_API void GetDependentShaderAndVFTypes(EShaderPlatform Platform, TArray& OutShaderTypes, TArray& OutShaderPipelineTypes, TArray& OutVFTypes) const; + bool GetLoadedCookedShaderMapId() const; + +private: + // 游戏线程和渲染线程的shader map. + TRefCountPtr GameThreadShaderMap; + TRefCountPtr RenderingThreadShaderMap; + + // 质量等级. + EMaterialQualityLevel::Type QualityLevel; + ERHIFeatureLevel::Type FeatureLevel; + + // 特殊标记. + uint32 bStencilDitheredLOD : 1; + uint32 bContainsInlineShaders : 1; + uint32 bLoadedCookedShaderMapId : 1; + + bool BeginCompileShaderMap( + const FMaterialShaderMapId& ShaderMapId, + const FStaticParameterSet &StaticParameterSet, + EShaderPlatform Platform, + TRefCountPtr& OutShaderMap, + const ITargetPlatform* TargetPlatform = nullptr); + void SetupMaterialEnvironment( + EShaderPlatform Platform, + const FShaderParametersMetadata& InUniformBufferStruct, + const FUniformExpressionSet& InUniformExpressionSet, + FShaderCompilerEnvironment& OutEnvironment + ) const; + + ENGINE_API TShaderRef GetShader(class FMeshMaterialShaderType* ShaderType, FVertexFactoryType* VertexFactoryType, int32 PermutationId, bool bFatalIfMissing = true) const; +}; +``` +由上面可知,FMaterial集大之所成,囊括了材质、Shader、VertexFactory、ShaderPipeline、ShaderMap等各种数据和操作接口,是这些数据的集散地。不过,它只是个抽象的父类,具体的功能需要由子类实现。它的子类只有FMaterialResource: +```c++ +// 实现FMaterial的接口, 用于渲染UMaterial或UMaterialInstance. +class FMaterialResource : public FMaterial +{ +public: + ENGINE_API FMaterialResource(); + ENGINE_API virtual ~FMaterialResource(); + + // 设置材质. + void SetMaterial(UMaterial* InMaterial, UMaterialInstance* InInstance, ERHIFeatureLevel::Type InFeatureLevel, EMaterialQualityLevel::Type InQualityLevel = EMaterialQualityLevel::Num) + { + Material = InMaterial; + MaterialInstance = InInstance; + SetQualityLevelProperties(InFeatureLevel, InQualityLevel); + } + + ENGINE_API uint32 GetNumVirtualTextureStacks() const; + ENGINE_API virtual FString GetMaterialUsageDescription() const override; + + // FMaterial interface. + ENGINE_API virtual void GetShaderMapId(EShaderPlatform Platform, const ITargetPlatform* TargetPlatform, FMaterialShaderMapId& OutId) const override; + ENGINE_API virtual EMaterialDomain GetMaterialDomain() const override; + ENGINE_API virtual bool IsTwoSided() const override; + ENGINE_API virtual bool IsDitheredLODTransition() const override; + ENGINE_API virtual bool IsTranslucencyWritingCustomDepth() const override; + ENGINE_API virtual bool IsTranslucencyWritingVelocity() const override; + ENGINE_API virtual bool IsTangentSpaceNormal() const override; + ENGINE_API virtual EMaterialShadingRate GetShadingRate() const override; + (......) + // 材质接口. + inline const UMaterial* GetMaterial() const { return Material; } + inline const UMaterialInstance* GetMaterialInstance() const { return MaterialInstance; } + inline void SetMaterial(UMaterial* InMaterial) { Material = InMaterial; } + inline void SetMaterialInstance(UMaterialInstance* InMaterialInstance) { MaterialInstance = InMaterialInstance; } +protected: + // 对应的材质. + UMaterial* Material; + // 对应的材质实例. + UMaterialInstance* MaterialInstance; + + // 编译指定材质属性的入口, 须有SetMaterialProperty调用. + ENGINE_API virtual int32 CompilePropertyAndSetMaterialProperty(EMaterialProperty Property, class FMaterialCompiler* Compiler, EShaderFrequency OverrideShaderFrequency, bool bUsePreviousFrameTime) const override; + + ENGINE_API virtual bool HasVertexPositionOffsetConnected() const override; + ENGINE_API virtual bool HasPixelDepthOffsetConnected() const override; + ENGINE_API virtual bool HasMaterialAttributesConnected() const override; + (......) +}; +``` +FMaterialResource只是实现了FMaterial未实现的接口,并且存储了UMaterial或UMaterialInstance的实例。如果UMaterialInstance和UMaterial的实例都有效的情况下,那么它们重叠的数据会优先取UMaterialInstance的数据,比如: +```c++ +// 获取着色模型域 +FMaterialShadingModelField FMaterialResource::GetShadingModels() const +{ + // 优先选用MaterialInstance的数据. + return MaterialInstance ? MaterialInstance->GetShadingModels() : Material->GetShadingModels(); +} +``` +渲染资源除了FMaterial之外,还有个比较核心的概念就是**FMaterialRenderContext**,它保存了**FMaterialRenderProxy和FMaterial**之间的关联配对: +```c++ +struct ENGINE_API FMaterialRenderContext +{ + // 用于材质shader的材质渲染代表. + const FMaterialRenderProxy* MaterialRenderProxy; + // 材质渲染资源. + const FMaterial& Material; + + // 是否显示选中时的颜色. + bool bShowSelection; + + // 构造函数. + FMaterialRenderContext(const FMaterialRenderProxy* InMaterialRenderProxy, const FMaterial& InMaterial, const FSceneView* InView); +}; +``` +FMaterialRenderContext较多地用于材质各种类型的接口的形参,比如: +```c++ +// FDefaultMaterialInstance中的获取向量参数值, 用到了FMaterialRenderContext参数. +virtual bool FDefaultMaterialInstance::GetVectorValue(const FHashedMaterialParameterInfo& ParameterInfo, FLinearColor* OutValue, const FMaterialRenderContext& Context) const +{ + const FMaterialResource* MaterialResource = Material->GetMaterialResource(Context.Material.GetFeatureLevel()); + + if(MaterialResource && MaterialResource->GetRenderingThreadShaderMap()) + { + return false; + } + else + { + return GetFallbackRenderProxy().GetVectorValue(ParameterInfo, OutValue, Context); + } +} +``` + +# 材质渲染 +材质数据的发起者依然是游戏线程侧的资源,一般是从磁盘加载的二进制资源,然后序列化成UMaterialInterface实例,或者由运行时动态创建并设置材质数据。不过绝大多数是由磁盘加载而来。 +当使用了材质的图元组件在被要求**收集MeshBatch**的时候,可以将其使用的UMaterialInterface对应的**FMaterialRenderProxy传递到FMeshBatchElement**中。下面以StaticMesh为例: +```c++ +// Engine\Source\Runtime\Engine\Private\StaticMeshRender.cpp + +bool FStaticMeshSceneProxy::GetMeshElement( + int32 LODIndex, + int32 BatchIndex, + int32 SectionIndex, + uint8 InDepthPriorityGroup, + bool bUseSelectionOutline, + bool bAllowPreCulledIndices, + FMeshBatch& OutMeshBatch) const +{ + const ERHIFeatureLevel::Type FeatureLevel = GetScene().GetFeatureLevel(); + const FStaticMeshLODResources& LOD = RenderData->LODResources[LODIndex]; + const FStaticMeshVertexFactories& VFs = RenderData->LODVertexFactories[LODIndex]; + const FStaticMeshSection& Section = LOD.Sections[SectionIndex]; + const FLODInfo& ProxyLODInfo = LODs[LODIndex]; + + // 获取材质的各种实例(包含UMaterialInterface, FMaterialRenderProxy和FMaterial) + UMaterialInterface* MaterialInterface = ProxyLODInfo.Sections[SectionIndex].Material; + FMaterialRenderProxy* MaterialRenderProxy = MaterialInterface->GetRenderProxy(); + const FMaterial* Material = MaterialRenderProxy->GetMaterial(FeatureLevel); + + FMeshBatchElement& OutMeshBatchElement = OutMeshBatch.Elements[0]; + + // 处理顶点工厂 + const FVertexFactory* VertexFactory = nullptr; + if (ProxyLODInfo.OverrideColorVertexBuffer) + { + (......) + } + + (......) + + if(NumPrimitives > 0) + { + OutMeshBatch.SegmentIndex = SectionIndex; + OutMeshBatch.LODIndex = LODIndex; + + // 赋值材质和渲染代表. + OutMeshBatch.MaterialRenderProxy = MaterialRenderProxy; + + (......) + } +} +``` +因此,可以知道,在组件收集网格元素的时候,材质的所有类型的数据已经准备好,并且可以被访问了。说明在游戏线程阶段,材质的各种类型的实例已经被加载、设置和创建。我们继续深究到底是什么时候创建的。首先看FMaterialRenderProxy,不同的UMaterialInterface的子类稍有不一样,具体如下代码所示: +```c++ +// Engine\Source\Runtime\Engine\Private\Materials\MaterialInstance.cpp +void UMaterialInstance::PostInitProperties() +{ + Super::PostInitProperties(); + + if(!HasAnyFlags(RF_ClassDefaultObject)) + { + // 创建FMaterialRenderProxy. + Resource = new FMaterialInstanceResource(this); + } +} + +FMaterialRenderProxy* UMaterialInstance::GetRenderProxy() const +{ + return Resource; +} + +// Engine\Source\Runtime\Engine\Private\Materials\Material.cpp + +void UMaterial::PostInitProperties() +{ + Super::PostInitProperties(); + if(!HasAnyFlags(RF_ClassDefaultObject)) + { + // 创建FMaterialRenderProxy. + DefaultMaterialInstance = new FDefaultMaterialInstance(this); + } + + FPlatformMisc::CreateGuid(StateId); +} + +FMaterialRenderProxy* UMaterial::GetRenderProxy() const +{ + return DefaultMaterialInstance; +} +``` +由此可推断,UMaterialInstance对应的FMaterialRenderProxy是在子类的PostInitProperties阶段被创建的。 +我们继续查明UMaterialInterface获取对应的FMaterial实例是哪个接口哪个成员: +```c++ +// Engine\Source\Runtime\Engine\Private\Materials\Material.cpp +// 获取UMaterial对应的FMaterialResource(FMaterial的子类)实例. +FMaterialResource* UMaterial::GetMaterialResource(ERHIFeatureLevel::Type InFeatureLevel, EMaterialQualityLevel::Type QualityLevel) +{ + if (QualityLevel == EMaterialQualityLevel::Num) + { + QualityLevel = GetCachedScalabilityCVars().MaterialQualityLevel; + } + return FindMaterialResource(MaterialResources, InFeatureLevel, QualityLevel, true); +} +``` +以上可以知道,是查找UMaterial::MaterialResources,那么继续深究其何时被创建: +```c++ +FMaterialResource* FindOrCreateMaterialResource(TArray& MaterialResources, + UMaterial* OwnerMaterial, + UMaterialInstance* OwnerMaterialInstance, + ERHIFeatureLevel::Type InFeatureLevel, + EMaterialQualityLevel::Type InQualityLevel) +{ + (......) + + FMaterialResource* CurrentResource = FindMaterialResource(MaterialResources, InFeatureLevel, QualityLevelForResource, false); + + // 如果当前资源列表不存在就创建新的FMaterialResource实例. + if (!CurrentResource) + { + // 优先使用材质实例的的接口来创建. + CurrentResource = OwnerMaterialInstance ? OwnerMaterialInstance->AllocatePermutationResource() : OwnerMaterial->AllocateResource(); + CurrentResource->SetMaterial(OwnerMaterial, OwnerMaterialInstance, InFeatureLevel, QualityLevelForResource); + // 添加到FMaterialResource实例列表. + MaterialResources.Add(CurrentResource); + } + + (......) + + return CurrentResource; +} +``` +以上创建FMaterialResource实例时会优先使用有效的OwnerMaterialInstance,然后才使用UMaterial的接口,下面进入它们创建FMaterialResource实例的接口: +```c++ +FMaterialResource* UMaterialInstance::AllocatePermutationResource() +{ + return new FMaterialResource(); +} + +FMaterialResource* UMaterial::AllocateResource() +{ + return new FMaterialResource(); +} +``` +好家伙,逻辑一样的,都是直接new一个FMaterialResource对象并返回。下面继续追踪有哪些接口会调用FindOrCreateMaterialResource: +- ProcessSerializedInlineShaderMaps +- UMaterial::PostLoad +- UMaterial::CacheResourceShadersForRendering +- UMaterial::AllMaterialsCacheResourceShadersForRendering +- UMaterial::ForceRecompileForRendering +- UMaterial::PostEditChangePropertyInternal +- UMaterial::SetMaterialUsage +- UMaterial::UpdateMaterialShaders +- UMaterial::UpdateMaterialShaderCacheAndTextureReferences + +以上接口都会直接或间接调用到FindOrCreateMaterialResource接口,从而触发FMaterialResource对象的创建。但在运行时的版本中,通常由UMaterial::PostLoad触发,调用堆栈如下所示: +- UMaterial::PostLoad + - ProcessSerializedInlineShaderMaps + - FindOrCreateMaterialResource + +此外,UMaterialInstance的部分接口也会触发FMaterialResource实例的创建,此文不继续追踪了。 + +我们继续研究FMaterial的GameThreadShaderMap和RenderingThreadShaderMap是在何处何时被设置和传递的: +```c++ +// 直接设置RenderingThreadShaderMap +void FMaterial::SetRenderingThreadShaderMap(const TRefCountPtr& InMaterialShaderMap) +{ + RenderingThreadShaderMap = InMaterialShaderMap; +} + +// 设置游戏线程ShaderMap. +void FMaterial::SetGameThreadShaderMap(FMaterialShaderMap* InMaterialShaderMap) +{ + GameThreadShaderMap = InMaterialShaderMap; + + TRefCountPtr ShaderMap = GameThreadShaderMap; + TRefCountPtr Material = this; + // 向渲染线程推送设置ShaderMap的指令. + ENQUEUE_RENDER_COMMAND(SetGameThreadShaderMap)([Material = MoveTemp(Material), ShaderMap = MoveTemp(ShaderMap)](FRHICommandListImmediate& RHICmdList) mutable + { + Material->RenderingThreadShaderMap = MoveTemp(ShaderMap); + }); +} + +// 设置内联ShaderMap +void FMaterial::SetInlineShaderMap(FMaterialShaderMap* InMaterialShaderMap) +{ + GameThreadShaderMap = InMaterialShaderMap; + bContainsInlineShaders = true; + bLoadedCookedShaderMapId = true; + + TRefCountPtr ShaderMap = GameThreadShaderMap; + TRefCountPtr Material = this; + // 向渲染线程推送设置ShaderMap的指令. + ENQUEUE_RENDER_COMMAND(SetInlineShaderMap)([Material = MoveTemp(Material), ShaderMap = MoveTemp(ShaderMap)](FRHICommandListImmediate& RHICmdList) mutable + { + Material->RenderingThreadShaderMap = MoveTemp(ShaderMap); + }); +} +``` +以上可以设置FMaterial的RenderingThreadShaderMap有3个接口,继续追踪有哪些接口会调用到它们: +- FMaterial::CacheShaders + - FMaterial::SetGameThreadShaderMap +- FMaterialShaderMap::LoadForRemoteRecompile + - FMaterial::SetGameThreadShaderMap +- ProcessSerializedInlineShaderMaps + - FMaterial::SetInlineShaderMap +- SetShaderMapsOnMaterialResources_RenderThread + - FMaterial::SetRenderingThreadShaderMap + +虽然上面有很多接口最终会设置到FMaterial的RenderingThreadShaderMap,不过多数情况下,运行时RenderingThreadShaderMap被设置的调用堆栈如下: +- UMaterial::PostLoad + - ProcessSerializedInlineShaderMaps + - FMaterial::SetInlineShaderMap + +一旦FMaterial的RenderingThreadShaderMap被正确设置,材质相关的其它众多数据将被渲染线程和渲染器自由地读取,如同鱼儿无忧无虑地遨游在湛蓝的大海之中。 + +# 材质编译 +UMaterialExpression就是表达式,每个材质节点UMaterialGraphNode都有一个UMaterialExpression实例。下面进入FMaterialCompiler(是抽象类,由子类FHLSLMaterialTranslator实现)的这两个接口的实现: +```c++ +// Engine\Source\Runtime\Engine\Private\Materials\HLSLMaterialTranslator.cpp + +int32 FHLSLMaterialTranslator::Add(int32 A,int32 B) +{ + if(A == INDEX_NONE || B == INDEX_NONE) + { + return INDEX_NONE; + } + + const uint64 Hash = CityHash128to64({ GetParameterHash(A), GetParameterHash(B) }); + if(GetParameterUniformExpression(A) && GetParameterUniformExpression(B)) + { + return AddUniformExpressionWithHash(Hash, new FMaterialUniformExpressionFoldedMath(GetParameterUniformExpression(A),GetParameterUniformExpression(B),FMO_Add),GetArithmeticResultType(A,B),TEXT("(%s + %s)"),*GetParameterCode(A),*GetParameterCode(B)); + } + else + { + return AddCodeChunkWithHash(Hash, GetArithmeticResultType(A,B),TEXT("(%s + %s)"),*GetParameterCode(A),*GetParameterCode(B)); + } +} + +int32 FHLSLMaterialTranslator::DDX( int32 X ) +{ + if (X == INDEX_NONE) + { + return INDEX_NONE; + } + + if (ShaderFrequency == SF_Compute) + { + // running a material in a compute shader pass (e.g. when using SVOGI) + return AddInlinedCodeChunk(MCT_Float, TEXT("0")); + } + + if (ShaderFrequency != SF_Pixel) + { + return NonPixelShaderExpressionError(); + } + + return AddCodeChunk(GetParameterType(X),TEXT("DDX(%s)"),*GetParameterCode(X)); +} +``` + +**UMaterialGraphNode即我们在材质编辑器创建的材质节点**,继承的父类依次是UMaterialGraphNode_Base、UEdGraphNode。 + +## FHLSLMaterialTranslator +FHLSLMaterialTranslator继承自FMaterialCompiler,作用就是将材质的表达式转译成HLSL代码,填充到MaterialTemplate.ush的宏和空缺代码段。 +```c++ +// Engine\Source\Runtime\Engine\Public\MaterialCompiler.h +class FMaterialCompiler +{ +public: + virtual ~FMaterialCompiler() { } + // 材质属性接口. + virtual void SetMaterialProperty(EMaterialProperty InProperty, EShaderFrequency OverrideShaderFrequency = SF_NumFrequencies, bool bUsePreviousFrameTime = false) = 0; + virtual void PushMaterialAttribute(const FGuid& InAttributeID) = 0; + virtual FGuid PopMaterialAttribute() = 0; + virtual const FGuid GetMaterialAttribute() = 0; + virtual void SetBaseMaterialAttribute(const FGuid& InAttributeID) = 0; + virtual void PushParameterOwner(const FMaterialParameterInfo& InOwnerInfo) = 0; + virtual FMaterialParameterInfo PopParameterOwner() = 0; + + // 调用材质表达式. + virtual int32 CallExpression(FMaterialExpressionKey ExpressionKey,FMaterialCompiler* InCompiler) = 0; + + // 平台和着色模型相关. + virtual EShaderFrequency GetCurrentShaderFrequency() const = 0; + virtual EMaterialCompilerType GetCompilerType() const; + inline bool IsVertexInterpolatorBypass() const; + virtual EMaterialValueType GetType(int32 Code) = 0; + virtual EMaterialQualityLevel::Type GetQualityLevel() = 0; + virtual ERHIFeatureLevel::Type GetFeatureLevel() = 0; + virtual EShaderPlatform GetShaderPlatform() = 0; + virtual const ITargetPlatform* GetTargetPlatform() const = 0; + virtual FMaterialShadingModelField GetMaterialShadingModels() const = 0; + (......) + // 材质表达式对应的接口. + virtual int32 AccessCollectionParameter(UMaterialParameterCollection* ParameterCollection, int32 ParameterIndex, int32 ComponentIndex) = 0; + virtual int32 ScalarParameter(FName ParameterName, float DefaultValue) = 0; + virtual int32 VectorParameter(FName ParameterName, const FLinearColor& DefaultValue) = 0; + virtual int32 Constant(float X) = 0; + virtual int32 Constant2(float X,float Y) = 0; + virtual int32 Sine(int32 X) = 0; + virtual int32 Cosine(int32 X) = 0; + virtual int32 Tangent(int32 X) = 0; + virtual int32 ReflectionVector() = 0; + + virtual int32 If(int32 A,int32 B,int32 AGreaterThanB,int32 AEqualsB,int32 ALessThanB,int32 Threshold) = 0; + virtual int32 VertexInterpolator(uint32 InterpolatorIndex) = 0; + + virtual int32 Add(int32 A,int32 B) = 0; + virtual int32 Sub(int32 A,int32 B) = 0; + virtual int32 Mul(int32 A,int32 B) = 0; + virtual int32 Div(int32 A,int32 B) = 0; + virtual int32 Dot(int32 A,int32 B) = 0; + virtual int32 Cross(int32 A,int32 B) = 0; + + virtual int32 DDX(int32 X) = 0; + virtual int32 DDY(int32 X) = 0; + + (......) +}; + +// Engine\Source\Runtime\Engine\Private\Materials\HLSLMaterialTranslator.h +class FHLSLMaterialTranslator : public FMaterialCompiler +{ +protected: + // 编译的材质. + FMaterial* Material; + // 编译输出结果, 会被存储到DDC. + FMaterialCompilationOutput& MaterialCompilationOutput; + + // 资源字符串. + FString ResourcesString; + // MaterialTemplate.usf字符串内容. + FString MaterialTemplate; + + // 平台相关. + EShaderFrequency ShaderFrequency; + EShaderPlatform Platform; + EMaterialQualityLevel::Type QualityLevel; + ERHIFeatureLevel::Type FeatureLevel; + FMaterialShadingModelField ShadingModelsFromCompilation; + const ITargetPlatform* TargetPlatform; + + // 编译的中间数据. + EMaterialProperty MaterialProperty; + TArray MaterialAttributesStack; + TArray ParameterOwnerStack; + TArray* CurrentScopeChunks; + bool SharedPixelProperties[CompiledMP_MAX]; + TArray FunctionStacks[SF_NumFrequencies]; + FStaticParameterSet StaticParameters; + + TArray SharedPropertyCodeChunks[SF_NumFrequencies]; + TArray UniformExpressions; + TArray > UniformVectorExpressions; + TArray > UniformScalarExpressions; + TArray > UniformTextureExpressions[NumMaterialTextureParameterTypes]; + TArray> UniformExternalTextureExpressions; + + TArray ParameterCollections; + TArray CustomExpressions; + TArray CustomOutputImplementations; + TArray CustomVertexInterpolators; + + // 顶点工厂栈入口. + TArray VTStacks; + FHashTable VTStackHash; + + TBitArray<> AllocatedUserTexCoords; + TBitArray<> AllocatedUserVertexTexCoords; + (.....) +public: + // 执行HLSL转译. + bool Translate(); + // 获取材质环境. + void GetMaterialEnvironment(EShaderPlatform InPlatform, FShaderCompilerEnvironment& OutEnvironment); + void GetSharedInputsMaterialCode(FString& PixelMembersDeclaration, FString& NormalAssignment, FString& PixelMembersInitializationEpilog); + // 获取材质着色器代码. + FString GetMaterialShaderCode(); +protected: + // 获取所有定义. + FString GetDefinitions(TArray& CodeChunks, int32 StartChunk, int32 EndChunk) const; + + // 代码块. + int32 AddCodeChunkInner(uint64 Hash, const TCHAR* FormattedCode, EMaterialValueType Type, bool bInlined); + int32 AddCodeChunk(EMaterialValueType Type, const TCHAR* Format, ...); + int32 AddCodeChunkWithHash(uint64 BaseHash, EMaterialValueType Type, const TCHAR* Format, ...); + int32 AddInlinedCodeChunk(EMaterialValueType Type, const TCHAR* Format, ...); + int32 AddInlinedCodeChunkWithHash(uint64 BaseHash, EMaterialValueType Type, const TCHAR* Format, ...); + + int32 AddUniformExpressionInner(uint64 Hash, FMaterialUniformExpression* UniformExpression, EMaterialValueType Type, const TCHAR* FormattedCode); + int32 AddUniformExpression(FMaterialUniformExpression* UniformExpression, EMaterialValueType Type, const TCHAR* Format, ...); + int32 AddUniformExpressionWithHash(uint64 BaseHash, FMaterialUniformExpression* UniformExpression, EMaterialValueType Type, const TCHAR* Format, ...); + + // 材质表达式. + virtual int32 Sine(int32 X) override; + virtual int32 Cosine(int32 X) override; + virtual int32 Tangent(int32 X) override; + virtual int32 Arcsine(int32 X) override; + virtual int32 ArcsineFast(int32 X) override; + virtual int32 Arccosine(int32 X) override; + virtual int32 Floor(int32 X) override; + virtual int32 Ceil(int32 X) override; + virtual int32 Round(int32 X) override; + virtual int32 Truncate(int32 X) override; + virtual int32 Sign(int32 X) override; + virtual int32 Frac(int32 X) override; + virtual int32 Fmod(int32 A, int32 B) override; + (......) +}; +``` \ No newline at end of file diff --git a/08-Assets/Images/ImageBag/Images/UE_UMaterial.png b/08-Assets/Images/ImageBag/Images/UE_UMaterial.png new file mode 100644 index 0000000000000000000000000000000000000000..01557fcbe800e016f29ab4615d68c87fd450daaf GIT binary patch literal 85150 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 z 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 XurgNq<~4SZJdeK4^~=Qw>xcgU2+hUi literal 0 HcmV?d00001