天天看點

剖析虛幻渲染體系(09)- 材質體系

目錄

  • 9.1 本篇概述
  • 9.2 材質基礎
    • 9.2.1 UMaterial
    • 9.2.2 UMaterialInstance
    • 9.2.3 FMaterialRenderProxy
    • 9.2.4 FMaterial, FMaterialResource
    • 9.2.5 材質總覽
  • 9.3 材質機制
    • 9.3.1 材質渲染
    • 9.3.2 材質編譯
      • 9.3.2.1 UMaterialExpression
      • 9.3.2.2 UMaterialGraphNode
      • 9.3.2.3 UMaterialGraph
      • 9.3.2.4 FHLSLMaterialTranslator
      • 9.3.2.5 MaterialTemplate.ush
      • 9.3.2.6 材質編譯流程
  • 9.4 材質開發
    • 9.4.1 材質調試與優化
    • 9.4.2 材質開發案例
      • 9.4.2.1 增加材質節點
      • 9.4.2.2 擴充自定義節點
      • 9.4.2.3 擴充材質模闆
  • 9.5 本篇總結
    • 9.5.1 本篇思考
  • 特别說明
  • 參考文獻

材質(Material)是UE渲染體系中非常重要的基礎體系,包含了材質藍圖、渲染狀态、幾何體屬性等等資料。由于UE做了各種跨平台和細節封裝,使得材質體系顯得龐大且複雜。本篇就視圖從表面到底層,層層剖析它的技術、特征和機制。

前面很多篇章都有大量涉及材質的概念、類型和代碼,本篇将更加深入且廣泛低闡述它的體系。主要闡述UE的以下内容:

  • 材質的基礎概念和基礎類型。
  • 材質的實作層級。
  • 材質的實作和原理。
  • 材質的使用方法和用例。

材質體系建立于Shader體系之上,是連結圖形程式、TA、藝術家的最主要的橋梁。

本章将分析材質涉及的基礎概念和類型,闡述它們之間的基本關系和使用方法。

UMaterial是屬于引擎層的概念,對應着我們在材質編輯器編輯的uasset資源檔案,可以被應用到網格上,以便控制它在場景中的視覺效果。它繼承自UMaterialInterface,它們的定義如下:

// 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<FMaterialTextureInfo> TextureStreamingData;
    // 存于此材質資源内的使用者資料清單.
    TArray<UAssetUserData*> AssetUserData;

private:
    // 強制編譯的目标Feature Level.
    uint32 FeatureLevelsToForceCompile;

public:
    //---- IInterface_AssetUserData接口 ----
    virtual void AddAssetUserData(UAssetUserData* InUserData) override;
    virtual void RemoveUserDataOfClass(TSubclassOf<UAssetUserData> InUserDataClass) override;
    virtual UAssetUserData* GetAssetUserDataOfClass(TSubclassOf<UAssetUserData> 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<UTexture*>& OutTextures, EMaterialQualityLevel::Type QualityLevel, bool bAllQualityLevels, ERHIFeatureLevel::Type FeatureLevel, bool bAllFeatureLevels) const
        PURE_VIRTUAL(UMaterialInterface::GetUsedTextures,);
    // 擷取用于渲染此材質的紋理清單和索引.
    virtual void GetUsedTexturesAndIndices(TArray<UTexture*>& OutTextures, TArray< TArray<int32> >& 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<FMaterialParameterInfo>& OutParameterInfo, TArray<FGuid>& OutParameterIds) const
        PURE_VIRTUAL(UMaterialInterface::GetAllScalarParameterInfo,return;);
    virtual void GetAllVectorParameterInfo(TArray<FMaterialParameterInfo>& OutParameterInfo, TArray<FGuid>& OutParameterIds) const
        PURE_VIRTUAL(UMaterialInterface::GetAllVectorParameterInfo,return;);
    virtual void GetAllTextureParameterInfo(TArray<FMaterialParameterInfo>& OutParameterInfo, TArray<FGuid>& OutParameterIds) const
        PURE_VIRTUAL(UMaterialInterface::GetAllTextureParameterInfo,return;);
    virtual void GetAllRuntimeVirtualTextureParameterInfo(TArray<FMaterialParameterInfo>& OutParameterInfo, TArray<FGuid>& OutParameterIds) const
        PURE_VIRTUAL(UMaterialInterface::GetAllRuntimeVirtualTextureParameterInfo, return;);
    virtual void GetAllFontParameterInfo(TArray<FMaterialParameterInfo>& OutParameterInfo, TArray<FGuid>& 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<UObject* const> GetReferencedTextures() const
        PURE_VIRTUAL(UMaterialInterface::GetReferencedTextures,return TArrayView<UObject* const>(););

    // 儲存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<FGuid>& 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 <typename FunctionType>
    static void IterateOverActiveFeatureLevels(FunctionType InHandler);

    // 通路材質采樣器類型的緩存的UEnum類型資訊.
    static UEnum* GetSamplerTypeEnum();

    bool UseAnyStreamingTexture() const;
    bool HasTextureStreamingData() const;
    const TArray<FMaterialTextureInfo>& GetTextureStreamingData() const;
    FORCEINLINE TArray<FMaterialTextureInfo>& GetTextureStreamingData();
    // 紋理流接口.
    bool FindTextureStreamingDataIndexRange(FName TextureName, int32& LowerIndex, int32& HigherIndex) const;
    void SetTextureStreamingData(const TArray<FMaterialTextureInfo>& 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<enum EMaterialDomain> MaterialDomain;
    TEnumAsByte<enum EBlendMode> BlendMode;
    TEnumAsByte<enum EDecalBlendMode> DecalBlendMode;
    TEnumAsByte<enum EMaterialDecalResponse> MaterialDecalResponse;
    TEnumAsByte<enum EMaterialShadingModel> 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<enum ETranslucencyLightingMode> 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<EMaterialShadingRate> 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<enum EBlendableLocation> BlendableLocation;
    uint8 BlendableOutputAlpha : 1;
    uint8 bEnableStencilTest : 1;
    TEnumAsByte<EMaterialStencilCompare> StencilCompare;
    uint8 StencilRefValue = 0;
    TEnumAsByte<enum ERefractionMode> RefractionMode;
    int32 BlendablePriority;
    uint8 bIsBlendable : 1;
    uint32 UsageFlagWarnings;
    float RefractionDepthBias;
    FGuid StateId;
    float MaxDisplacement;

    // 當渲染器需要擷取參數值時,代表這個材質到渲染器的FMaterialRenderProxy衍生物.
    class FDefaultMaterialInstance* DefaultMaterialInstance;

#if WITH_EDITORONLY_DATA
    // 編輯器參數.
    TMap<FName, TArray<UMaterialExpression*> > EditorParameters;
    // 材質圖. 編輯器模型下的資料.
    class UMaterialGraph*    MaterialGraph;
#endif

private:
    // 從地盤序列化而來的内聯材質資源. 由遊戲線程的PostLoad處理.
    TArray<FMaterialResource> LoadedMaterialResources;
    // 用于渲染材質的資源清單.
    TArray<FMaterialResource*> MaterialResources;
#if WITH_EDITOR
    // 正在緩存或烘焙的材質資源.
    TMap<const class ITargetPlatform*, TArray<FMaterialResource*>> 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<FAssetRegistryTag>& 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<UObject* const> GetReferencedTextures() const override final;
    
    (......)
};
           

UMaterialInterface是個基礎的抽象類,定義了一組通用的材質屬性和接口。UMaterialInterface聲明的主要資料有:

  • USubsurfaceProfile* SubsurfaceProfile:次表面散射輪廓,常用于皮膚、玉石等材質。
  • TArray<UAssetUserData*> AssetUserData:使用者資料,可以存多個資料。
  • FLightmassMaterialInterfaceSettings LightmassSettings:離線GI資料。
  • TArray TextureStreamingData:紋理流資料。

UMaterialInterface的子類不止UMaterial,還有UMaterialInstance(後面又解析)。

UMaterial定義了材質所需的所有資料和操作接口,并負責打通其它關聯類型的連結。它的核心資料有:

  • TArray<FMaterialResource*> MaterialResources:材質渲染資源,一個材質可以擁有多個渲染資源執行個體。
  • FDefaultMaterialInstance* DefaultMaterialInstance:預設的材質渲染代理,繼承自FMaterialRenderProxy。
  • UPhysicalMaterial* PhysMaterial:實體材質。
  • TArray LoadedMaterialResources:已經加載的材質資源,通常由遊戲線程從磁盤加載并序列化而來。
  • 材質的各種屬性和标記。

其中FMaterialResource是渲染材質的資源,屬于渲染層的類型和資料,後面會有章節解析。

一個UMaterial正是對應着材質編輯器的資源,也是我們最常接觸的對象:

剖析虛幻渲染體系(09)- 材質體系

我們最常接觸的材質編輯器編輯的正是UMaterial執行個體,左側屬性面闆的選項跟UMaterial的聲明一緻。

UMaterial擁有唯一一個特殊的子類UPreviewMaterial,它主要用于編輯器相關的材質預覽,如材質編輯器左上角的預覽視窗、浏覽器的材質縮略圖等等。

UMaterialInstance是材質執行個體,不能單獨存在,而需要依賴UMaterialInterface類型的父類,意味着父類可以是UMaterialInterface的任意一個子類,但最上層的父類必須是UMaterial。它的定義如下:

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<EBlendMode> BlendMode;
    float OpacityMaskClipValue;
    FMaterialShadingModelField ShadingModels;

    // 覆寫Parent的各種類型的資料.
    TArray<struct FScalarParameterValue> ScalarParameterValues;
    TArray<struct FVectorParameterValue> VectorParameterValues;
    TArray<struct FTextureParameterValue> TextureParameterValues;
    TArray<struct FRuntimeVirtualTextureParameterValue> RuntimeVirtualTextureParameterValues;
    TArray<struct FFontParameterValue> FontParameterValues;
    struct FMaterialInstanceBasePropertyOverrides BasePropertyOverrides;

    (......)

private:
    FStaticParameterSet StaticParameters;
    FMaterialCachedParameters CachedLayerParameters;
    TArray<UObject*> CachedReferencedTextures;
    // 已加載的材質資源.
    TArray<FMaterialResource> LoadedMaterialResources;
    TArray<FMaterialResource*> 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<FMaterialShaderMap*>& OutShaderMaps);
    void GetAllParametersOfType(EMaterialParameterType Type, TArray<FMaterialParameterInfo>& OutParameterInfo, TArray<FGuid>& 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被建立:

// Engine\Source\Runtime\Engine\Classes\Materials\MaterialInstanceConstant.h

// 固定材質執行個體
class UMaterialInstanceConstant : public UMaterialInstance
{
    // 編輯器資料
#if WITH_EDITOR
    friend class UMaterialInstanceConstantFactoryNew;
    friend class UMaterialEditorInstanceConstant;
    virtual ENGINE_API void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif

    class UPhysicalMaterialMask* PhysMaterialMask;

    // Begin UMaterialInterface interface.
    virtual UPhysicalMaterialMask* GetPhysicalMaterialMask() const override;
    // End UMaterialInterface interface.

    float K2_GetScalarParameterValue(FName ParameterName);
    class UTexture* K2_GetTextureParameterValue(FName ParameterName);
    FLinearColor K2_GetVectorParameterValue(FName ParameterName);

    ENGINE_API void PostLoad();
    
    (......)
};


// Engine\Source\Runtime\Engine\Classes\Materials\MaterialInstanceDynamic.h

// 動态材質執行個體
class ENGINE_API UMaterialInstanceDynamic : public UMaterialInstance
{
    // 建立動态執行個體, 需要給定父親執行個體.
    static UMaterialInstanceDynamic* Create(class UMaterialInterface* ParentMaterial, class UObject* InOuter);
    static UMaterialInstanceDynamic* Create( class UMaterialInterface* ParentMaterial, class UObject* InOuter, FName Name );
    
    // 從材質或執行個體層級拷貝Uniform參數((scalar, vector and texture).
    void CopyMaterialUniformParameters(UMaterialInterface* Source);
    void CopyInterpParameters(UMaterialInstance* Source);
    void CopyParameterOverrides(UMaterialInstance* MaterialInstance);
    void CopyScalarAndVectorParameters(const UMaterialInterface& SourceMaterialToCopyFrom, ERHIFeatureLevel::Type FeatureLevel);
    
    // 清理參數.
    void ClearParameterValues();
    
    // 初始化.
    bool InitializeScalarParameterAndGetIndex(const FName& ParameterName, float Value, int32& OutParameterIndex);
    bool InitializeVectorParameterAndGetIndex(const FName& ParameterName, const FLinearColor& Value, int32& OutParameterIndex);
    
    // 資料設定接口.
    void SetScalarParameterValue(FName ParameterName, float Value);
    void SetScalarParameterValueByInfo(const FMaterialParameterInfo& ParameterInfo, float Value);
    bool SetScalarParameterByIndex(int32 ParameterIndex, float Value);
    bool SetVectorParameterByIndex(int32 ParameterIndex, const FLinearColor& Value);
    void SetTextureParameterValue(FName ParameterName, class UTexture* Value);
    void SetTextureParameterValueByInfo(const FMaterialParameterInfo& ParameterInfo, class UTexture* Value);
    void SetVectorParameterValue(FName ParameterName, FLinearColor Value);
    void SetVectorParameterValueByInfo(const FMaterialParameterInfo& ParameterInfo, FLinearColor Value);
    void SetFontParameterValue(const FMaterialParameterInfo& ParameterInfo, class UFont* FontValue, int32 FontPage);
    
    // 資料擷取接口.
    float K2_GetScalarParameterValue(FName ParameterName);
    float K2_GetScalarParameterValueByInfo(const FMaterialParameterInfo& ParameterInfo);
    class UTexture* K2_GetTextureParameterValue(FName ParameterName);
    class UTexture* K2_GetTextureParameterValueByInfo(const FMaterialParameterInfo& ParameterInfo);
    FLinearColor K2_GetVectorParameterValue(FName ParameterName);
    FLinearColor K2_GetVectorParameterValueByInfo(const FMaterialParameterInfo& ParameterInfo);
    void K2_InterpolateMaterialInstanceParams(UMaterialInstance* SourceA, UMaterialInstance* SourceB, float Alpha);
    void K2_CopyMaterialInstanceParameters(UMaterialInterface* Source, bool bQuickParametersOnly = false);

    virtual bool HasOverridenBaseProperties()const override{ return false; }
    virtual float GetOpacityMaskClipValue() const override;
    virtual bool GetCastDynamicShadowAsMasked() const override;
    virtual FMaterialShadingModelField GetShadingModels() const override;
    virtual bool IsShadingModelFromMaterialExpression() const override;
    virtual EBlendMode GetBlendMode() const override;
    virtual bool IsTwoSided() const override;
    virtual bool IsDitheredLODTransition() const override;
    virtual bool IsMasked() const override;

    // 為了重映射到正确的紋理流資料, 必須追蹤每個改了名的紋理.
    TMap<FName, TArray<FName> > RenamedTextures;
    
    // 擷取紋理密度.
    virtual float GetTextureDensity(FName TextureName, const struct FMeshUVChannelInfo& UVChannelData) const override;
};
           

UMaterialInstanceConstant顧名思義固定材質執行個體,用于編輯器預先建立和編輯好的材質執行個體資源:

剖析虛幻渲染體系(09)- 材質體系

材質執行個體編輯器一覽。右側中間Material Property Overrides顯示了材質執行個體可以編輯和覆寫的屬性,比較受限。

UMaterialInstanceConstant的誕生就是為了避免運作時因修改材質參數而引起重新編譯,它内部有限的資料覆寫也是因為此。如果不重新編譯,就無法支援對材質的正常修改,是以執行個體隻能更改預定義的材質參數的值。 這裡的參數就是在材質編輯器内定義的唯一的名稱、類型和預設值靜态定義。另外,需要明确注意的是,在運作時的代碼(非編輯器代碼)中,我們是無法更改UMaterialInstanceConstant執行個體的材質屬性。UMaterialInstanceConstant還有一個專用于渲染地貌的ULandscapeMaterialInstanceConstant的子類。

UMaterialInstanceDynamic與UMaterialInstanceConstant不同,它提供了可以在運作時代碼動态建立和修改材質屬性的功能,并且同樣不會引起材質重新編譯。UE内置代碼中包含了大量的UMaterialInstanceDynamic建立、設定和渲染代碼,下面舉個使用案例:

// Engine\Source\Runtime\Engine\Private\Components\DecalComponent.cpp

class UMaterialInstanceDynamic* UDecalComponent::CreateDynamicMaterialInstance()
{
    // 建立動态執行個體, 其中DecalMaterial是動态執行個體的父親, 由使用者指定UDecalComponent的材質.
    UMaterialInstanceDynamic* Instance = UMaterialInstanceDynamic::Create(DecalMaterial, this);

    // 覆寫成新的貼花材質.
    SetDecalMaterial(Instance);

    return Instance;
}
           

UMaterialInstance之間的繼承關系可以是任意的樹狀結構,理論上繼承的層級沒有限制(但太多的繼承層級增加維護難度,降低運作效率)。最頂層的父類一定是UMaterial執行個體,次級及之後的父類一定是UMaterialInstance執行個體。下圖是材質執行個體編輯器顯示的4級層級:

剖析虛幻渲染體系(09)- 材質體系

4級材質執行個體層級。其中最頂層是UMaterial執行個體,下面 的3級則是UMaterialInstance執行個體。

若嘗試将頂層或中間層的父類置為空,UE也可以容忍,隻是材質的渲染效果會異常:

剖析虛幻渲染體系(09)- 材質體系

我們知道圖元、網格、光源等場景的類型都有遊戲線程代表和渲染線程代表,而材質也不例外。作為遊戲線程代表的UMaterialInterface對應的渲染線程代表便是FMaterialRenderProxy。FMaterialRenderProxy負責接收遊戲線程代表的資料,然後傳遞給渲染器去處理和渲染。FMaterialRenderProxy的定義如下:

// 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<FMaterialRenderProxy*>& 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<FMaterialRenderProxy*> MaterialRenderProxyMap;
    ENGINE_API static TSet<FMaterialRenderProxy*> DeferredUniformExpressionCacheRequests;
};
           

