9.1 本篇概述
材質(Material)是UE渲染體系中非常重要的基礎體系,包含了材質藍圖、渲染狀態、幾何體屬性等等數據。由於UE做了各種跨平台和細節封裝,使得材質體系顯得龐大且復雜。本篇就視圖從表面到底層,層層剖析它的技術、特征和機制。
前面很多篇章都有大量涉及材質的概念、類型和代碼,本篇將更加深入且廣泛低闡述它的體系。主要闡述UE的以下內容:
- 材質的基礎概念和基礎類型。
- 材質的實現層級。
- 材質的實現和原理。
- 材質的使用方法和用例。
材質體系建立於Shader體系之上,是鏈接圖形程序、TA、藝術家的最主要的橋梁。
9.2 材質基礎
本章將分析材質涉及的基礎概念和類型,闡述它們之間的基本關系和使用方法。
9.2.1 UMaterial
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正是對應着材質編輯器的資源,也是我們最常接觸的對象:
我們最常接觸的材質編輯器編輯的正是UMaterial實例,左側屬性面板的選項跟UMaterial的聲明一致。
UMaterial擁有唯一一個特殊的子類UPreviewMaterial,它主要用於編輯器相關的材質預覽,如材質編輯器左上角的預覽窗口、瀏覽器的材質縮略圖等等。
9.2.2 UMaterialInstance
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顧名思義固定材質實例,用於編輯器預先創建和編輯好的材質實例資源:
材質實例編輯器一覽。右側中間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級層級:
4級材質實例層級。其中最頂層是UMaterial實例,下面 的3級則是UMaterialInstance實例。
若嘗試將頂層或中間層的父類置為空,UE也可以容忍,只是材質的渲染效果會異常:
9.2.3 FMaterialRenderProxy
我們知道圖元、網格、光源等場景的類型都有游戲線程代表和渲染線程代表,而材質也不例外。作為游戲線程代表的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的專用於渲染線程,如果沒有特別說明,一般(非絕對)用於渲染線程。如果錯誤地調用了不該調用的接口或訪問了數據,將出現競爭條件,引發隨機崩潰,增加指數級的調試難度。
9.2.4 FMaterial, FMaterialResource
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);
}
}
9.2.5 材質總覽
前幾幾節已經詳細闡述了材質體系內的基礎類型、概念和它們的定義。本節直接上它們的UML圖,以統覽它們的關系:
如果上圖文字太小看不清,可以點擊放大下面的圖片版本:
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);
(......)
}
9.3 材質機制
本章主要分析材質的部分底層機制,比如材質的渲染、編譯機制和過程、材質的緩存策略等。
9.3.1 材質渲染
本節將闡述材質的數據傳遞、更新、渲染邏輯。
材質數據的發起者依然是游戲線程側的資源,一般是從磁盤加載的二進制資源,然后序列化成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觸發,調用堆棧如下所示:
- UMaterial::PostLoad
- ProcessSerializedInlineShaderMaps
- FindOrCreateMaterialResource
- ProcessSerializedInlineShaderMaps
此外,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::SetGameThreadShaderMap
-
ProcessSerializedInlineShaderMaps
- FMaterial::SetInlineShaderMap
-
SetShaderMapsOnMaterialResources_RenderThread
- FMaterial::SetRenderingThreadShaderMap
雖然上面有很多接口最終會設置到FMaterial的RenderingThreadShaderMap,不過多數情況下,運行時RenderingThreadShaderMap被設置的調用堆棧如下:
- UMaterial::PostLoad
- ProcessSerializedInlineShaderMaps
- FMaterial::SetInlineShaderMap
- ProcessSerializedInlineShaderMaps
一旦FMaterial的RenderingThreadShaderMap被正確設置,材質相關的其它眾多數據將被渲染線程和渲染器自由地讀取,如同魚兒無憂無慮地遨游在湛藍的大海之中。
9.3.2 材質編譯
在上一篇文章Shader體系中,已經闡述過Shader的編譯過程,不過本節講述的是如何將材質藍圖轉成HLSL代碼的過程和機制。
前面幾個小節先了解材質藍圖編譯過程涉及的主要類型和概念。
9.3.2.1 UMaterialExpression
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內置了很多材質節點,下圖是其中一小部分子類:
下面選兩個最常用的子類作為分析的案例:
// 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片段。
9.3.2.2 UMaterialGraphNode
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
(......)
};
材質節點包含了圖形界面的信息和對應的表達式,采用了視圖和數據相分離的經典設計模式。
9.3.2.3 UMaterialGraph
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;
(......)
};
9.3.2.4 FHLSLMaterialTranslator
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渲染器和材質烘焙。
9.3.2.5 MaterialTemplate.ush
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模塊引用。
- 待填充的宏定義。
- 待填充的接口實現。
- 頂點、像素、材質屬性等結構體定義。部分結構體待填充。
- 材質屬性、數據處理、表達式、工具類接口定義。部分接口待填充。
9.3.2.6 材質編譯流程
上面幾個小節詳細分析了材質編譯涉及到的核心類型,本節將直接分析材質藍圖編譯換成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::FinishCompilation
- FMaterial::SerializeInlineShaderMap
- SerializeInlineShaderMaps
其中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::CacheResourceShadersForRendering
- UMaterial::CacheShadersForResources
- FMaterial::CacheShaders
- UMaterial::CacheShadersForResources
- UMaterialInstance::CacheResourceShadersForRendering
- UMaterialInstance::CacheShadersForResources
- FMaterial::CacheShaders
- UMaterialInstance::CacheShadersForResources
而CacheResourceShadersForRendering調用邏輯很多,包含UMaterial和UMaterialInstance的各種設置接口:
- SetMaterialUsage
- UpdateMaterialShaderCacheAndTextureReferences
- PostLoad
- PostEditChangePropertyInternal
- ForceRecompileForRendering
- AllMaterialsCacheResourceShadersForRendering
以上接口中,必定會調用的是PostLoad。
至此,材質編譯的整個流程就明晰了,流程圖如下所示(以UMaterial為例,UMaterialInstance也一樣):
實際上,在編譯不同類型的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 | MaterialTemplate_x.usf + VertexFactory_x.usf + VertexShader_x.usf |
其中:
- PixelShader_x.usf:某個渲染Pass的PixelShader代碼,如BasePassPixelShader.usf、ShadowDepthPixelShader.usf、DepthOnlyPixelShader.usf。
- VertexShader_x.usf:某個渲染Pass的VertexShader代碼,如BasePassVertexShader.usf、ShadowDepthVertexShader.usf、DepthOnlyVertexShader.usf。
另外提一下,在材質編輯器之中是可以查看填充MaterialTemplate之后的各個目標平台代碼的:
下圖是打開HLSL之后的代碼預覽界面:
9.4 材質開發
本章將講述材質的開發案例、調試技巧和優化技術。
9.4.1 材質調試與優化
在材質和材質實例編輯器都可以查看當前材質編譯后的指令數量、紋理采樣數量、插值器數據等:
上:材質編輯器的統計數據窗口;下:材質實例編輯器的統計數據。
打開材質編輯器上側的Platform Stats(平台數據)之后,可以查看指定平台更詳細的數據(PS、VS、采樣器等等):
點擊上圖的Settings按鈕,可以查看其它平台的數據。
打開材質編輯器的菜單Asset / Size Map,可以打開材質資源的占用空間(磁盤和內存)布局圖:
打開材質編輯器的菜單Windows / Developer Tools / Material Analyzer,可以查看指定材質的層級、各種類型屬性的數量:
如果某些數據異常或可改進,系統會提示並給出修改意見。例如下圖提示有多個材質擁有相同的排列:
可以點擊Create Local Collection將所有相關實例放置到一個本地集合中,這樣就可以輕松地找到並更新它們,以獲得更有效的材質參數設置。
當然,上面的材質分析器一次只能手動選擇一個材質,效率很慢。實際上可以基於此開發批處理檢查工具,甚至可以定制某些規則,自動創建本地參數集合,以提升材質優化的效率和效果。
另外,在編輯材質藍圖時,可以注意以下幾點:
1、注意靜態變量、開關、等級和FeatureLevel的數量,這些通常會增加排列數量。
2、合並材質參數,減少或避免不必要的材質節點。
3、合理增加注解,模塊化並抽象成材質函數,對於復雜的材質非常必要,有利於維護和擴展,提升材質復用率。
4、雖然材質節點大多數是PS節點,但也有少量是VS節點,如下所示:
特別是VertexInterpolator節點,可以將部分邏輯放到VS計算,然后利用VertexInterpolator進行硬件插值之后輸出PS的值:
VertexInterpolator支持Float~Float4的插值,最多支持4個Float4插值,如果想知道當前VertexInterpolator的數量,可以在Stats窗口看到:
另外Shader Stage Switch也可以區別對待VS和PS的值:
9.4.2 材質開發案例
9.4.2.1 增加材質節點
本小節以添加名為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材質節點並使用它了(下圖)。
9.4.2.2 擴展自定義節點
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的函數體內可以定義局部結構體的特性,支持了對任意數量的函數和變量的定義。
9.4.2.3 擴展材質模板
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:
這樣就可以在材質節點中搜索並應用改材質函數了:
9.5 本篇總結
本篇主要闡述了UE的材質體系的基礎概念、類型、機制,希望童鞋們學習完本篇之后,對UE的材質不再陌生,能夠輕松自如地掌握、應用、擴展它。
9.5.1 本篇思考
按慣例,本篇也布置一些小思考,以助理解和加深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