FMaterialRenderProxy是個抽象類,定義了一個靜态全局的材質渲染代理映射表和擷取FMaterial渲染執行個體的接口。具體的邏輯由子類完成,它的子類有:

  • FDefaultMaterialInstance:渲染UMaterial的預設代表執行個體。
  • FMaterialInstanceResource:渲染UMaterialInstance執行個體的代表。
  • FColoredMaterialRenderProxy:覆寫材質顔色向量參數的材質渲染代表。
  • FLandscapeMaskMaterialRenderProxy:地貌遮罩材質渲染代表。
  • FLightmassMaterialProxy:Lightmass材質渲染代理。
  • ......

我們将注意力放到兩個重要的子類:FDefaultMaterialInstance和FMaterialInstanceResource,它們的定義如下:

// 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 <typename ValueType>
    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 <typename ValueType>
    void RenderThread_UpdateParameter(const FHashedMaterialParameterInfo& ParameterInfo, const ValueType& Value )
    {
        LLM_SCOPE(ELLMTag::MaterialInstance);

        InvalidateUniformExpressionCache(false);
        TArray<TNamedParameter<ValueType> >& ValueArray = GetValueArray<ValueType>();
        const int32 ParameterCount = ValueArray.Num();
        for (int32 ParameterIndex = 0; ParameterIndex < ParameterCount; ++ParameterIndex)
        {
            TNamedParameter<ValueType>& Parameter = ValueArray[ParameterIndex];
            if (Parameter.Info == ParameterInfo)
            {
                Parameter.Value = Value;
                return;
            }
        }
        TNamedParameter<ValueType> NewParameter;
        NewParameter.Info = ParameterInfo;
        NewParameter.Value = Value;
        ValueArray.Add(NewParameter);
    }

    // 查找指定名字的參數值.
    template <typename ValueType>
    const ValueType* RenderThread_FindParameterByName(const FHashedMaterialParameterInfo& ParameterInfo) const
    {
        const TArray<TNamedParameter<ValueType> >& ValueArray = GetValueArray<ValueType>();
        const int32 ParameterCount = ValueArray.Num();
        for (int32 ParameterIndex = 0; ParameterIndex < ParameterCount; ++ParameterIndex)
        {
            const TNamedParameter<ValueType>& Parameter = ValueArray[ParameterIndex];
            if (Parameter.Info == ParameterInfo)
            {
                return &Parameter.Value;
            }
        }
        return NULL;
    }
    
private:
    template <typename ValueType> TArray<TNamedParameter<ValueType> >& GetValueArray();

    // 材質執行個體的父親.
    UMaterialInterface* Parent;
    // 遊戲線程的父親.
    UMaterialInterface* GameThreadParent;
    // 所屬的材質執行個體.
    UMaterialInstance* Owner;
    
    // 各種類型的參數值清單.
    TArray<TNamedParameter<FLinearColor> > VectorParameterArray;
    TArray<TNamedParameter<float> > ScalarParameterArray;
    TArray<TNamedParameter<const UTexture*> > TextureParameterArray;
    TArray<TNamedParameter<const URuntimeVirtualTexture*> > RuntimeVirtualTextureParameterArray; 
};
           

需要格外注意的是,FMaterialRenderProxy既會被遊戲線程處理,又會被渲染線程處理,需要小心注意它們之間的資料通路和接口調用。帶有GameThread的是專用于遊戲線程,帶有RenderThread的專用于渲染線程,如果沒有特别說明,一般(非絕對)用于渲染線程。如果錯誤地調用了不該調用的接口或通路了資料,将出現競争條件,引發随機崩潰,增加指數級的調試難度。

FMaterial有3個功能:

  • 表示材質到材質的編譯過程,并提供可擴充性鈎子(CompileProperty等) 。
  • 将材質資料傳遞到渲染器,并使用函數通路材質屬性。
  • 存儲緩存的shader map,和其他來自編譯的瞬态輸出,這對異步着色器編譯是必要的。

下面是FMaterial的定義:

// 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<const FMaterialTextureParameterInfo> GetUniformTextureExpressions(EMaterialTextureParameterType Type) const;
    ENGINE_API TArrayView<const FMaterialVectorParameterInfo> GetUniformVectorParameterExpressions() const;
    ENGINE_API TArrayView<const FMaterialScalarParameterInfo> GetUniformScalarParameterExpressions() const;
    inline TArrayView<const FMaterialTextureParameterInfo> GetUniform2DTextureExpressions() const { return GetUniformTextureExpressions(EMaterialTextureParameterType::Standard2D); }
    inline TArrayView<const FMaterialTextureParameterInfo> GetUniformCubeTextureExpressions() const { return GetUniformTextureExpressions(EMaterialTextureParameterType::Cube); }
    inline TArrayView<const FMaterialTextureParameterInfo> GetUniform2DArrayTextureExpressions() const { return GetUniformTextureExpressions(EMaterialTextureParameterType::Array2D); }
    inline TArrayView<const FMaterialTextureParameterInfo> GetUniformVolumeTextureExpressions() const { return GetUniformTextureExpressions(EMaterialTextureParameterType::Volume); }
    inline TArrayView<const FMaterialTextureParameterInfo> 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<FMaterialShaderMap> ShaderMap = GameThreadShaderMap;
        TRefCountPtr<FMaterial> 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<FMaterialShaderMap>& InMaterialShaderMap);

    ENGINE_API virtual void AddReferencedObjects(FReferenceCollector& Collector);

    virtual TArrayView<UObject* const> GetReferencedTextures() const = 0;

    // 擷取shader/shader pipeline.
    template<typename ShaderType>
    TShaderRef<ShaderType> GetShader(FVertexFactoryType* VertexFactoryType, const typename ShaderType::FPermutationDomain& PermutationVector, bool bFatalIfMissing = true) const;
    template <typename ShaderType>
    TShaderRef<ShaderType> 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<FShaderType*>& OutShaderTypes, TArray<const FShaderPipelineType*>& OutShaderPipelineTypes, TArray<FVertexFactoryType*>& OutVFTypes) const;
    bool GetLoadedCookedShaderMapId() const;

private:
    // 遊戲線程和渲染線程的shader map.
    TRefCountPtr<FMaterialShaderMap> GameThreadShaderMap;
    TRefCountPtr<FMaterialShaderMap> 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<class FMaterialShaderMap>& OutShaderMap, 
        const ITargetPlatform* TargetPlatform = nullptr);
    void SetupMaterialEnvironment(
        EShaderPlatform Platform,
        const FShaderParametersMetadata& InUniformBufferStruct,
        const FUniformExpressionSet& InUniformExpressionSet,
        FShaderCompilerEnvironment& OutEnvironment
        ) const;

    ENGINE_API TShaderRef<FShader> GetShader(class FMeshMaterialShaderType* ShaderType, FVertexFactoryType* VertexFactoryType, int32 PermutationId, bool bFatalIfMissing = true) const;
};
           

由上面可知,FMaterial集大之所成,囊括了材質、Shader、VertexFactory、ShaderPipeline、ShaderMap等各種資料和操作接口,是這些資料的集散地。不過,它隻是個抽象的父類,具體的功能需要由子類實作。它的子類隻有FMaterialResource:

// 實作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的資料,比如:

// 擷取着色模型域
FMaterialShadingModelField FMaterialResource::GetShadingModels() const 
{
    // 優先選用MaterialInstance的資料.
    return MaterialInstance ? MaterialInstance->GetShadingModels() : Material->GetShadingModels();
}
           

需要注意的是FMaterialResource須保證UMaterial執行個體有效,MaterialInstance可以為空。

FMaterialResource還有子類FLandscapeMaterialResource,對應渲染ULandscapeMaterialInstanceConstant的材質。

渲染資源除了FMaterial之外,還有個比較核心的概念就是FMaterialRenderContext,它儲存了FMaterialRenderProxy和FMaterial之間的關聯配對:

struct ENGINE_API FMaterialRenderContext
{
    // 用于材質shader的材質渲染代表.
    const FMaterialRenderProxy* MaterialRenderProxy;
    // 材質渲染資源.
    const FMaterial& Material;

    // 是否顯示選中時的顔色.
    bool bShowSelection;

    // 構造函數.
    FMaterialRenderContext(const FMaterialRenderProxy* InMaterialRenderProxy, const FMaterial& InMaterial, const FSceneView* InView);
};
           

FMaterialRenderContext較多地用于材質各種類型的接口的形參,比如:

// 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);
    }
}
           

前幾幾節已經詳細闡述了材質體系内的基礎類型、概念和它們的定義。本節直接上它們的UML圖,以統覽它們的關系:

classDiagram-v2

UObject <|-- UMaterialInterface

UMaterialInterface <|-- UMaterial

UMaterialInterface <|-- UMaterialInstance

UMaterialInstance <|-- UMaterialInstanceConstant

UMaterialInstanceConstant <|-- ULandscapeMaterialInstanceConstant

UMaterialInstance <|-- UMaterialInstanceDynamic

FRenderResource <|-- FMaterialRenderProxy

FMaterialRenderProxy <|-- FDefaultMaterialInstance

FMaterialRenderProxy <|-- FMaterialInstanceResource

FMaterialRenderProxy <|-- FColoredMaterialRenderProxy

FMaterial <|-- FMaterialResource

FMaterialResource <|-- FLandscapeMaterialResource

FMaterialRenderProxy <-- FMaterialContext

FMaterial <-- FMaterialContext

UMaterial <-- UMaterialInterface

FMaterialRenderProxy <-- UMaterialInterface

class UMaterialInterface{

UMaterial* GetBaseMaterial()

UMaterial* GetMaterial()

FMaterialRenderProxy* GetRenderProxy()

}

MaterialResources o-- UMaterial

class UMaterial{

TArray<FMaterialResource*> MaterialResources

class UMaterialInstance{

UMaterialInterface* Parent

FMaterial <-- FMaterialRenderProxy

class FMaterialRenderProxy{

FMaterial* GetMaterial()

static TSet<FMaterialRenderProxy*> MaterialRenderProxyMap

class FMaterialInstanceResource{

UMaterialInterface* GameThreadParent

UMaterialInstance* Owner

class FDefaultMaterialInstance{

UMaterial* Material

class FMaterial{

FMaterialShaderMap GameThreadShaderMap

FMaterialShaderMap RenderingThreadShaderMap

Material <-- FMaterialResource

MaterialInstance <-- FMaterialResource

class FMaterialResource{

UMaterialInstance* MaterialInstance

如果上圖文字太小看不清,可以點選放大下面的圖檔版本:

剖析虛幻渲染體系(09)- 材質體系

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的代碼:

// 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);
        
    (......)
}
           

本章主要分析材質的部分底層機制,比如材質的渲染、編譯機制和過程、材質的緩存政策等。

本節将闡述材質的資料傳遞、更新、渲染邏輯。

材質資料的發起者依然是遊戲線程側的資源,一般是從磁盤加載的二進制資源,然後序列化成UMaterialInterface執行個體,或者由運作時動态建立并設定材質資料。不過絕大多數是由磁盤加載而來。

當使用了材質的圖元元件在被要求收集網格元素的時候,可以将其使用的UMaterialInterface對應的FMaterialRenderProxy傳遞到FMeshBatchElement中。下面以StaticMesh為例:

// 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的子類稍有不一樣,具體如下代碼所示:

// 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執行個體是哪個接口哪個成員:

// 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,那麼繼續深究其何時被建立:

FMaterialResource* FindOrCreateMaterialResource(TArray<FMaterialResource*>& 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執行個體的接口:

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觸發,調用堆棧如下所示:

      • FindOrCreateMaterialResource

此外,UMaterialInstance的部分接口也會觸發FMaterialResource執行個體的建立,此文不繼續追蹤了。

我們繼續研究FMaterial的GameThreadShaderMap和RenderingThreadShaderMap是在何處何時被設定和傳遞的:

// 直接設定RenderingThreadShaderMap
void FMaterial::SetRenderingThreadShaderMap(const TRefCountPtr<FMaterialShaderMap>& InMaterialShaderMap)
{
    RenderingThreadShaderMap = InMaterialShaderMap;
}

// 設定遊戲線程ShaderMap.
void FMaterial::SetGameThreadShaderMap(FMaterialShaderMap* InMaterialShaderMap)
{
    GameThreadShaderMap = InMaterialShaderMap;

    TRefCountPtr<FMaterialShaderMap> ShaderMap = GameThreadShaderMap;
    TRefCountPtr<FMaterial> 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<FMaterialShaderMap> ShaderMap = GameThreadShaderMap;
    TRefCountPtr<FMaterial> 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::SetInlineShaderMap
  • SetShaderMapsOnMaterialResources_RenderThread
    • FMaterial::SetRenderingThreadShaderMap

雖然上面有很多接口最終會設定到FMaterial的RenderingThreadShaderMap,不過多數情況下,運作時RenderingThreadShaderMap被設定的調用堆棧如下:

一旦FMaterial的RenderingThreadShaderMap被正确設定,材質相關的其它衆多資料将被渲染線程和渲染器自由地讀取,如同魚兒無憂無慮地遨遊在湛藍的大海之中。

在上一篇文章Shader體系中,已經闡述過Shader的編譯過程,不過本節講述的是如何将材質藍圖轉成HLSL代碼的過程和機制。

前面幾個小節先了解材質藍圖編譯過程涉及的主要類型和概念。

UMaterialExpression就是表達式,每個材質節點UMaterialGraphNode都有一個UMaterialExpression執行個體,它的主要定義如下:

// Engine\Source\Runtime\Engine\Classes\Materials\MaterialExpression.h

class ENGINE_API UMaterialExpression : public UObject
{
#if WITH_EDITORONLY_DATA
    int32 MaterialExpressionEditorX;
    int32 MaterialExpressionEditorY;
    // 材質節點.
    UEdGraphNode*    GraphNode;
#endif
    // 所屬的材質.
    class UMaterial* Material;
    // 所屬的材質函數.
    class UMaterialFunction* Function;

    uint8 bIsParameterExpression : 1;

    // 編輯器資料和标記.
#if WITH_EDITORONLY_DATA
    uint32 bShowInputs:1;
    uint32 bShowOutputs:1;
    TArray<FText> MenuCategories;
    // 表達式輸出.
    TArray<FExpressionOutput> Outputs;
#endif

    //~ Begin UObject Interface.
    virtual void PostInitProperties() override;
    virtual void PostLoad() override;
    virtual void PostDuplicate(bool bDuplicateForPIE) override;
    virtual void Serialize( FStructuredArchive::FRecord Record ) override;
    virtual bool IsEditorOnly() const
    {
        return true;
    }
    //~ End UObject Interface.

#if WITH_EDITOR
    // 編譯
    virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) { return INDEX_NONE; }
    virtual int32 CompilePreview(class FMaterialCompiler* Compiler, int32 OutputIndex) { return Compile(Compiler, OutputIndex); }
#endif

    // 資料擷取接口.
    virtual void GetTexturesForceMaterialRecompile(TArray<UTexture *> &Textures) const { }
    virtual UObject* GetReferencedTexture() const { return nullptr; }
    virtual bool CanReferenceTexture() const { return false; }

#if WITH_EDITOR
    // 擷取所有輸入表達式.
    bool GetAllInputExpressions(TArray<UMaterialExpression*>& InputExpressions);

    // 參數接口.
    virtual bool HasAParameterName() const { return false; }
    virtual void ValidateParameterName(const bool bAllowDuplicateName = true);
    virtual bool HasClassAndNameCollision(UMaterialExpression* OtherExpression) const;
    virtual void SetValueToMatchingExpression(UMaterialExpression* OtherExpression) {};
    virtual FName GetParameterName() const { return NAME_None; }
    virtual void SetParameterName(const FName& Name) {}

    (......)
#endif // WITH_EDITOR
};
           

繼承自UMaterialExpression的子類非常非常多(約200個),因為UE内置了很多材質節點,下圖是其中一小部分子類:

剖析虛幻渲染體系(09)- 材質體系

下面選兩個最常用的子類作為分析的案例:

// Engine\Source\Runtime\Engine\Classes\Materials\MaterialExpressionAdd.h

class UMaterialExpressionAdd : public UMaterialExpression
{
    // 加法表達的兩個操作數.
    FExpressionInput A;
    FExpressionInput B;

    // 當A和B非法時的代替值.
    float ConstA;
    float ConstB;

    //~ Begin UMaterialExpression Interface
#if WITH_EDITOR
    virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
    virtual void GetCaption(TArray<FString>& OutCaptions) const override;
    virtual FText GetKeywords() const override {return FText::FromString(TEXT("+"));}
#endif // WITH_EDITOR
    //~ End UMaterialExpression Interface
};

// Engine\Source\Runtime\Engine\Private\Materials\MaterialExpressions.cpp

// 編譯表達式.
int32 UMaterialExpressionAdd::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
    // 擷取兩個操作數.
    int32 Arg1 = A.GetTracedInput().Expression ? A.Compile(Compiler) : Compiler->Constant(ConstA);
    int32 Arg2 = B.GetTracedInput().Expression ? B.Compile(Compiler) : Compiler->Constant(ConstB);
    
    // 相加并傳回結果.
    return Compiler->Add(Arg1, Arg2);
}

// 擷取說明.
void UMaterialExpressionAdd::GetCaption(TArray<FString>& OutCaptions) const
{
    FString ret = TEXT("Add");

    FExpressionInput ATraced = A.GetTracedInput();
    FExpressionInput BTraced = B.GetTracedInput();
    if(!ATraced.Expression || !BTraced.Expression)
    {
        ret += TEXT("(");
        ret += ATraced.Expression ? TEXT(",") : FString::Printf( TEXT("%.4g,"), ConstA);
        ret += BTraced.Expression ? TEXT(")") : FString::Printf( TEXT("%.4g)"), ConstB);
    }

    OutCaptions.Add(ret);
}

// Engine\Source\Runtime\Engine\Classes\Materials\MaterialExpressionDDX.h

class UMaterialExpressionDDX : public UMaterialExpression
{
    FExpressionInput Value;

    //~ Begin UMaterialExpression Interface
#if WITH_EDITOR
    virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
    virtual void GetCaption(TArray<FString>& OutCaptions) const override;
#endif
    //~ End UMaterialExpression Interface
};

int32 UMaterialExpressionDDX::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
    int32 ValueInput = INDEX_NONE;

    if(Value.GetTracedInput().Expression)
    {
        ValueInput = Value.Compile(Compiler);
    }

    if(ValueInput == INDEX_NONE)
    {
        return INDEX_NONE;
    }

    return Compiler->DDX(ValueInput);
}

void UMaterialExpressionDDX::GetCaption(TArray<FString>& OutCaptions) const
{
    OutCaptions.Add(FString(TEXT("DDX")));
}
           

上面兩個類型在Compile時調用了編譯器的Add和DDX,下面進入FMaterialCompiler(是抽象類,由子類FHLSLMaterialTranslator實作)的這兩個接口的實作:

// 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));
}
           

是以材質表達式的編譯,實際上就是對參數和對應的函數序列化成HLSL片段。

UMaterialGraphNode即我們在材質編輯器建立的材質節點,繼承的父類依次是UMaterialGraphNode_Base、UEdGraphNode,它們的定義如下:

// Engine\Source\Runtime\Engine\Classes\EdGraph\EdGraphNode.h

class ENGINE_API UEdGraphNode : public UObject
{
public:
    // 引腳
    TArray<UEdGraphPin*> Pins;

    int32 NodePosX;
    int32 NodePosY;
    int32 NodeWidth;
    int32 NodeHeight;

    TEnumAsByte<ENodeAdvancedPins::Type> AdvancedPinDisplay;

private:
    // 狀态和标記.
    ENodeEnabledState EnabledState;
    ESaveOrphanPinMode OrphanedPinSaveMode;
    uint8 bDisableOrphanPinSaving:1;
    uint8 bDisplayAsDisabled:1;
    uint8 bUserSetEnabledState:1;
    uint8 bIsIntermediateNode : 1;
    uint8 bHasCompilerMessage:1;

    // 接口.
    virtual bool IsInDevelopmentMode() const;
    bool IsAutomaticallyPlacedGhostNode() const;
    void MakeAutomaticallyPlacedGhostNode();
    virtual void Serialize(FArchive& Ar) override;

    (......)
};

// Engine\Source\Editor\UnrealEd\Classes\MaterialGraph\MaterialGraphNode_Base.h

class UMaterialGraphNode_Base : public UEdGraphNode
{
    // Pin接口.
    virtual void CreateInputPins() {};
    virtual void CreateOutputPins() {};
    virtual bool IsRootNode() const {return false;}
    class UEdGraphPin* GetInputPin(int32 InputIndex) const;
    UNREALED_API void GetInputPins(TArray<class UEdGraphPin*>& OutInputPins) const;
    class UEdGraphPin* GetOutputPin(int32 OutputIndex) const;
    UNREALED_API void GetOutputPins(TArray<class UEdGraphPin*>& OutOutputPins) const;
    UNREALED_API void ReplaceNode(UMaterialGraphNode_Base* OldNode);

    // 輸入接口.
    virtual int32 GetInputIndex(const UEdGraphPin* InputPin) const {return -1;}
    virtual uint32 GetInputType(const UEdGraphPin* InputPin) const;
    void InsertNewNode(UEdGraphPin* FromPin, UEdGraphPin* NewLinkPin, TSet<UEdGraphNode*>& OutNodeList);

    //~ Begin UEdGraphNode Interface.
    virtual void AllocateDefaultPins() override;
    virtual void ReconstructNode() override;
    virtual void RemovePinAt(const int32 PinIndex, const EEdGraphPinDirection PinDirection) override;
    virtual void AutowireNewNode(UEdGraphPin* FromPin) override;
    virtual bool CanCreateUnderSpecifiedSchema(const UEdGraphSchema* Schema) const override;
    virtual FString GetDocumentationLink() const override;
    //~ End UEdGraphNode Interface.

protected:
    void ModifyAndCopyPersistentPinData(UEdGraphPin& TargetPin, const UEdGraphPin& SourcePin) const;
};

// Engine\Source\Editor\UnrealEd\Classes\MaterialGraph\MaterialGraphNode.h

class UMaterialGraphNode : public UMaterialGraphNode_Base
{
    // 材質表達式.
    class UMaterialExpression* MaterialExpression;

    bool bPreviewNeedsUpdate;
    bool bIsErrorExpression;
    bool bIsPreviewExpression;

    FRealtimeStateGetter RealtimeDelegate;
    FSetMaterialDirty MaterialDirtyDelegate;
    FSimpleDelegate InvalidatePreviewMaterialDelegate;

public:
    UNREALED_API void PostCopyNode();
    UNREALED_API FMaterialRenderProxy* GetExpressionPreview();
    UNREALED_API void RecreateAndLinkNode();
    UNREALED_API int32 GetOutputIndex(const UEdGraphPin* OutputPin);
    uint32 GetOutputType(const UEdGraphPin* OutputPin);

    //~ Begin UObject Interface
    virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
    virtual void PostEditImport() override;
    virtual void PostDuplicate(bool bDuplicateForPIE) override;
    //~ End UObject Interface

    //~ Begin UMaterialGraphNode_Base Interface
    virtual void CreateInputPins() override;
    virtual void CreateOutputPins() override;
    virtual UNREALED_API int32 GetInputIndex(const UEdGraphPin* InputPin) const override;
    virtual uint32 GetInputType(const UEdGraphPin* InputPin) const override;
    //~ End UMaterialGraphNode_Base Interface

    (......)
};
           

材質節點包含了圖形界面的資訊和對應的表達式,采用了視圖和資料相分離的經典設計模式。

UMaterialGraph是UMaterial的一個成員,用來存儲編輯器産生的材質節點和參數。它和相關類型的定義如下:

// Engine\Source\Runtime\Engine\Classes\EdGraph\EdGraph.h

class ENGINE_API UEdGraph : public UObject
{
public:
    // 圖形樣式.
    TSubclassOf<class UEdGraphSchema>  Schema;
    // 圖形節點.
    TArray<class UEdGraphNode*> Nodes;

    uint32 bEditable:1;
    uint32 bAllowDeletion:1;
    uint32 bAllowRenaming:1;

    (......)

public:
    FDelegateHandle AddOnGraphChangedHandler( const FOnGraphChanged::FDelegate& InHandler );
    void RemoveOnGraphChangedHandler( FDelegateHandle Handle );
    //~ Begin UObject interface
    virtual void BuildSubobjectMapping(UObject* OtherObject, TMap<UObject*, UObject*>& ObjectMapping) const override;

    // 節點操作.
    template <typename NodeClass>
    NodeClass* CreateIntermediateNode();
    void AddNode( UEdGraphNode* NodeToAdd, bool bUserAction = false, bool bSelectNewNode = true );
    bool RemoveNode( UEdGraphNode* NodeToRemove, bool bBreakAllLinks = true );
    
    (......)

protected:
    // 建立節點.
    UEdGraphNode* CreateNode( TSubclassOf<UEdGraphNode> NewNodeClass, bool bFromUI, bool bSelectNewNode );
    UEdGraphNode* CreateNode(TSubclassOf<UEdGraphNode> NewNodeClass, bool bSelectNewNode = true)
    UEdGraphNode* CreateUserInvokedNode(TSubclassOf<UEdGraphNode> NewNodeClass, bool bSelectNewNode = true)

private:
    FOnGraphChanged OnGraphChanged;
};

// Engine\Source\Editor\UnrealEd\Classes\MaterialGraph\MaterialGraph.h

class UNREALED_API UMaterialGraph : public UEdGraph
{
    // 對應的材質執行個體.
    class UMaterial*                Material;
    // 材質函數.
    class UMaterialFunction*        MaterialFunction;
    // 根節點.
    class UMaterialGraphNode_Root*    RootNode;

    // 材質輸入清單.
    TArray<FMaterialInputInfo> MaterialInputs;

    // 委托.
    FRealtimeStateGetter RealtimeDelegate;
    FSetMaterialDirty MaterialDirtyDelegate;
    FToggleExpressionCollapsed ToggleCollapsedDelegate;

public:
    // 重建材質圖.
    void RebuildGraph();

    // 增加表達式到材質圖.
    class UMaterialGraphNode*            AddExpression(UMaterialExpression* Expression, bool bUserInvoked);
    class UMaterialGraphNode_Comment*    AddComment(UMaterialExpressionComment* Comment, bool bIsUserInvoked = false);

    // 連接配接所有節點.
    void LinkGraphNodesFromMaterial();
    void LinkMaterialExpressionsFromGraph() const;

    (......)
};
           

FHLSLMaterialTranslator繼承自FMaterialCompiler,作用就是将材質的表達式轉譯成HLSL代碼,填充到MaterialTemplate.ush的宏和空缺代碼段。它們的定義如下:

// 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<FGuid> MaterialAttributesStack;
    TArray<FMaterialParameterInfo> ParameterOwnerStack;
    TArray<FShaderCodeChunk>* CurrentScopeChunks;
    bool SharedPixelProperties[CompiledMP_MAX];
    TArray<FMaterialFunctionCompileState*> FunctionStacks[SF_NumFrequencies];
    FStaticParameterSet StaticParameters;

    TArray<FShaderCodeChunk> SharedPropertyCodeChunks[SF_NumFrequencies];
    TArray<FShaderCodeChunk> UniformExpressions;
    TArray<TRefCountPtr<FMaterialUniformExpression> > UniformVectorExpressions;
    TArray<TRefCountPtr<FMaterialUniformExpression> > UniformScalarExpressions;
    TArray<TRefCountPtr<FMaterialUniformExpressionTexture> > UniformTextureExpressions[NumMaterialTextureParameterTypes];
    TArray<TRefCountPtr<FMaterialUniformExpressionExternalTexture>> UniformExternalTextureExpressions;

    TArray<UMaterialParameterCollection*> ParameterCollections;
    TArray<FMaterialCustomExpressionEntry> CustomExpressions;
    TArray<FString> CustomOutputImplementations;
    TArray<UMaterialExpressionVertexInterpolator*> CustomVertexInterpolators;

    // 頂點工廠棧入口.
    TArray<FMaterialVTStackEntry> 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<FShaderCodeChunk>& 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;
    
    (......)
};
           

FHLSLMaterialTranslator實作了FMaterialCompiler的所有抽象接口,它的核心核心成員和接口如下:

  • FMaterial* Material:編譯的目标材質。
  • FMaterialCompilationOutput& MaterialCompilationOutput:編譯後的結果。
  • FString MaterialTemplate:待填充或填充後的MaterialTemplate.ush字元串。
  • Translate():執行HLSL轉譯,将表達式轉譯成代碼塊儲存到對應的屬性槽中。
  • GetMaterialShaderCode():将材質的宏、屬性、表達式等資料填充到MaterialTemplate.ush并傳回結果。

後面有小節專門闡述FHLSLMaterialTranslator的轉譯過程。

另外,FMaterialCompiler還有個子類FProxyMaterialCompiler,用于Lightmass渲染器和材質烘焙。

MaterialTemplate.usf是材質shader模闆,内涵大量%s的空缺和待替換的宏,它們由FHLSLMaterialTranslator::GetMaterialShaderCode負責填充。它的部分原始代碼如下:

// Engine\Shaders\Private\MaterialTemplate.ush

#include "/Engine/Private/SceneTexturesCommon.ush"
#include "/Engine/Private/EyeAdaptationCommon.ush"
#include "/Engine/Private/Random.ush"
#include "/Engine/Private/SobolRandom.ush"
#include "/Engine/Private/MonteCarlo.ush"
#include "/Engine/Generated/UniformBuffers/Material.ush"
#include "/Engine/Private/DepthOfFieldCommon.ush"
#include "/Engine/Private/CircleDOFCommon.ush"
#include "/Engine/Private/GlobalDistanceFieldShared.ush"
#include "/Engine/Private/SceneData.ush"
#include "/Engine/Private/HairShadingCommon.ush"

//////////////////////////////////////////////////////////////////////////
//! Must match ESceneTextureId

// 後處理屬性宏.
#define PPI_SceneColor 0
#define PPI_SceneDepth 1
#define PPI_DiffuseColor 2
#define PPI_SpecularColor 3
#define PPI_SubsurfaceColor 4
#define PPI_BaseColor 5
#define PPI_Specular 6
#define PPI_Metallic 7
#define PPI_WorldNormal 8
#define PPI_SeparateTranslucency 9
#define PPI_Opacity 10
#define PPI_Roughness 11
#define PPI_MaterialAO 12
#define PPI_CustomDepth 13
(......)

//////////////////////////////////////////////////////////////////////////

// 待填充的宏定義.
#define NUM_MATERIAL_TEXCOORDS_VERTEX %s
#define NUM_MATERIAL_TEXCOORDS %s
#define NUM_CUSTOM_VERTEX_INTERPOLATORS %s
#define NUM_TEX_COORD_INTERPOLATORS %s

// 頂點插值位置定義.
%s

// 檔案引用和宏定義.

#include "/Engine/Private/PaniniProjection.ush"

#ifndef USE_DITHERED_LOD_TRANSITION
    #if USE_INSTANCING
        #ifndef USE_DITHERED_LOD_TRANSITION_FOR_INSTANCED
            #error "USE_DITHERED_LOD_TRANSITION_FOR_INSTANCED should have been defined"
        #endif
        #define USE_DITHERED_LOD_TRANSITION USE_DITHERED_LOD_TRANSITION_FOR_INSTANCED
    #else
        #ifndef USE_DITHERED_LOD_TRANSITION_FROM_MATERIAL
            #error "USE_DITHERED_LOD_TRANSITION_FROM_MATERIAL should have been defined"
        #endif
        #define USE_DITHERED_LOD_TRANSITION USE_DITHERED_LOD_TRANSITION_FROM_MATERIAL
    #endif
#endif

#ifndef USE_STENCIL_LOD_DITHER
    #define USE_STENCIL_LOD_DITHER    USE_STENCIL_LOD_DITHER_DEFAULT
#endif

#define MATERIALBLENDING_ANY_TRANSLUCENT (MATERIALBLENDING_TRANSLUCENT || MATERIALBLENDING_ADDITIVE || MATERIALBLENDING_MODULATE)

(......)

// 材質的各類結構體.
struct FMaterialAttributes
{
%s
};

struct FPixelMaterialInputs
{
%s
};

// 像素參數.
struct FMaterialPixelParameters
{
#if NUM_TEX_COORD_INTERPOLATORS
    float2 TexCoords[NUM_TEX_COORD_INTERPOLATORS];
#endif

    half4 VertexColor;
    half3 WorldNormal;
    half3 WorldTangent;
    half3 ReflectionVector;
    half3 CameraVector;
    half3 LightVector;
    float4 SvPosition;
    float4 ScreenPosition;
    half UnMirrored;
    half TwoSidedSign;
    half3x3 TangentToWorld;

#if USE_WORLDVERTEXNORMAL_CENTER_INTERPOLATION
    half3 WorldVertexNormal_Center;
#endif

    float3 AbsoluteWorldPosition;
    float3 WorldPosition_CamRelative;
    float3 WorldPosition_NoOffsets;
    float3 WorldPosition_NoOffsets_CamRelative;
    half3 LightingPositionOffset;
    float AOMaterialMask;

#if LIGHTMAP_UV_ACCESS
    float2    LightmapUVs;
#endif

#if USE_INSTANCING
    half4 PerInstanceParams;
#endif

    uint PrimitiveId;

#if TEX_COORD_SCALE_ANALYSIS
    FTexCoordScalesParams TexCoordScalesParams;
#endif

    (.....)
};

// 頂點參數.
struct FMaterialVertexParameters
{
    float3 WorldPosition;
    half3x3 TangentToWorld;
#if USE_INSTANCING
    float4x4 InstanceLocalToWorld;
    float3 InstanceLocalPosition;
    float4 PerInstanceParams;
    uint InstanceId;
    uint InstanceOffset;

#elif IS_MESHPARTICLE_FACTORY 
    float4x4 InstanceLocalToWorld;
#endif
    float4x4 PrevFrameLocalToWorld;

    float3 PreSkinnedPosition;
    float3 PreSkinnedNormal;

#if GPU_SKINNED_MESH_FACTORY
    float3 PreSkinOffset;
    float3 PostSkinOffset;
#endif

    half4 VertexColor;
#if NUM_MATERIAL_TEXCOORDS_VERTEX
    float2 TexCoords[NUM_MATERIAL_TEXCOORDS_VERTEX];
    #if ES3_1_PROFILE
    float2 TexCoordOffset;
    #endif
#endif

    FMaterialParticleParameters Particle;
    uint PrimitiveId;

    (......)
};

// 資料操作接口.
MaterialFloat3x3 GetLocalToWorld3x3(uint PrimitiveId);
MaterialFloat3x3 GetLocalToWorld3x3();
float3 GetObjectWorldPosition(FMaterialPixelParameters Parameters);
float3 GetObjectWorldPosition(FMaterialTessellationParameters Parameters);
(......)

// 材質表達式接口.
float MaterialExpressionDepthOfFieldFunction(float SceneDepth, int FunctionValueIndex);
float2 MaterialExpressionGetAtlasUVs(FMaterialPixelParameters Parameters);
float4 MaterialExpressionGetHairAuxilaryData(FMaterialPixelParameters Parameters);
float3 MaterialExpressionGetHairColorFromMelanin(float Melanin, float Redness, float3 DyeColor);
(......)

// 材質屬性查找.
MaterialFloat4 ProcessMaterialColorTextureLookup(MaterialFloat4 TextureValue);
MaterialFloat4 ProcessMaterialVirtualColorTextureLookup(MaterialFloat4 TextureValue);
MaterialFloat4 ProcessMaterialExternalTextureLookup(MaterialFloat4 TextureValue);
MaterialFloat4 ProcessMaterialLinearColorTextureLookup(MaterialFloat4 TextureValue);
MaterialFloat ProcessMaterialGreyscaleTextureLookup(MaterialFloat TextureValue);
(......)

// 統一材質表達式.
%s

// 材質屬性擷取接口.
half3 GetMaterialNormalRaw(FPixelMaterialInputs PixelMaterialInputs);
half3 GetMaterialNormal(FMaterialPixelParameters Parameters, FPixelMaterialInputs PixelMaterialInputs);
half3 GetMaterialTangentRaw(FPixelMaterialInputs PixelMaterialInputs);
half3 GetMaterialTangent(FPixelMaterialInputs PixelMaterialInputs);
half3 GetMaterialEmissiveRaw(FPixelMaterialInputs PixelMaterialInputs);
half3 GetMaterialEmissive(FPixelMaterialInputs PixelMaterialInputs);
half3 GetMaterialEmissiveForCS(FMaterialPixelParameters Parameters);
{
%s;
}
uint GetMaterialShadingModel(FPixelMaterialInputs PixelMaterialInputs);
half3 GetMaterialBaseColorRaw(FPixelMaterialInputs PixelMaterialInputs);
half3 GetMaterialBaseColor(FPixelMaterialInputs PixelMaterialInputs);
half GetMaterialCustomData0(FMaterialPixelParameters Parameters)
{
%s;
}
half GetMaterialCustomData1(FMaterialPixelParameters Parameters)
{
%s;
}
half GetMaterialAmbientOcclusionRaw(FPixelMaterialInputs PixelMaterialInputs);
half GetMaterialAmbientOcclusion(FPixelMaterialInputs PixelMaterialInputs);
half2 GetMaterialRefraction(FPixelMaterialInputs PixelMaterialInputs);
(......)

// 計算材質參數接口.
void CalcMaterialParametersEx(
    in out FMaterialPixelParameters Parameters,
    in out FPixelMaterialInputs PixelMaterialInputs,
    float4 SvPosition,
    float4 ScreenPosition,
    FIsFrontFace bIsFrontFace,
    float3 TranslatedWorldPosition,
    float3 TranslatedWorldPositionExcludingShaderOffsets);
void CalcMaterialParameters(
    in out FMaterialPixelParameters Parameters,
    in out FPixelMaterialInputs PixelMaterialInputs,
    float4 SvPosition,
    FIsFrontFace bIsFrontFace);
void CalcMaterialParametersPost(
    in out FMaterialPixelParameters Parameters,
    in out FPixelMaterialInputs PixelMaterialInputs,
    float4 SvPosition,
    FIsFrontFace bIsFrontFace);
float ApplyPixelDepthOffsetToMaterialParameters(inout FMaterialPixelParameters MaterialParameters, FPixelMaterialInputs PixelMaterialInputs, out float OutDepth);

(......)
           

以上可知,MaterialTemplate.ush包含了大量的資料和接口,主要有幾類:

  • 基礎shader子產品引用。
  • 待填充的宏定義。
  • 待填充的接口實作。
  • 頂點、像素、材質屬性等結構體定義。部分結構體待填充。
  • 材質屬性、資料處理、表達式、工具類接口定義。部分接口待填充。

上面幾個小節詳細分析了材質編譯涉及到的核心類型,本節将直接分析材質藍圖編譯換成HLSL代碼的過程。

材質ShaderMap的編譯入口在FMaterial的以下兩個接口:

  • FMaterial::BeginCompileShaderMap
  • FMaterial::GetMaterialExpressionSource

不過BeginCompileShaderMap處于主流程中,适用性更強,下面以它為起點,解析材質藍圖的編譯流程:

// Engine\Source\Runtime\Engine\Private\Materials\MaterialShared.cpp

bool FMaterial::BeginCompileShaderMap(const FMaterialShaderMapId& ShaderMapId, const FStaticParameterSet &StaticParameterSet,
 EShaderPlatform Platform, TRefCountPtr<FMaterialShaderMap>& OutShaderMap, const ITargetPlatform* TargetPlatform)
{
    // 注意隻在編輯器期間才會執行.
#if WITH_EDITORONLY_DATA
    bool bSuccess = false;
    // 建立shader map.
    TRefCountPtr<FMaterialShaderMap> NewShaderMap = new FMaterialShaderMap();

#if WITH_EDITOR
    NewShaderMap->AssociateWithAsset(GetAssetPath());
#endif
    
    // 生成材質shader代碼.
    // 輸出結果.
    FMaterialCompilationOutput NewCompilationOutput;
    // 轉換器.
    FHLSLMaterialTranslator MaterialTranslator(this, NewCompilationOutput, StaticParameterSet, Platform,GetQualityLevel(), ShaderMapId.FeatureLevel, TargetPlatform);
    // 執行表達式轉換, 填充到MaterialTemplate.ush.
    bSuccess = MaterialTranslator.Translate();

    // 表達式轉換成功才需要執行後續操作.
    if(bSuccess)
    {
        // 為材質建立一個着色器編譯環境,所有的編譯作業将共享此材質.
        TRefCountPtr<FShaderCompilerEnvironment> MaterialEnvironment = new FShaderCompilerEnvironment();
        MaterialEnvironment->TargetPlatform = TargetPlatform;
        // 擷取材質環境.
        MaterialTranslator.GetMaterialEnvironment(Platform, *MaterialEnvironment);
        // 擷取材質shader代碼.
        const FString MaterialShaderCode = MaterialTranslator.GetMaterialShaderCode();
        
        const bool bSynchronousCompile = RequiresSynchronousCompilation() || !GShaderCompilingManager->AllowAsynchronousShaderCompiling();

        // 包含虛拟的材質檔案路徑.
        MaterialEnvironment->IncludeVirtualPathToContentsMap.Add(TEXT("/Engine/Generated/Material.ush"), MaterialShaderCode);

        // 編譯材質的shader代碼.
        NewShaderMap->Compile(this, ShaderMapId, MaterialEnvironment, NewCompilationOutput, Platform, bSynchronousCompile);

        if (bSynchronousCompile) // 同步編譯
        {
            // 同步模式, 直接指派給OutShaderMap.
            OutShaderMap = NewShaderMap->CompiledSuccessfully() ? NewShaderMap : nullptr;
        }
        else // 異步編譯
        {
            // 先将NewShaderMap放到等待編譯結束的清單.
            OutstandingCompileShaderMapIds.AddUnique( NewShaderMap->GetCompilingId() );
            // 異步模式, OutShaderMap先設為null, 會回退到預設的材質.
            OutShaderMap = nullptr;
        }
    }

    return bSuccess;
#else
    UE_LOG(LogMaterial, Fatal,TEXT("Not supported."));
    return false;
#endif
}
           

以上接口分為幾步:初始化資料和建立編譯對象,執行材質藍圖轉譯,如果轉譯成功,建立材質着色器編譯環境,然後編譯轉譯後的材質藍圖shader代碼,最後會分為是否異步處理不同的傳回ShaderMap,如果是同步直接傳回,如果是異步則先放入到等待編譯結束的清單OutstandingCompileShaderMapIds。

處理OutstandingCompileShaderMapIds的邏輯堆棧鍊如下所示:

  • UMaterial::Serialize
    • SerializeInlineShaderMaps
      • FMaterial::SerializeInlineShaderMap
        • FMaterial::FinishCompilation
          • FMaterial::GetShaderMapIDsWithUnfinishedCompilation

其中FMaterial::GetShaderMapIDsWithUnfinishedCompilation的代碼如下:

void FMaterial::GetShaderMapIDsWithUnfinishedCompilation(TArray<int32>& ShaderMapIds)
{
    // 添加尚未完成編譯的ShaderMapId到清單.(先檢測GameThreadShaderMap, 再檢測OutstandingCompileShaderMapIds)
    if (GameThreadShaderMap && !GameThreadShaderMap->IsCompilationFinalized())
    {
        ShaderMapIds.Add(GameThreadShaderMap->GetCompilingId());
    }
    else if (OutstandingCompileShaderMapIds.Num() != 0 )
    {
        ShaderMapIds.Append(OutstandingCompileShaderMapIds);
    }
}
           

FMaterial::BeginCompileShaderMap還有幾個FHLSLMaterialTranslator的重要接口未解析,下面将它們一網打盡:

// Engine\Source\Runtime\Engine\Private\Materials\HLSLMaterialTranslator.cpp

bool FHLSLMaterialTranslator::Translate()
{
    bSuccess = true;

    // 編譯輸出需要儲存到MaterialCompilationOutput, 它可以自動儲存到DDC.

    Material->CompileErrors.Empty();
    Material->ErrorExpressions.Empty();
    bCompileForComputeShader = Material->IsLightFunction();
    int32 NormalCodeChunkEnd = -1;
    int32 Chunk[CompiledMP_MAX];

    memset(Chunk, INDEX_NONE, sizeof(Chunk));

    // 在通路主要屬性之前轉譯所有自定義頂點插值器,以便類型資訊可用.
    {
        CustomVertexInterpolators.Empty();
        CurrentCustomVertexInterpolatorOffset = 0;
        NextVertexInterpolatorIndex = 0;
        MaterialProperty = MP_MAX;
        ShaderFrequency = SF_Vertex;

        TArray<UMaterialExpression*> Expressions;
        Material->GatherExpressionsForCustomInterpolators(Expressions);
        GatherCustomVertexInterpolators(Expressions);

        // 重置共享的堆棧資料.
        while (FunctionStacks[SF_Vertex].Num() > 1)
        {
            FMaterialFunctionCompileState* Stack = FunctionStacks[SF_Vertex].Pop(false);
            delete Stack;
        }
        FunctionStacks[SF_Vertex][0]->Reset();

        // 當表達式清單可用時,應用節點計數限制.
        int32 NumMaterialLayersAttributes = 0;
        for (UMaterialExpression* Expression : Expressions)
        {
            if (UMaterialExpressionMaterialAttributeLayers* Layers = Cast<UMaterialExpressionMaterialAttributeLayers>(Expression))
            {
                ++NumMaterialLayersAttributes;

                if (NumMaterialLayersAttributes > 1)
                {
                    Errorf(TEXT("Materials can contain only one Material Attribute Layers node."));
                    break;
                }
            }
        }
    }

    const EShaderFrequency NormalShaderFrequency = FMaterialAttributeDefinitionMap::GetShaderFrequency(MP_Normal);
    const EMaterialDomain Domain = Material->GetMaterialDomain();
    const EBlendMode BlendMode = Material->GetBlendMode();

    // 收集任何自定義輸出表達式的實作.
    TArray<UMaterialExpressionCustomOutput*> CustomOutputExpressions;
    Material->GatherCustomOutputExpressions(CustomOutputExpressions);
    TSet<UClass*> SeenCustomOutputExpressionsClasses;

    // 一些自定義輸出必須預先編譯,以便它們可以作為共享輸入重用.
    CompileCustomOutputs(CustomOutputExpressions, SeenCustomOutputExpressionsClasses, true);            

    // 法線最先被編譯.
    {
        Chunk[MP_Normal]    = Material->CompilePropertyAndSetMaterialProperty(MP_Normal, this);
        NormalCodeChunkEnd    = SharedPropertyCodeChunks[NormalShaderFrequency].Num();
    }

    (......)

    // 其餘材質屬性.
    Chunk[MP_EmissiveColor]                    = Material->CompilePropertyAndSetMaterialProperty(MP_EmissiveColor            ,this);
    Chunk[MP_DiffuseColor]                    = Material->CompilePropertyAndSetMaterialProperty(MP_DiffuseColor            ,this);
    Chunk[MP_SpecularColor]                    = Material->CompilePropertyAndSetMaterialProperty(MP_SpecularColor            ,this);
    Chunk[MP_BaseColor]                        = Material->CompilePropertyAndSetMaterialProperty(MP_BaseColor                ,this);
    Chunk[MP_Metallic]                        = Material->CompilePropertyAndSetMaterialProperty(MP_Metallic                ,this);
    Chunk[MP_Specular]                        = Material->CompilePropertyAndSetMaterialProperty(MP_Specular                ,this);
    Chunk[MP_Roughness]                        = Material->CompilePropertyAndSetMaterialProperty(MP_Roughness                ,this);
    Chunk[MP_Anisotropy]                    = Material->CompilePropertyAndSetMaterialProperty(MP_Anisotropy                ,this);
    Chunk[MP_Opacity]                        = Material->CompilePropertyAndSetMaterialProperty(MP_Opacity                ,this);
    Chunk[MP_OpacityMask]                    = Material->CompilePropertyAndSetMaterialProperty(MP_OpacityMask            ,this);
    Chunk[MP_Tangent]                        = Material->CompilePropertyAndSetMaterialProperty(MP_Tangent                ,this);
    Chunk[MP_WorldPositionOffset]            = Material->CompilePropertyAndSetMaterialProperty(MP_WorldPositionOffset    ,this);
    Chunk[MP_WorldDisplacement]                = Material->CompilePropertyAndSetMaterialProperty(MP_WorldDisplacement        ,this);
    Chunk[MP_TessellationMultiplier]        = Material->CompilePropertyAndSetMaterialProperty(MP_TessellationMultiplier    ,this);

    // 處理shading model.
    Chunk[MP_ShadingModel]                    = Material->CompilePropertyAndSetMaterialProperty(MP_ShadingModel            ,this);
    FMaterialShadingModelField MaterialShadingModels = Material->GetShadingModels(); 
    if (Material->IsShadingModelFromMaterialExpression() && ShadingModelsFromCompilation.IsValid())
    {
        MaterialShadingModels = ShadingModelsFromCompilation;
    }
    ValidateShadingModelsForFeatureLevel(MaterialShadingModels);

    (......)

    // 自義定等資料.
    Chunk[MP_CustomData0]                    = Material->CompilePropertyAndSetMaterialProperty(MP_CustomData0        ,this);
    Chunk[MP_CustomData1]                    = Material->CompilePropertyAndSetMaterialProperty(MP_CustomData1        ,this);
    Chunk[MP_AmbientOcclusion]                = Material->CompilePropertyAndSetMaterialProperty(MP_AmbientOcclusion    ,this);
    if (IsTranslucentBlendMode(BlendMode) || MaterialShadingModels.HasShadingModel(MSM_SingleLayerWater))
    {
        int32 UserRefraction = ForceCast(Material->CompilePropertyAndSetMaterialProperty(MP_Refraction, this), MCT_Float1);
        int32 RefractionDepthBias = ForceCast(ScalarParameter(FName(TEXT("RefractionDepthBias")), Material->GetRefractionDepthBiasValue()), MCT_Float1);

        Chunk[MP_Refraction] = AppendVector(UserRefraction, RefractionDepthBias);
    }
    
    (......)

    ResourcesString = TEXT("");

    (......)

    // 代碼塊生成已完成.
    bAllowCodeChunkGeneration = false;

    // 處理編譯和材質的各種标記.
    bUsesEmissiveColor = IsMaterialPropertyUsed(MP_EmissiveColor, Chunk[MP_EmissiveColor], FLinearColor(0, 0, 0, 0), 3);
    bUsesPixelDepthOffset = (AllowPixelDepthOffset(Platform) && IsMaterialPropertyUsed(MP_PixelDepthOffset, Chunk[MP_PixelDepthOffset], FLinearColor(0, 0, 0, 0), 1))
        || (Domain == MD_DeferredDecal && Material->GetDecalBlendMode() == DBM_Volumetric_DistanceFunction);
    bool bUsesWorldPositionOffsetCurrent = IsMaterialPropertyUsed(MP_WorldPositionOffset, Chunk[MP_WorldPositionOffset], FLinearColor(0, 0, 0, 0), 3);
    bool bUsesWorldPositionOffsetPrevious = IsMaterialPropertyUsed(MP_WorldPositionOffset, Chunk[CompiledMP_PrevWorldPositionOffset], FLinearColor(0, 0, 0, 0), 3);
    bUsesWorldPositionOffset = bUsesWorldPositionOffsetCurrent || bUsesWorldPositionOffsetPrevious;
    MaterialCompilationOutput.bModifiesMeshPosition = bUsesPixelDepthOffset || bUsesWorldPositionOffset;
    MaterialCompilationOutput.bUsesWorldPositionOffset = bUsesWorldPositionOffset;
    MaterialCompilationOutput.bUsesPixelDepthOffset = bUsesPixelDepthOffset;
    bIsFullyRough = Chunk[MP_Roughness] != INDEX_NONE && IsMaterialPropertyUsed(MP_Roughness, Chunk[MP_Roughness], FLinearColor(1, 0, 0, 0), 1) == false;
    bUsesAnisotropy = IsMaterialPropertyUsed(MP_Anisotropy, Chunk[MP_Anisotropy], FLinearColor(0, 0, 0, 0), 1);
    MaterialCompilationOutput.bUsesAnisotropy = bUsesAnisotropy;
    if (bUsesSceneDepth)
    {
        MaterialCompilationOutput.SetIsSceneTextureUsed(PPI_SceneDepth);
    }
    MaterialCompilationOutput.bUsesDistanceCullFade = bUsesDistanceCullFade;
    
    (......)

    bool bDBufferAllowed = IsUsingDBuffers(Platform);
    bool bDBufferBlendMode = IsDBufferDecalBlendMode((EDecalBlendMode)Material->GetDecalBlendMode());

    FString InterpolatorsOffsetsDefinitionCode;
    TBitArray<> FinalAllocatedCoords = GetVertexInterpolatorsOffsets(InterpolatorsOffsetsDefinitionCode);

    MaterialCompilationOutput.NumUsedUVScalars = GetNumUserTexCoords() * 2;
    MaterialCompilationOutput.NumUsedCustomInterpolatorScalars = CurrentCustomVertexInterpolatorOffset;

    // 先處理法線代碼塊.
    {
        GetFixedParameterCode(
            0,
            NormalCodeChunkEnd,
            Chunk[MP_Normal],
            SharedPropertyCodeChunks[NormalShaderFrequency],
            TranslatedCodeChunkDefinitions[MP_Normal],
            TranslatedCodeChunks[MP_Normal]);

        if (TranslatedCodeChunkDefinitions[MP_Normal].IsEmpty())
        {
            TranslatedCodeChunkDefinitions[MP_Normal] = GetDefinitions(SharedPropertyCodeChunks[NormalShaderFrequency], 0, NormalCodeChunkEnd);
        }
    }

    // 其餘屬性的代碼塊, 将忽略法線.
    for(uint32 PropertyId = 0; PropertyId < MP_MAX; ++PropertyId)
    {
        if (PropertyId == MP_MaterialAttributes || PropertyId == MP_Normal || PropertyId == MP_CustomOutput)
        {
            continue;
        }

        const EShaderFrequency PropertyShaderFrequency = FMaterialAttributeDefinitionMap::GetShaderFrequency((EMaterialProperty)PropertyId);

        int32 StartChunk = 0;
        if (PropertyShaderFrequency == NormalShaderFrequency && SharedPixelProperties[PropertyId])
        {
            StartChunk = NormalCodeChunkEnd;
        }

        GetFixedParameterCode(
            StartChunk,
            SharedPropertyCodeChunks[PropertyShaderFrequency].Num(),
            Chunk[PropertyId],
            SharedPropertyCodeChunks[PropertyShaderFrequency],
            TranslatedCodeChunkDefinitions[PropertyId],
            TranslatedCodeChunks[PropertyId]);
    }

    // 處理材質屬性.
    for(uint32 PropertyId = MP_MAX; PropertyId < CompiledMP_MAX; ++PropertyId)
    {
        switch(PropertyId)
        {
            case CompiledMP_EmissiveColorCS:
                if (bCompileForComputeShader)
                {
                    GetFixedParameterCode(Chunk[PropertyId], SharedPropertyCodeChunks[SF_Compute], TranslatedCodeChunkDefinitions[PropertyId], TranslatedCodeChunks[PropertyId]);
                }
                break;
            case CompiledMP_PrevWorldPositionOffset:
                {
                    GetFixedParameterCode(Chunk[PropertyId], SharedPropertyCodeChunks[SF_Vertex], TranslatedCodeChunkDefinitions[PropertyId], TranslatedCodeChunks[PropertyId]);
                }
                break;
            default: check(0);
                break;
        }
    }

    // 輸出任何自定義輸出表達式的實作.
    for (int32 ExpressionIndex = 0; ExpressionIndex < CustomOutputImplementations.Num(); ExpressionIndex++)
    {
        ResourcesString += CustomOutputImplementations[ExpressionIndex] + "\r\n\r\n";
    }

    // Uniform标量表達式.
    for (const FMaterialUniformExpression* ScalarExpression : UniformScalarExpressions)
    {
        FMaterialUniformPreshaderHeader& Preshader = MaterialCompilationOutput.UniformExpressionSet.UniformScalarPreshaders.AddDefaulted_GetRef();
        Preshader.OpcodeOffset = MaterialCompilationOutput.UniformExpressionSet.UniformPreshaderData.Num();
        ScalarExpression->WriteNumberOpcodes(MaterialCompilationOutput.UniformExpressionSet.UniformPreshaderData);
        Preshader.OpcodeSize = MaterialCompilationOutput.UniformExpressionSet.UniformPreshaderData.Num() - Preshader.OpcodeOffset;
    }

    // Uniform向量表達式.
    for (FMaterialUniformExpression* VectorExpression : UniformVectorExpressions)
    {
        FMaterialUniformPreshaderHeader& Preshader = MaterialCompilationOutput.UniformExpressionSet.UniformVectorPreshaders.AddDefaulted_GetRef();
        Preshader.OpcodeOffset = MaterialCompilationOutput.UniformExpressionSet.UniformPreshaderData.Num();
        VectorExpression->WriteNumberOpcodes(MaterialCompilationOutput.UniformExpressionSet.UniformPreshaderData);
        Preshader.OpcodeSize = MaterialCompilationOutput.UniformExpressionSet.UniformPreshaderData.Num() - Preshader.OpcodeOffset;
    }

    // Uniform标量表達式.
    for (uint32 TypeIndex = 0u; TypeIndex < NumMaterialTextureParameterTypes; ++TypeIndex)
    {
        MaterialCompilationOutput.UniformExpressionSet.UniformTextureParameters[TypeIndex].Empty(UniformTextureExpressions[TypeIndex].Num());
        for (FMaterialUniformExpressionTexture* TextureExpression : UniformTextureExpressions[TypeIndex])
        {
            TextureExpression->GetTextureParameterInfo(MaterialCompilationOutput.UniformExpressionSet.UniformTextureParameters[TypeIndex].AddDefaulted_GetRef());
        }
    }
    
    // 外部紋理.
     MaterialCompilationOutput.UniformExpressionSet.UniformExternalTextureParameters.Empty(UniformExternalTextureExpressions.Num());
    for (FMaterialUniformExpressionExternalTexture* TextureExpression : UniformExternalTextureExpressions)
    {
        TextureExpression->GetExternalTextureParameterInfo(MaterialCompilationOutput.UniformExpressionSet.UniformExternalTextureParameters.AddDefaulted_GetRef());
    }

    // 加載MaterialTemplate.ush檔案的代碼.
    LoadShaderSourceFileChecked(TEXT("/Engine/Private/MaterialTemplate.ush"), GetShaderPlatform(), MaterialTemplate);

    // 找到'#line'的字元串位置.
    const int32 LineIndex = MaterialTemplate.Find(TEXT("#line"), ESearchCase::CaseSensitive);

    // 計算'#line'語句之前的行結束位置.
    MaterialTemplateLineNumber = INDEX_NONE;
    int32 StartPosition = LineIndex + 1;
    do 
    {
        MaterialTemplateLineNumber++;
        StartPosition = MaterialTemplate.Find(TEXT("\n"), ESearchCase::CaseSensitive, ESearchDir::FromEnd, StartPosition - 1);
    } 
    while (StartPosition != INDEX_NONE);
   
    MaterialTemplateLineNumber += 3;

    // 材質參數集.
    MaterialCompilationOutput.UniformExpressionSet.SetParameterCollections(ParameterCollections);
    // 建立材質統一緩沖結構.
    MaterialCompilationOutput.UniformExpressionSet.CreateBufferStruct();
    // 存儲統一VT采樣的數量.
    MaterialCompilationOutput.EstimatedNumVirtualTextureLookups = NumVtSamples;

    // 清理所有函數堆棧.
    ClearAllFunctionStacks();

    return bSuccess;
}

// 擷取材質的環境(宏定義).
void FHLSLMaterialTranslator::GetMaterialEnvironment(EShaderPlatform InPlatform, FShaderCompilerEnvironment& OutEnvironment)
{
    if (bNeedsParticlePosition || Material->ShouldGenerateSphericalParticleNormals() || bUsesSphericalParticleOpacity)
    {
        OutEnvironment.SetDefine(TEXT("NEEDS_PARTICLE_POSITION"), 1);
    }

    if (bNeedsParticleVelocity || Material->IsUsedWithNiagaraMeshParticles())
    {
        OutEnvironment.SetDefine(TEXT("NEEDS_PARTICLE_VELOCITY"), 1);
    }

    (......)

    OutEnvironment.SetDefine(TEXT("MATERIAL_ENABLE_TRANSLUCENCY_FOGGING"), Material->ShouldApplyFogging());
    OutEnvironment.SetDefine(TEXT("MATERIAL_ENABLE_TRANSLUCENCY_CLOUD_FOGGING"), Material->ShouldApplyCloudFogging());
    OutEnvironment.SetDefine(TEXT("MATERIAL_IS_SKY"), Material->IsSky());
    OutEnvironment.SetDefine(TEXT("MATERIAL_COMPUTE_FOG_PER_PIXEL"), Material->ComputeFogPerPixel());
    OutEnvironment.SetDefine(TEXT("MATERIAL_FULLY_ROUGH"), bIsFullyRough || Material->IsFullyRough());
    OutEnvironment.SetDefine(TEXT("MATERIAL_USES_ANISOTROPY"), bUsesAnisotropy);

    (......)

    // 材質參數合.
    for (int32 CollectionIndex = 0; CollectionIndex < ParameterCollections.Num(); CollectionIndex++)
    {
        const FString CollectionName = FString::Printf(TEXT("MaterialCollection%u"), CollectionIndex);
        FShaderUniformBufferParameter::ModifyCompilationEnvironment(*CollectionName, ParameterCollections[CollectionIndex]->GetUniformBufferStruct(), InPlatform, OutEnvironment);
    }
    OutEnvironment.SetDefine(TEXT("IS_MATERIAL_SHADER"), TEXT("1"));

    // 處理shading model.
    FMaterialShadingModelField ShadingModels = Material->GetShadingModels();
    if (Material->IsShadingModelFromMaterialExpression() && ShadingModelsFromCompilation.IsValid())
    {
        ShadingModels = ShadingModelsFromCompilation;
    }
    if (ShadingModels.IsLit())
    {    
        int NumSetMaterials = 0;
        if (ShadingModels.HasShadingModel(MSM_DefaultLit))
        {
            OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_DEFAULT_LIT"), TEXT("1"));
            NumSetMaterials++;
        }
        if (ShadingModels.HasShadingModel(MSM_Subsurface))
        {
            OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_SUBSURFACE"), TEXT("1"));
            NumSetMaterials++;
        }
        if (ShadingModels.HasShadingModel(MSM_PreintegratedSkin))
        {
            OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN"), TEXT("1"));
            NumSetMaterials++;
        }
        if (ShadingModels.HasShadingModel(MSM_SubsurfaceProfile))
        {
            OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE"), TEXT("1"));
            NumSetMaterials++;
        }
        
        (......)
    }
    else
    {
        OutEnvironment.SetDefine(TEXT("MATERIAL_SINGLE_SHADINGMODEL"), TEXT("1"));
        OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_UNLIT"), TEXT("1"));
    }

    (......)
}

// 擷取材質表達式轉譯後的shader代碼. (填充MaterialTemplate帶%s的空缺代碼)
FString FHLSLMaterialTranslator::GetMaterialShaderCode()
{    
    // 延遲格式化MaterialTemplate字元串.
    FLazyPrintf LazyPrintf(*MaterialTemplate);

    // 為頂點插值器配置設定插槽.
    FString VertexInterpolatorsOffsetsDefinition;
    TBitArray<> FinalAllocatedCoords = GetVertexInterpolatorsOffsets(VertexInterpolatorsOffsetsDefinition);

    const uint32 NumUserVertexTexCoords = GetNumUserVertexTexCoords();
    const uint32 NumUserTexCoords = GetNumUserTexCoords();
    const uint32 NumCustomVectors = FMath::DivideAndRoundUp((uint32)CurrentCustomVertexInterpolatorOffset, 2u);
    const uint32 NumTexCoordVectors = FinalAllocatedCoords.FindLast(true) + 1;

    LazyPrintf.PushParam(*FString::Printf(TEXT("%u"),NumUserVertexTexCoords));
    LazyPrintf.PushParam(*FString::Printf(TEXT("%u"),NumUserTexCoords));
    LazyPrintf.PushParam(*FString::Printf(TEXT("%u"),NumCustomVectors));
    LazyPrintf.PushParam(*FString::Printf(TEXT("%u"),NumTexCoordVectors));

    LazyPrintf.PushParam(*VertexInterpolatorsOffsetsDefinition);

    FString MaterialAttributesDeclaration;

    // 将材質的浮點類型序列化成HLSL類型.
    const TArray<FGuid>& OrderedVisibleAttributes = FMaterialAttributeDefinitionMap::GetOrderedVisibleAttributeList();
    for(const FGuid& AttributeID : OrderedVisibleAttributes)
    {
        const FString PropertyName = FMaterialAttributeDefinitionMap::GetAttributeName(AttributeID);
        const EMaterialValueType PropertyType = FMaterialAttributeDefinitionMap::GetValueType(AttributeID);
        switch (PropertyType)
        {
        case MCT_Float1: case MCT_Float: MaterialAttributesDeclaration += FString::Printf(TEXT("\tfloat %s;") LINE_TERMINATOR, *PropertyName); break;
        case MCT_Float2: MaterialAttributesDeclaration += FString::Printf(TEXT("\tfloat2 %s;") LINE_TERMINATOR, *PropertyName); break;
        case MCT_Float3: MaterialAttributesDeclaration += FString::Printf(TEXT("\tfloat3 %s;") LINE_TERMINATOR, *PropertyName); break;
        case MCT_Float4: MaterialAttributesDeclaration += FString::Printf(TEXT("\tfloat4 %s;") LINE_TERMINATOR, *PropertyName); break;
        case MCT_ShadingModel: MaterialAttributesDeclaration += FString::Printf(TEXT("\tuint %s;") LINE_TERMINATOR, *PropertyName); break;
        }
    }
    LazyPrintf.PushParam(*MaterialAttributesDeclaration);

    FString PixelMembersDeclaration;
    FString NormalAssignment;
    FString PixelMembersSetupAndAssignments;
    // 擷取共享的輸入材質代碼.
    GetSharedInputsMaterialCode(PixelMembersDeclaration, NormalAssignment, PixelMembersSetupAndAssignments);
    LazyPrintf.PushParam(*PixelMembersDeclaration);
    LazyPrintf.PushParam(*ResourcesString);

    if (bCompileForComputeShader)
    {
        LazyPrintf.PushParam(*GenerateFunctionCode(CompiledMP_EmissiveColorCS));
    }
    else
    {
        LazyPrintf.PushParam(TEXT("return 0"));
    }

    LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), Material->GetTranslucencyDirectionalLightingIntensity()));
    LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), Material->GetTranslucentShadowDensityScale()));
    LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), Material->GetTranslucentSelfShadowDensityScale()));
    LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), Material->GetTranslucentSelfShadowSecondDensityScale()));
    LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), Material->GetTranslucentSelfShadowSecondOpacity()));
    LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), Material->GetTranslucentBackscatteringExponent()));

    {
        FLinearColor Extinction = Material->GetTranslucentMultipleScatteringExtinction();
        LazyPrintf.PushParam(*FString::Printf(TEXT("return MaterialFloat3(%.5f, %.5f, %.5f)"), Extinction.R, Extinction.G, Extinction.B));
    }

    LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), Material->GetOpacityMaskClipValue()));
    LazyPrintf.PushParam(*GenerateFunctionCode(MP_WorldPositionOffset));
    LazyPrintf.PushParam(*GenerateFunctionCode(CompiledMP_PrevWorldPositionOffset));
    LazyPrintf.PushParam(*GenerateFunctionCode(MP_WorldDisplacement));
    LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), Material->GetMaxDisplacement()));
    LazyPrintf.PushParam(*GenerateFunctionCode(MP_TessellationMultiplier));
    LazyPrintf.PushParam(*GenerateFunctionCode(MP_CustomData0));
    LazyPrintf.PushParam(*GenerateFunctionCode(MP_CustomData1));

    // 填充自定義紋理坐标配置設定.
    FString CustomUVAssignments;
    int32 LastProperty = -1;
    for (uint32 CustomUVIndex = 0; CustomUVIndex < NumUserTexCoords; CustomUVIndex++)
    {
        if (CustomUVIndex == 0)
        {
            CustomUVAssignments += TranslatedCodeChunkDefinitions[MP_CustomizedUVs0 + CustomUVIndex];
        }

        if (TranslatedCodeChunkDefinitions[MP_CustomizedUVs0 + CustomUVIndex].Len() > 0)
        {
            if (LastProperty >= 0)
            {
                check(TranslatedCodeChunkDefinitions[LastProperty].Len() == TranslatedCodeChunkDefinitions[MP_CustomizedUVs0 + CustomUVIndex].Len());
            }
            LastProperty = MP_CustomizedUVs0 + CustomUVIndex;
        }
        CustomUVAssignments += FString::Printf(TEXT("\tOutTexCoords[%u] = %s;") LINE_TERMINATOR, CustomUVIndex, *TranslatedCodeChunks[MP_CustomizedUVs0 + CustomUVIndex]);
    }
    LazyPrintf.PushParam(*CustomUVAssignments);

    // 填充自定義頂點着色器插值器配置設定.
    FString CustomInterpolatorAssignments;
    for (UMaterialExpressionVertexInterpolator* Interpolator : CustomVertexInterpolators)
    {
        if (Interpolator->InterpolatorOffset != INDEX_NONE)
        {
            const EMaterialValueType Type = Interpolator->InterpolatedType == MCT_Float ? MCT_Float1 : Interpolator->InterpolatedType;
            const TCHAR* Swizzle[2] = { TEXT("x"), TEXT("y") };
            const int32 Offset = Interpolator->InterpolatorOffset;
            const int32 Index = Interpolator->InterpolatorIndex;

            CustomInterpolatorAssignments += FString::Printf(TEXT("\tOutTexCoords[VERTEX_INTERPOLATOR_%i_TEXCOORDS_X].%s = VertexInterpolator%i(Parameters).x;") LINE_TERMINATOR, Index, Swizzle[Offset%2], Index);
                
            if (Type >= MCT_Float2)
            {
                (......)
            }
        }
    }
    LazyPrintf.PushParam(*CustomInterpolatorAssignments);

    // Normal所需的初始化器
    LazyPrintf.PushParam(*TranslatedCodeChunkDefinitions[MP_Normal]);
    LazyPrintf.PushParam(*NormalAssignment);
    //最後是剩餘的通用代碼,然後是對每個輸入的指派
    LazyPrintf.PushParam(*PixelMembersSetupAndAssignments);
    LazyPrintf.PushParam(*FString::Printf(TEXT("%u"),MaterialTemplateLineNumber));

    return LazyPrintf.GetResultString();
}
           

現在簡明扼要地總結FHLSLMaterialTranslator的幾個重要接口:

  • Translate:轉譯材質藍圖的材質節點表達式,将所有材質屬性的編譯結果填充到格子的FShaderCodeChunk中。
  • GetMaterialEnvironment:處理材質藍圖的編譯環境(宏定義)。
  • GetMaterialShaderCode:填充MaterialTemplate.ush的空缺代碼,根據Translate編譯的FShaderCodeChunk對應的材質屬性接口,以及其它的宏定義、結構體、工具類接口。

經過FHLSLMaterialTranslator的編譯之後,将獲得完整的材質Shader代碼,便會送入FMaterialShaderMap::Compile接口進行編譯,編譯後的shader代碼儲存到FMaterialShaderMap之中。

調用FMaterial::BeginCompileShaderMap的接口隻有FMaterial::CacheShaders,調用FMaterial::CacheShaders的核心調用堆棧如下:

    • UMaterial::CacheShadersForResources
  • UMaterialInstance::CacheResourceShadersForRendering
    • UMaterialInstance::CacheShadersForResources

而CacheResourceShadersForRendering調用邏輯很多,包含UMaterial和UMaterialInstance的各種設定接口:

  • SetMaterialUsage
  • UpdateMaterialShaderCacheAndTextureReferences
  • PostLoad
  • PostEditChangePropertyInternal
  • ForceRecompileForRendering
  • AllMaterialsCacheResourceShadersForRendering

以上接口中,必定會調用的是PostLoad。

至此,材質編譯的整個流程就明晰了,流程圖如下所示(以UMaterial為例,UMaterialInstance也一樣):

graph TD

A1[UMaterial::SetMaterialUsage] --> B(UMaterial::CacheResourceShadersForRendering)

A2[UMaterial::PostLoad] --> B(UMaterial::CacheResourceShadersForRendering)

A3[UMaterial::ForceRecompileForRendering] --> B(UMaterial::CacheResourceShadersForRendering)

B --> C(UMaterial::CacheShadersForResources)

C --> C1(FMaterial::CacheShaders)

C1 -->D(FMaterial::BeginCompileShaderMap)

D -->E(FHLSLMaterialTranslator::Translate)

E -->F(FHLSLMaterialTranslator::GetMaterialEnvironment)

F -->G(FHLSLMaterialTranslator::GetMaterialShaderCode)

G -->H(FMaterialShaderMap::Compile)

H -->I(處理編譯結果)

實際上,在編譯不同類型的shader時,需要的資料不完全一樣:

類型 組成
GlobalShader Shader_x.usf
MaterialShader Shader_x.usf + MaterialTemplate_x.usf
MeshMaterialShader Shader_x.usf + MaterialTemplate_x.usf + VertexFactory_x.usf

其中:

  • Shader_x.usf:引擎Shader目錄下的已有檔案,如DeferredLightVertexShaders.usf、DeferredLightPixelShaders.usf。
  • MaterialTemplate_x.usf:FHLSLMaterialTranslator編譯材質藍圖後填充MaterialTemplate.ush的代碼。
  • VertexFactory_x.usf:引擎Shader目錄下的已有頂點工廠檔案代碼,如LocalVertexFactory.ush、GpuSkinVertexFactory.ush。

另外,生成同個材質藍圖的不同着色頻率的shader代碼,所需的資料也有所不同:

着色頻率
PixelShader MaterialTemplate_x.usf + VertexFactory_x.usf + PixelShader_x.usf
VertexShader MaterialTemplate_x.usf + VertexFactory_x.usf + VertexShader_x.usf
GeometryShader
  • PixelShader_x.usf:某個渲染Pass的PixelShader代碼,如BasePassPixelShader.usf、ShadowDepthPixelShader.usf、DepthOnlyPixelShader.usf。
  • VertexShader_x.usf:某個渲染Pass的VertexShader代碼,如BasePassVertexShader.usf、ShadowDepthVertexShader.usf、DepthOnlyVertexShader.usf。

另外提一下,在材質編輯器之中是可以檢視填充MaterialTemplate之後的各個目标平台代碼的:

剖析虛幻渲染體系(09)- 材質體系

下圖是打開HLSL之後的代碼預覽界面:

剖析虛幻渲染體系(09)- 材質體系

本章将講述材質的開發案例、調試技巧和優化技術。

在材質和材質執行個體編輯器都可以檢視目前材質編譯後的指令數量、紋理采樣數量、插值器資料等:

剖析虛幻渲染體系(09)- 材質體系

上:材質編輯器的統計資料視窗;下:材質執行個體編輯器的統計資料。

打開材質編輯器上側的Platform Stats(平台資料)之後,可以檢視指定平台更詳細的資料(PS、VS、采樣器等等):

剖析虛幻渲染體系(09)- 材質體系

點選上圖的Settings按鈕,可以檢視其它平台的資料。

剖析虛幻渲染體系(09)- 材質體系

打開材質編輯器的菜單Asset / Size Map,可以打開材質資源的占用空間(磁盤和記憶體)布局圖:

剖析虛幻渲染體系(09)- 材質體系

打開材質編輯器的菜單Windows / Developer Tools / Material Analyzer,可以檢視指定材質的層級、各種類型屬性的數量:

剖析虛幻渲染體系(09)- 材質體系

如果某些資料異常或可改進,系統會提示并給出修改意見。例如下圖提示有多個材質擁有相同的排列:

剖析虛幻渲染體系(09)- 材質體系

可以點選Create Local Collection将所有相關執行個體放置到一個本地集合中,這樣就可以輕松地找到并更新它們,以獲得更有效的材質參數設定。

當然,上面的材質分析器一次隻能手動選擇一個材質,效率很慢。實際上可以基于此開發批處理檢查工具,甚至可以定制某些規則,自動建立本地參數集合,以提升材質優化的效率和效果。

另外,在編輯材質藍圖時,可以注意以下幾點:

1、注意靜态變量、開關、等級和FeatureLevel的數量,這些通常會增加排列數量。

2、合并材質參數,減少或避免不必要的材質節點。

3、合理增加注解,子產品化并抽象成材質函數,對于複雜的材質非常必要,有利于維護和擴充,提升材質複用率。

4、雖然材質節點大多數是PS節點,但也有少量是VS節點,如下所示:

剖析虛幻渲染體系(09)- 材質體系

特别是VertexInterpolator節點,可以将部分邏輯放到VS計算,然後利用VertexInterpolator進行硬體插值之後輸出PS的值:

剖析虛幻渲染體系(09)- 材質體系

VertexInterpolator支援Float~Float4的插值,最多支援4個Float4插值,如果想知道目前VertexInterpolator的數量,可以在Stats視窗看到:

剖析虛幻渲染體系(09)- 材質體系

另外Shader Stage Switch也可以差別對待VS和PS的值:

剖析虛幻渲染體系(09)- 材質體系

本小節以添加名為

MyCustomOperation

的材質節點為例。

首先需要在FMaterialCompiler和FHLSLMaterialTranslator添加相關接口和實作:

// Engine\Source\Runtime\Engine\Public\MaterialCompiler.h

class FMaterialCompiler
{
public:
    virtual int32 MyCustomOperation(int32 A, int32 B) { return 0; } // 不寫成抽象接口, 防止其它子類報錯.
    
    (......)
};

// Engine\Source\Runtime\Engine\Private\Materials\HLSLMaterialTranslator.h

class FHLSLMaterialTranslator : public FMaterialCompiler
{
public:
    virtual int32 MyCustomOperation(int32 A, int32 B) override;
    
    (......)
};

// Engine\Source\Runtime\Engine\Private\Materials\HLSLMaterialTranslator.cpp

int32 FHLSLMaterialTranslator::MyCustomOperation(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))
    {
        // 既然是自定義的操作節點, 可以随意指定符合HLSL标準文法的代碼片段^_^
        return AddUniformExpressionWithHash(Hash, new FMaterialUniformExpressionFoldedMath(GetParameterUniformExpression(A),GetParameterUniformExpression(B),FMO_Add),GetArithmeticResultType(A,B),TEXT("(%s + %s * 0.5)"),*GetParameterCode(A),*GetParameterCode(B));
    }
    else
    {
        return AddCodeChunkWithHash(Hash, GetArithmeticResultType(A,B),TEXT("(%s + %s * 0.5)"),*GetParameterCode(A),*GetParameterCode(B));
    }
}
           

實作以上接口之後,需要添加對應的材質表達式的類型和檔案:

// Engine\Source\Runtime\Engine\Classes\Materials\MaterialExpressionMyCustomOperation.h

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "MaterialExpressionIO.h"
#include "Materials/MaterialExpression.h"
// UBT編譯而成的頭檔案, 不能遺漏.
#include "MaterialExpressionMyCustomOperation.generated.h"

UCLASS(MinimalAPI)
class UMaterialExpressionMyCustomOperation : public UMaterialExpression
{
    GENERATED_UCLASS_BODY()

    UPROPERTY(meta = (RequiredInput = "false", ToolTip = "Defaults to 'ConstA' if not specified"))
    FExpressionInput A;
    
    UPROPERTY(meta = (RequiredInput = "false", ToolTip = "Defaults to 'ConstB' if not specified"))
    FExpressionInput B;

    UPROPERTY(EditAnywhere, Category=MaterialExpressionAdd, meta=(OverridingInputProperty = "A"))
    float ConstA;
    
    UPROPERTY(EditAnywhere, Category=MaterialExpressionAdd, meta=(OverridingInputProperty = "B"))
    float ConstB;

    //~ Begin UMaterialExpression Interface
#if WITH_EDITOR
    virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
    virtual void GetCaption(TArray<FString>& OutCaptions) const override;
#endif // WITH_EDITOR
    //~ End UMaterialExpression Interface
};

// Engine\Source\Runtime\Engine\Private\Materials\MaterialExpressions.cpp

// 增加新節點的頭檔案引用.
#include "Materials/MaterialExpressionMyCustomOperation.h"


// 預設構造函數.
UMaterialExpressionMyCustomOperation::UMaterialExpressionMyCustomOperation(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    // Structure to hold one-time initialization
    struct FConstructorStatics
    {
        FText NAME_Math;
        FConstructorStatics()
            : NAME_Math(LOCTEXT( "Math", "Math" ))
        {
        }
    };
    static FConstructorStatics ConstructorStatics;

    ConstA = 0.0f;
    ConstB = 1.0f;

#if WITH_EDITORONLY_DATA
    MenuCategories.Add(ConstructorStatics.NAME_Math);
#endif
}

// 編譯.
int32 UMaterialExpressionMyCustomOperation::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
    int32 Arg1 = A.GetTracedInput().Expression ? A.Compile(Compiler) : Compiler->Constant(ConstA);
    int32 Arg2 = B.GetTracedInput().Expression ? B.Compile(Compiler) : Compiler->Constant(ConstB);

    return Compiler->MyCustomOperation(Arg1, Arg2);
}

// 說明.
void UMaterialExpressionMyCustomOperation::GetCaption(TArray<FString>& OutCaptions) const
{
    FString ret = TEXT("MyCustomOperation");

    FExpressionInput ATraced = A.GetTracedInput();
    FExpressionInput BTraced = B.GetTracedInput();
    if(!ATraced.Expression || !BTraced.Expression)
    {
        ret += TEXT("(");
        ret += ATraced.Expression ? TEXT(",") : FString::Printf( TEXT("%.4g,"), ConstA);
        ret += BTraced.Expression ? TEXT(")") : FString::Printf( TEXT("%.4g)"), ConstB);
    }

    OutCaptions.Add(ret);
}
           

添加以上代碼後,編譯引擎代碼,啟動UE編輯器,打開材質編輯器,便可以在節點中搜尋到MyCustomOperation材質節點并使用它了(下圖)。

剖析虛幻渲染體系(09)- 材質體系

UE自帶的Custom材質節點雖然可以編寫任意代碼,但它限制在某個函數内。比如在Custom編寫了以下代碼:

return 1.0;
           

實際上HLSL編譯器會将它編譯成類似以下的代碼:

float CustomExpression0()
{
    return 1.0;
}
           

限制在了函數體内,将極大地限制我們的發揮,比如不能在Custom節點定義函數、結構體,也不能引用其它ush檔案。

正常且快速的做法是在Custom階段的前後增加大括号:

return 0.0; } // 比對編譯器的{

float MyParameter = 1.0;

// 正常的代碼.
float MyFunction()
{
    return MyParameter;
// 此處不需要加}, 因為編譯器後面會加
           

生成的HLSL代碼如下所示:

float CustomExpression0()
{
    return 1.0;
}

float MyParameter = 1.0;

float MyFunction()
{
    return MyParameter;
}
           

但是這種方式非常不雅觀,而且材質節點獲得的結果是第一個return的值。下面是另外一種稍微優雅一點的方式,支援多個函數和變量的定義:

struct FMyStruct
{
    float3 ColorDensity;
    
    float3 ColorOperation(float3 Color)
    {
        return Color * ColorDensity;
    } 
    
    float3 Out()
    {
        // InColor是函數輸入參數.
        return ColorOperation(InColor);
    }
};

FMyStruct MyStruct;
FMyStruct.ColorDensity = float3(0.5, 0.5, 0);

return MyStruct.Out();
           

上面的代碼利用HLSL的函數體内可以定義局部結構體的特性,支援了對任意數量的函數和變量的定義。

MaterialTemplate.ush定義了大量全局的宏、結構體、接口,我們當然也可以修改它,增加所需的代碼或資料,比如:

  • 增加全局靜态變量。
  • 增加宏定義。
  • 增加引用檔案。
  • 增加%s的空缺代碼,在HLSL編譯器填充它。
  • 增加結構體。
  • 增加自定義函數。

以上所有資料和接口,可以結合Custom節點和MaterialFunction,暴露給材質藍圖通路。

舉個例子,比如我們在MaterialTemplate.ush增加一個全局靜态常量:

// Engine\Shaders\Private\MaterialTemplate.ush

// 緊挨着include之後.
static const float MyGlobalParameter = 0.5;
           

編譯引擎,啟動後打開材質編輯器,新增一個材質函數,在材質函數增加一個Custom節點,Custom節點的代碼如下:

return MyGlobalParameter;
           

然後勾選材質函數的Expose to Library:

剖析虛幻渲染體系(09)- 材質體系

這樣就可以在材質節點中搜尋并應用改材質函數了:

剖析虛幻渲染體系(09)- 材質體系

本篇主要闡述了UE的材質體系的基礎概念、類型、機制,希望童鞋們學習完本篇之後,對UE的材質不再陌生,能夠輕松自如地掌握、應用、擴充它。

按慣例,本篇也布置一些小思考,以助了解和加深UE材質體系的掌握和了解:

  • UMaterialInterface、FMaterialRenderProxy、FMaterial的關聯和差別是什麼?為什麼要有這麼多材質的類型?
  • 材質渲染時的資料更新流程是什麼?
  • 材質藍圖編譯的流程是怎樣的?編譯器的主要接口功能是什麼?
  • 編輯材質時,需要注意哪些性能資料?如何檢視?
  • 增加新的Shading Model,以支援二次元風格的渲染。

  • 感謝所有參考文獻的作者,部分圖檔來自參考文獻和網絡,侵删。
  • 本系列文章為筆者原創,隻發表在部落格園上,歡迎分享本文連結,但未經同意,不允許轉載!
  • 系列文章,未完待續,完整目錄請戳内容綱目。

  • Unreal Engine Source
  • Rendering and Graphics
  • Materials
  • Graphics Programming
  • UE4渲染子產品分析
  • UE4 Render System Sheet
  • 【UE4 Renderer】<03> PipelineBase
  • UE4材質系統源碼分析之UMaterial和材質節點介紹
  • UE4材質系統源碼分析之材質編譯成HLSL CODE
  • UE4 HLSL 和 Shader 開發指南和技巧
  • Material Analyzer
  • Unreal Engine4 Custom Function
  • Unreal Engine 4 Rendering Part 6: Adding a new Shading Model

繼續閱讀