--- title: 剖析虚幻渲染体系(08)- Shader体系 date: 2024-02-04 21:44:10 excerpt: tags: rating: ⭐ --- # 前言 原文地址:https://www.cnblogs.com/timlly/p/15092257.html # FShader ```c++ class FShader { public: // 在编译触发之前修改编译环境参数, 可由子类覆盖. static void ModifyCompilationEnvironment(const FShaderPermutationParameters&, FShaderCompilerEnvironment&) {} // 是否需要编译指定的排列, 可由子类覆盖. static bool ShouldCompilePermutation(const FShaderPermutationParameters&) { return true; } // 检测编译结果是否有效, 可由子类覆盖. static bool ValidateCompiledResult(EShaderPlatform InPlatform, const FShaderParameterMap& InParameterMap, TArray& OutError) { return true; } // 取得RayTracingPayloadType static ERayTracingPayloadType GetRayTracingPayloadType(const int32 PermutationId) { return static_cast(0); } // 获取各类数据的Hash的接口. RENDERCORE_API const FSHAHash& GetHash() const; RENDERCORE_API const FSHAHash& GetVertexFactoryHash() const; RENDERCORE_API const FSHAHash& GetOutputHash() const; /** Returns an identifier suitable for deterministic sorting of shaders between sessions. */ uint32 GetSortKey() const { return SortKey; } // 保存并检测shader代码的编译结果. RENDERCORE_API void Finalize(const FShaderMapResourceCode* Code); // 数据获取接口. inline FShaderType* GetType(const FShaderMapPointerTable& InPointerTable) const { return Type.Get(InPointerTable.ShaderTypes); } inline FShaderType* GetType(const FPointerTableBase* InPointerTable) const { return Type.Get(InPointerTable); } inline FVertexFactoryType* GetVertexFactoryType(const FShaderMapPointerTable& InPointerTable) const { return VFType.Get(InPointerTable.VFTypes); } inline FVertexFactoryType* GetVertexFactoryType(const FPointerTableBase* InPointerTable) const { return VFType.Get(InPointerTable); } inline FShaderType* GetTypeUnfrozen() const { return Type.GetUnfrozen(); } inline int32 GetResourceIndex() const { checkSlow(ResourceIndex != INDEX_NONE); return ResourceIndex; } inline EShaderPlatform GetShaderPlatform() const { return Target.GetPlatform(); } inline EShaderFrequency GetFrequency() const { return Target.GetFrequency(); } inline const FShaderTarget GetTarget() const { return Target; } inline bool IsFrozen() const { return Type.IsFrozen(); } inline uint32 GetNumInstructions() const { return NumInstructions; } #if WITH_EDITORONLY_DATA inline uint32 GetNumTextureSamplers() const { return NumTextureSamplers; } inline uint32 GetCodeSize() const { return CodeSize; } inline void SetNumInstructions(uint32 Value) { NumInstructions = Value; } #else inline uint32 GetNumTextureSamplers() const { return 0u; } inline uint32 GetCodeSize() const { return 0u; } #endif // 尝试返回匹配指定类型的自动绑定的Uniform Buffer, 如果不存在则返回未绑定的. template FORCEINLINE_DEBUGGABLE const TShaderUniformBufferParameter& GetUniformBufferParameter() const { const FShaderUniformBufferParameter& FoundParameter = GetUniformBufferParameter(UniformBufferStructType::FTypeInfo::GetStructMetadata()); return static_cast&>(FoundParameter); } FORCEINLINE_DEBUGGABLE const FShaderUniformBufferParameter& GetUniformBufferParameter(const FShaderParametersMetadata* SearchStruct) const { const FHashedName SearchName = SearchStruct->GetShaderVariableHashedName(); return GetUniformBufferParameter(SearchName); } FORCEINLINE_DEBUGGABLE const FShaderUniformBufferParameter& GetUniformBufferParameter(const FHashedName SearchName) const { int32 FoundIndex = INDEX_NONE; TArrayView UniformBufferParameterStructsView(UniformBufferParameterStructs); for (int32 StructIndex = 0, Count = UniformBufferParameterStructsView.Num(); StructIndex < Count; StructIndex++) { if (UniformBufferParameterStructsView[StructIndex] == SearchName) { FoundIndex = StructIndex; break; } } if (FoundIndex != INDEX_NONE) { const FShaderUniformBufferParameter& FoundParameter = UniformBufferParameters[FoundIndex]; return FoundParameter; } else { // This can happen if the uniform buffer was not bound // There's no good way to distinguish not being bound due to temporary debugging / compiler optimizations or an actual code bug, // Hence failing silently instead of an error message static FShaderUniformBufferParameter UnboundParameter; return UnboundParameter; } } RENDERCORE_API const FShaderParametersMetadata* FindAutomaticallyBoundUniformBufferStruct(int32 BaseIndex) const; RENDERCORE_API void DumpDebugInfo(const FShaderMapPointerTable& InPtrTable); #if WITH_EDITOR RENDERCORE_API void SaveShaderStableKeys(const FShaderMapPointerTable& InPtrTable, EShaderPlatform TargetShaderPlatform, int32 PermutationId, const struct FStableShaderKeyAndValue& SaveKeyVal); #endif // WITH_EDITOR /** Returns the meta data for the root shader parameter struct. */ static inline const FShaderParametersMetadata* GetRootParametersMetadata() { return nullptr; } private: RENDERCORE_API void BuildParameterMapInfo(const TMap& ParameterMap); public: // 着色器参数绑定. LAYOUT_FIELD(FShaderParameterBindings, Bindings); // 着色器参数绑定的映射信息. LAYOUT_FIELD(FShaderParameterMapInfo, ParameterMapInfo); protected: LAYOUT_FIELD(TMemoryImageArray, UniformBufferParameterStructs); LAYOUT_FIELD(TMemoryImageArray, UniformBufferParameters); // 下面3个是编辑器参数. // 着色器的编译输出和结果参数映射的哈希值, 用于查找匹配的资源. LAYOUT_FIELD_EDITORONLY(FSHAHash, OutputHash); // 顶点工厂资源哈希值 LAYOUT_FIELD_EDITORONLY(FSHAHash, VFSourceHash); // Shader资源哈希值. LAYOUT_FIELD_EDITORONLY(FSHAHash, SourceHash); private: // 着色器类型. LAYOUT_FIELD(TIndexedPtr, Type); // 顶点工厂类型. LAYOUT_FIELD(TIndexedPtr, VFType); // 目标平台和着色频率(frequency). LAYOUT_FIELD(FShaderTarget, Target); // 在FShaderMapResource的shader索引. LAYOUT_FIELD(int32, ResourceIndex); // shader指令数. LAYOUT_FIELD(uint32, NumInstructions); /** Truncated version of OutputHash, intended for sorting. Not suitable for unique shader identification. */ LAYOUT_FIELD(uint32, SortKey); // 纹理采样器数量. LAYOUT_FIELD_EDITORONLY(uint32, NumTextureSamplers); // shader代码尺寸. LAYOUT_FIELD_EDITORONLY(uint32, CodeSize); }; ``` 以上可知,FShader存储着Shader关联的绑定参数、顶点工厂、编译后的各类资源等数据,并提供了编译器修改和检测接口,还有各类数据获取接口。 FShader实际上是个基础父类,它的子类有: - **FGlobalShader**:全局着色器,它的子类在内存中只有唯一的实例,常用于屏幕方块绘制、后处理等。相比父类FShader,增加了SetParameters设置视图统一缓冲的接口。FGlobalShader包含了后处理、光照、工具类、可视化、地形、虚拟纹理等方面的Shader代码,可以是VS、PS、CS,但CS必然是FGlobalShader的子类 - **FMaterialShader**:材质着色器,由FMaterialShaderType指定的材质引用的着色器,是材质蓝图在实例化后的一个shader子集。FMaterialShader主要包含了模型、专用Pass、体素化等方面的Shader代码,可以是VS、PS、GS等,但不会有CS。 ## Shader Parameter 位于`Engine\Source\Runtime\RenderCore\Public\ShaderParameters.h`。 - FShaderParameter:着色器的寄存器绑定参数, 它的类型可以是float1/2/3/4,数组等。 - FShaderResourceParameter:着色器资源绑定(纹理或采样器)。 - FRWShaderParameter:绑定了UAV或SRV资源的类型。 - FShaderUniformBufferParameter:着色器统一缓冲参数。 ## Uniform Buffer 位于`Engine\Source\Runtime\RHI\Public\RHIResources.h`。 UE的Uniform Buffer涉及了几个核心的概念,最底层的是RHI层的FRHIUniformBuffer,封装了各种图形API的统一缓冲区(也叫Constant Buffer)。 ```c++ class FRHIUniformBuffer : public FRHIResource { public: // 构造函数. FRHIUniformBuffer(const FRHIUniformBufferLayout& InLayout); // 引用计数操作. uint32 AddRef() const; uint32 Release() const; // 数据获取接口. uint32 GetSize() const; const FRHIUniformBufferLayout& GetLayout() const; bool IsGlobal() const; private: // RHI Uniform Buffer的布局. const FRHIUniformBufferLayout* Layout; // 缓冲区尺寸. uint32 LayoutConstantBufferSize; }; // 定义FRHIUniformBuffer的引用类型. typedef TRefCountPtr FUniformBufferRHIRef; // Engine\Source\Runtime\RenderCore\Public\ShaderParameterMacros.h // 引用了指定类型的FRHIUniformBuffer的实例资源. 注意是继承了FUniformBufferRHIRef. template class TUniformBufferRef : public FUniformBufferRHIRef { public: TUniformBufferRef(); // 根据给定的值创建Uniform Buffer, 并返回结构体引用. (模板) static TUniformBufferRef CreateUniformBufferImmediate(const TBufferStruct& Value, EUniformBufferUsage Usage, EUniformBufferValidation Validation = EUniformBufferValidation::ValidateResources); // 根据给定的值创建[局部]的Uniform Buffer, 并返回结构体引用. static FLocalUniformBuffer CreateLocalUniformBuffer(FRHICommandList& RHICmdList, const TBufferStruct& Value, EUniformBufferUsage Usage); // 立即刷新缓冲区数据到RHI. void UpdateUniformBufferImmediate(const TBufferStruct& Value); private: // 私有构造体, 只能给TUniformBuffer和TRDGUniformBuffer创建. TUniformBufferRef(FRHIUniformBuffer* InRHIRef); template friend class TUniformBuffer; friend class TRDGUniformBuffer; }; ``` 最后TUniformBuffer和TRDGUniformBuffer引用了FUniformBufferRHIRef。TUniformBuffer为`TUniformBufferRef UniformBufferRHI`成员变量;TRDGUniformBuffer为`TRefCountPtr UniformBufferRHI`。 ![[UE_Uniform.png]] ### 定义宏 - SHADER_PARAMETER_STRUCT_REF:引用着色器参数结构体(全局的才行) - SHADER_PARAMETER_STRUCT_INCLUDE:包含着色器参数结构体(局部或全局都行) # Vertex Factory 我们知道,在引擎中存在着静态网格、蒙皮骨骼、程序化网格以及地形等等类型的网格类型,而材质就是通过顶点工厂FVertexFactory来支持这些网格类型。实际上,顶点工厂要涉及各方面的数据和类型,包含但不限于: - 顶点着色器。顶点着色器的输入输出需要顶点工厂来表明数据的布局。 - 顶点工厂的参数和RHI资源。这些数据将从C++层传入到顶点着色器中进行处理。 - 顶点缓冲和顶点布局。通过顶点布局,我们可以自定义和扩展顶点缓冲的输入,从而实现定制化的Shader代码。 - 几何预处理。顶点缓冲、网格资源、材质参数等等都可以在真正渲染前预处理它们。 ![[UE_VertexFactory.png]] **顶点工厂在渲染层级中的关系。由图可知,顶点工厂是渲染线程的对象,横跨于CPU和GPU两端。** ```c++ // Engine\Source\Runtime\RHI\Public\RHI.h // 顶点元素. struct FVertexElement { uint8 StreamIndex; // 流索引 uint8 Offset; // 偏移 TEnumAsByte Type; // 类型 uint8 AttributeIndex;// 属性索引 uint16 Stride; // 步长 // 实例索引或顶点索引是否实例化的, 若是0, 则元素会对每个实例进行重复. uint16 bUseInstanceIndex; FVertexElement(); FVertexElement(uint8 InStreamIndex, ...); void operator=(const FVertexElement& Other); friend FArchive& operator<<(FArchive& Ar,FVertexElement& Element); FString ToString() const; void FromString(const FString& Src); void FromString(const FStringView& Src); }; // 顶点声明元素列表的类型. typedef TArray > FVertexDeclarationElementList; // Engine\Source\Runtime\RHI\Public\RHIResources.h // 顶点声明的RHI资源 class FRHIVertexDeclaration : public FRHIResource { public: virtual bool GetInitializer(FVertexDeclarationElementList& Init) { return false; } }; // 顶点缓冲区 class FRHIVertexBuffer : public FRHIResource { public: FRHIVertexBuffer(uint32 InSize,uint32 InUsage); uint32 GetSize() const; uint32 GetUsage() const; protected: FRHIVertexBuffer(); void Swap(FRHIVertexBuffer& Other); void ReleaseUnderlyingResource(); private: // 尺寸. uint32 Size; // 缓冲区标记, 如BUF_UnorderedAccess uint32 Usage; }; // Engine\Source\Runtime\RenderCore\Public\VertexFactory.h // 顶点输入流. struct FVertexInputStream { // 顶点流索引 uint32 StreamIndex : 4; // 在VertexBuffer的偏移. uint32 Offset : 28; // 顶点缓存区 FRHIVertexBuffer* VertexBuffer; FVertexInputStream(); FVertexInputStream(uint32 InStreamIndex, uint32 InOffset, FRHIVertexBuffer* InVertexBuffer); inline bool operator==(const FVertexInputStream& rhs) const; inline bool operator!=(const FVertexInputStream& rhs) const; }; // 顶点输入流数组. typedef TArray> FVertexInputStreamArray; // 顶点流标记 enum class EVertexStreamUsage : uint8 { Default = 0 << 0, // 默认 Instancing = 1 << 0, // 实例化 Overridden = 1 << 1, // 覆盖 ManualFetch = 1 << 2 // 手动获取 }; // 顶点输入流类型. enum class EVertexInputStreamType : uint8 { Default = 0, // 默认 PositionOnly, // 只有位置 PositionAndNormalOnly // 只有位置和法线 }; // 顶点流组件. struct FVertexStreamComponent { // 流数据的顶点缓冲区, 如果为null, 则不会有数据从此顶点流被读取. const FVertexBuffer* VertexBuffer = nullptr; // vertex buffer的偏移. uint32 StreamOffset = 0; // 数据的偏移, 相对于顶点缓冲区中每个元素的开头. uint8 Offset = 0; // 数据的步长. uint8 Stride = 0; // 从流读取的数据类型. TEnumAsByte Type = VET_None; // 顶点流标记. EVertexStreamUsage VertexStreamUsage = EVertexStreamUsage::Default; (......) }; // 着色器使用的顶点工厂的参数绑定接口. class FVertexFactoryShaderParameters { public: // 绑定参数到ParameterMap. 具体逻辑由子类完成. void Bind(const class FShaderParameterMap& ParameterMap) {} // 获取顶点工厂的着色器绑定和顶点流. 具体逻辑由子类完成. void GetElementShaderBindings( const class FSceneInterface* Scene, const class FSceneView* View, const class FMeshMaterialShader* Shader, const EVertexInputStreamType InputStreamType, ERHIFeatureLevel::Type FeatureLevel, const class FVertexFactory* VertexFactory, const struct FMeshBatchElement& BatchElement, class FMeshDrawSingleShaderBindings& ShaderBindings, FVertexInputStreamArray& VertexStreams) const {} (......) }; // 用来表示顶点工厂类型的类. class FVertexFactoryType { public: // 类型定义 typedef FVertexFactoryShaderParameters* (*ConstructParametersType)(EShaderFrequency ShaderFrequency, const class FShaderParameterMap& ParameterMap); typedef const FTypeLayoutDesc* (*GetParameterTypeLayoutType)(EShaderFrequency ShaderFrequency); (......) // 获取顶点工厂类型数量. static int32 GetNumVertexFactoryTypes(); // 获取全局的着色器工厂列表. static RENDERCORE_API TLinkedList*& GetTypeList(); // 获取已存的材质类型列表. static RENDERCORE_API const TArray& GetSortedMaterialTypes(); // 通过名字查找FVertexFactoryType static RENDERCORE_API FVertexFactoryType* GetVFByName(const FHashedName& VFName); // 初始化FVertexFactoryType静态成员, 必须在VF类型创建之前调用. static void Initialize(const TMap >& ShaderFileToUniformBufferVariables); static void Uninitialize(); // 构造/析构函数. RENDERCORE_API FVertexFactoryType(...); virtual ~FVertexFactoryType(); // 数据获取接口. const TCHAR* GetName() const; FName GetFName() const; const FHashedName& GetHashedName() const; const TCHAR* GetShaderFilename() const; // 着色器参数接口. FVertexFactoryShaderParameters* CreateShaderParameters(...) const; const FTypeLayoutDesc* GetShaderParameterLayout(...) const; void GetShaderParameterElementShaderBindings(...) const; // 标记访问. bool IsUsedWithMaterials() const; bool SupportsStaticLighting() const; bool SupportsDynamicLighting() const; bool SupportsPrecisePrevWorldPos() const; bool SupportsPositionOnly() const; bool SupportsCachingMeshDrawCommands() const; bool SupportsPrimitiveIdStream() const; // 获取哈希. friend uint32 GetTypeHash(const FVertexFactoryType* Type); // 基于顶点工厂类型的源码和包含计算出来的哈希. const FSHAHash& GetSourceHash(EShaderPlatform ShaderPlatform) const; // 是否需要缓存材质的着色器类型. bool ShouldCache(const FVertexFactoryShaderPermutationParameters& Parameters) const; void ModifyCompilationEnvironment(...); void ValidateCompiledResult(EShaderPlatform Platform, ...); bool SupportsTessellationShaders() const; // 增加引用的Uniform Buffer包含. void AddReferencedUniformBufferIncludes(...); void FlushShaderFileCache(...); const TMap& GetReferencedUniformBufferStructsCache() const; private: static uint32 NumVertexFactories; static bool bInitializedSerializationHistory; // 顶点工厂类型的各类数据和标记. const TCHAR* Name; const TCHAR* ShaderFilename; FName TypeName; FHashedName HashedName; uint32 bUsedWithMaterials : 1; uint32 bSupportsStaticLighting : 1; uint32 bSupportsDynamicLighting : 1; uint32 bSupportsPrecisePrevWorldPos : 1; uint32 bSupportsPositionOnly : 1; uint32 bSupportsCachingMeshDrawCommands : 1; uint32 bSupportsPrimitiveIdStream : 1; ConstructParametersType ConstructParameters; GetParameterTypeLayoutType GetParameterTypeLayout; GetParameterTypeElementShaderBindingsType GetParameterTypeElementShaderBindings; ShouldCacheType ShouldCacheRef; ModifyCompilationEnvironmentType ModifyCompilationEnvironmentRef; ValidateCompiledResultType ValidateCompiledResultRef; SupportsTessellationShadersType SupportsTessellationShadersRef; // 全局顶点工厂类型列表. TLinkedList GlobalListLink; // 缓存引用的Uniform Buffer的包含. TMap ReferencedUniformBufferStructsCache; // 跟踪ReferencedUniformBufferStructsCache缓存了哪些平台的声明. bool bCachedUniformBufferStructDeclarations; }; // ------顶点工厂的工具宏------ // 实现顶点工厂参数类型 #define IMPLEMENT_VERTEX_FACTORY_PARAMETER_TYPE(FactoryClass, ShaderFrequency, ParameterClass) // 顶点工厂类型的声明 #define DECLARE_VERTEX_FACTORY_TYPE(FactoryClass) // 顶点工厂类型的实现 #define IMPLEMENT_VERTEX_FACTORY_TYPE(FactoryClass,ShaderFilename,bUsedWithMaterials,bSupportsStaticLighting,bSupportsDynamicLighting,bPrecisePrevWorldPos,bSupportsPositionOnly) // 顶点工厂的虚函数表实现 #define IMPLEMENT_VERTEX_FACTORY_VTABLE(FactoryClass // 顶点工厂 class FVertexFactory : public FRenderResource { public: FVertexFactory(ERHIFeatureLevel::Type InFeatureLevel); virtual FVertexFactoryType* GetType() const; // 获取顶点数据流. void GetStreams(ERHIFeatureLevel::Type InFeatureLevel, EVertexInputStreamType VertexStreamType, FVertexInputStreamArray& OutVertexStreams) const { // Default顶点流类型 if (VertexStreamType == EVertexInputStreamType::Default) { bool bSupportsVertexFetch = SupportsManualVertexFetch(InFeatureLevel); // 将顶点工厂的数据构造到FVertexInputStream中并添加到输出列表 for (int32 StreamIndex = 0;StreamIndex < Streams.Num();StreamIndex++) { const FVertexStream& Stream = Streams[StreamIndex]; if (!(EnumHasAnyFlags(EVertexStreamUsage::ManualFetch, Stream.VertexStreamUsage) && bSupportsVertexFetch)) { if (!Stream.VertexBuffer) { OutVertexStreams.Add(FVertexInputStream(StreamIndex, 0, nullptr)); } else { if (EnumHasAnyFlags(EVertexStreamUsage::Overridden, Stream.VertexStreamUsage) && !Stream.VertexBuffer->IsInitialized()) { OutVertexStreams.Add(FVertexInputStream(StreamIndex, 0, nullptr)); } else { OutVertexStreams.Add(FVertexInputStream(StreamIndex, Stream.Offset, Stream.VertexBuffer->VertexBufferRHI)); } } } } } // 只有位置和的顶点流类型 else if (VertexStreamType == EVertexInputStreamType::PositionOnly) { // Set the predefined vertex streams. for (int32 StreamIndex = 0; StreamIndex < PositionStream.Num(); StreamIndex++) { const FVertexStream& Stream = PositionStream[StreamIndex]; OutVertexStreams.Add(FVertexInputStream(StreamIndex, Stream.Offset, Stream.VertexBuffer->VertexBufferRHI)); } } // 只有位置和法线的顶点流类型 else if (VertexStreamType == EVertexInputStreamType::PositionAndNormalOnly) { // Set the predefined vertex streams. for (int32 StreamIndex = 0; StreamIndex < PositionAndNormalStream.Num(); StreamIndex++) { const FVertexStream& Stream = PositionAndNormalStream[StreamIndex]; OutVertexStreams.Add(FVertexInputStream(StreamIndex, Stream.Offset, Stream.VertexBuffer->VertexBufferRHI)); } } else { // NOT_IMPLEMENTED } } // 偏移实例的数据流. void OffsetInstanceStreams(uint32 InstanceOffset, EVertexInputStreamType VertexStreamType, FVertexInputStreamArray& VertexStreams) const; static void ModifyCompilationEnvironment(...); static void ValidateCompiledResult(...); static bool SupportsTessellationShaders(); // FRenderResource接口, 释放RHI资源. virtual void ReleaseRHI(); // 设置/获取顶点声明的RHI引用. FVertexDeclarationRHIRef& GetDeclaration(); void SetDeclaration(FVertexDeclarationRHIRef& NewDeclaration); // 根据类型获取顶点声明的RHI引用. const FVertexDeclarationRHIRef& GetDeclaration(EVertexInputStreamType InputStreamType) const { switch (InputStreamType) { case EVertexInputStreamType::Default: return Declaration; case EVertexInputStreamType::PositionOnly: return PositionDeclaration; case EVertexInputStreamType::PositionAndNormalOnly: return PositionAndNormalDeclaration; } return Declaration; } // 各类标记. virtual bool IsGPUSkinned() const; virtual bool SupportsPositionOnlyStream() const; virtual bool SupportsPositionAndNormalOnlyStream() const; virtual bool SupportsNullPixelShader() const; // 用面向摄像机精灵的方式渲染图元. virtual bool RendersPrimitivesAsCameraFacingSprites() const; // 是否需要顶点声明. bool NeedsDeclaration() const; // 是否支持手动的顶点获取. inline bool SupportsManualVertexFetch(const FStaticFeatureLevel InFeatureLevel) const; // 根据流类型获取索引. inline int32 GetPrimitiveIdStreamIndex(EVertexInputStreamType InputStreamType) const; protected: inline void SetPrimitiveIdStreamIndex(EVertexInputStreamType InputStreamType, int32 StreamIndex) { PrimitiveIdStreamIndex[static_cast(InputStreamType)] = StreamIndex; } // 为顶点流组件创建顶点元素. FVertexElement AccessStreamComponent(const FVertexStreamComponent& Component,uint8 AttributeIndex); FVertexElement AccessStreamComponent(const FVertexStreamComponent& Component, uint8 AttributeIndex, EVertexInputStreamType InputStreamType); // 初始化顶点声明. void InitDeclaration(const FVertexDeclarationElementList& Elements, EVertexInputStreamType StreamType = EVertexInputStreamType::Default) { if (StreamType == EVertexInputStreamType::PositionOnly) { PositionDeclaration = PipelineStateCache::GetOrCreateVertexDeclaration(Elements); } else if (StreamType == EVertexInputStreamType::PositionAndNormalOnly) { PositionAndNormalDeclaration = PipelineStateCache::GetOrCreateVertexDeclaration(Elements); } else // (StreamType == EVertexInputStreamType::Default) { // Create the vertex declaration for rendering the factory normally. Declaration = PipelineStateCache::GetOrCreateVertexDeclaration(Elements); } } // 顶点流, 需要设置到顶点流的信息体. struct FVertexStream { const FVertexBuffer* VertexBuffer = nullptr; uint32 Offset = 0; uint16 Stride = 0; EVertexStreamUsage VertexStreamUsage = EVertexStreamUsage::Default; uint8 Padding = 0; friend bool operator==(const FVertexStream& A,const FVertexStream& B); FVertexStream(); }; // 用于渲染顶点工厂的顶点流. TArray > Streams; // VF(顶点工厂)可以显式地将此设置为false,以避免在没有声明的情况下出现错误. 主要用于需要直接从缓冲区获取数据的VF(如Niagara). bool bNeedsDeclaration = true; bool bSupportsManualVertexFetch = false; int8 PrimitiveIdStreamIndex[3] = { -1, -1, -1 }; private: // 只有位置的顶点流, 用于渲染深度Pass的顶点工厂. TArray > PositionStream; // 只有位置和法线的顶点流. TArray > PositionAndNormalStream; // 用于常规渲染顶点工厂的RHI顶点声明. FVertexDeclarationRHIRef Declaration; // PositionStream和PositionAndNormalStream对应的RHI资源. FVertexDeclarationRHIRef PositionDeclaration; FVertexDeclarationRHIRef PositionAndNormalDeclaration; }; ``` 上面展示了Vertex Factory的很多类型,有好几个是核心类,比如FVertexFactory、FVertexElement、FRHIVertexDeclaration、FRHIVertexBuffer、FVertexFactoryType、FVertexStreamComponent、FVertexInputStream、FVertexFactoryShaderParameters等。那么它们之间的关系是什么呢? 为了更好地说明它们之间的关系,以静态模型的FStaticMeshDataType为例: ![[UE_VertexFactory_FStaticMeshDataType.jpg]] FStaticMeshDataType会包含若干个FVertexStreamComponent实例,每个FVertexStreamComponent包含了一个在**FVertexDeclarationElementList**的**FVertexElement实例索引**和一个在**FVertexInputStreamArray**列表的**FVertexStream实例索引**。 此外,FVertexFactory是个基类,内置的子类主要有: - FGeometryCacheVertexVertexFactory:几何缓存顶点的顶点工厂,常用于预生成的布料、动作等网格类型。 - FGPUBaseSkinVertexFactory:GPU蒙皮骨骼网格的父类,它的子类有: - TGPUSkinVertexFactory:可指定骨骼权重方式的GPU蒙皮的顶点工厂。 - FLocalVertexFactory:局部顶点工厂,常用于**静态网格**,它拥有**数量较多的子类**: - FInstancedStaticMeshVertexFactory:实例化的静态网格顶点工厂。 - FSplineMeshVertexFactory:样条曲线网格顶点工厂。 - FGeometryCollectionVertexFactory:几何收集顶点工厂。 - FGPUSkinPassthroughVertexFactory:启用了Skin Cache模式的蒙皮骨骼顶点工厂。 - FSingleTriangleMeshVertexFactory:单个三角形网格的顶点工厂,用于体积云渲染。 - ...... - FParticleVertexFactoryBase:用于粒子渲染的顶点工厂基类。 - FLandscapeVertexFactory:用于渲染地形的顶点工厂。 除了以上继承自FVertexFactory,还有一些不是继承自FVertexFactory的类型,如: - FGPUBaseSkinAPEXClothVertexFactory:布料顶点工厂。 - TGPUSkinAPEXClothVertexFactory:可带骨骼权重模式的布料顶点工厂。 除了FVertexFactory,相应的其它核心类也有继承体系。比如FVertexFactoryShaderParameters的子类有: - FGeometryCacheVertexFactoryShaderParameters - FGPUSkinVertexFactoryShaderParameters - FMeshParticleVertexFactoryShaderParameters - FParticleSpriteVertexFactoryShaderParameters - FGPUSpriteVertexFactoryShaderParametersVS - FGPUSpriteVertexFactoryShaderParametersPS - FSplineMeshVertexFactoryShaderParameters - FLocalVertexFactoryShaderParametersBase - FLandscapeVertexFactoryVertexShaderParameters - FLandscapeVertexFactoryPixelShaderParameters - ...... 另外,有部分顶点工厂还会在内部派生FStaticMeshDataType的类型,以复用静态网格相关的数据成员。为了更好地说明顶点工厂的使用方式,下面就以最常见的FLocalVertexFactory和使用了FLocalVertexFactory的CableComponent为例: ```c++ class ENGINE_API FLocalVertexFactory : public FVertexFactory { public: FLocalVertexFactory(ERHIFeatureLevel::Type InFeatureLevel, const char* InDebugName); // 派生自FStaticMeshDataType的数据类型. struct FDataType : public FStaticMeshDataType { FRHIShaderResourceView* PreSkinPositionComponentSRV = nullptr; }; // 环境变量更改和校验. static bool ShouldCompilePermutation(const FVertexFactoryShaderPermutationParameters& Parameters); static void ModifyCompilationEnvironment(const FVertexFactoryShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment); static void ValidateCompiledResult(const FVertexFactoryType* Type, EShaderPlatform Platform, const FShaderParameterMap& ParameterMap, TArray& OutErrors); // 由TSynchronizedResource从游戏线程更新而来的数据. void SetData(const FDataType& InData); // 从其它顶点工厂复制数据. void Copy(const FLocalVertexFactory& Other); // FRenderResource接口. virtual void InitRHI() override; virtual void ReleaseRHI() override { UniformBuffer.SafeRelease(); FVertexFactory::ReleaseRHI(); } // 顶点颜色接口. void SetColorOverrideStream(FRHICommandList& RHICmdList, const FVertexBuffer* ColorVertexBuffer) const; void GetColorOverrideStream(const FVertexBuffer* ColorVertexBuffer, FVertexInputStreamArray& VertexStreams) const; // 着色器参数和其它数据接口. inline FRHIShaderResourceView* GetPositionsSRV() const; inline FRHIShaderResourceView* GetPreSkinPositionSRV() const; inline FRHIShaderResourceView* GetTangentsSRV() const; inline FRHIShaderResourceView* GetTextureCoordinatesSRV() const; inline FRHIShaderResourceView* GetColorComponentsSRV() const; inline const uint32 GetColorIndexMask() const; inline const int GetLightMapCoordinateIndex() const; inline const int GetNumTexcoords() const; FRHIUniformBuffer* GetUniformBuffer() const; (......) protected: // 从游戏线程传入的数据. FDataType是FStaticMeshDataType的子类. FDataType Data; // 局部顶点工厂的着色器参数. TUniformBufferRef UniformBuffer; // 顶点颜色流索引. int32 ColorStreamIndex; (......) }; // Engine\Source\Runtime\Engine\Public\LocalVertexFactory.cpp void FLocalVertexFactory::InitRHI() { // 是否使用gpu场景. const bool bCanUseGPUScene = UseGPUScene(GMaxRHIShaderPlatform, GMaxRHIFeatureLevel); // 初始化位置流和位置声明. if (Data.PositionComponent.VertexBuffer != Data.TangentBasisComponents[0].VertexBuffer) { // 增加顶点声明. auto AddDeclaration = [this, bCanUseGPUScene](EVertexInputStreamType InputStreamType, bool bAddNormal) { // 顶点流元素. FVertexDeclarationElementList StreamElements; StreamElements.Add(AccessStreamComponent(Data.PositionComponent, 0, InputStreamType)); bAddNormal = bAddNormal && Data.TangentBasisComponents[1].VertexBuffer != NULL; if (bAddNormal) { StreamElements.Add(AccessStreamComponent(Data.TangentBasisComponents[1], 2, InputStreamType)); } const uint8 TypeIndex = static_cast(InputStreamType); PrimitiveIdStreamIndex[TypeIndex] = -1; if (GetType()->SupportsPrimitiveIdStream() && bCanUseGPUScene) { // When the VF is used for rendering in normal mesh passes, this vertex buffer and offset will be overridden StreamElements.Add(AccessStreamComponent(FVertexStreamComponent(&GPrimitiveIdDummy, 0, 0, sizeof(uint32), VET_UInt, EVertexStreamUsage::Instancing), 1, InputStreamType)); PrimitiveIdStreamIndex[TypeIndex] = StreamElements.Last().StreamIndex; } // 初始化声明. InitDeclaration(StreamElements, InputStreamType); }; // 增加PositionOnly和PositionAndNormalOnly两种顶点声明, 其中前者不需要法线. AddDeclaration(EVertexInputStreamType::PositionOnly, false); AddDeclaration(EVertexInputStreamType::PositionAndNormalOnly, true); } // 顶点声明元素列表. FVertexDeclarationElementList Elements; // 顶点位置 if(Data.PositionComponent.VertexBuffer != NULL) { Elements.Add(AccessStreamComponent(Data.PositionComponent,0)); } // 图元id { const uint8 Index = static_cast(EVertexInputStreamType::Default); PrimitiveIdStreamIndex[Index] = -1; if (GetType()->SupportsPrimitiveIdStream() && bCanUseGPUScene) { // When the VF is used for rendering in normal mesh passes, this vertex buffer and offset will be overridden Elements.Add(AccessStreamComponent(FVertexStreamComponent(&GPrimitiveIdDummy, 0, 0, sizeof(uint32), VET_UInt, EVertexStreamUsage::Instancing), 13)); PrimitiveIdStreamIndex[Index] = Elements.Last().StreamIndex; } } // 切线和法线, 切线法线才需要被顶点流使用, 副法线由shader生成. uint8 TangentBasisAttributes[2] = { 1, 2 }; for(int32 AxisIndex = 0;AxisIndex < 2;AxisIndex++) { if(Data.TangentBasisComponents[AxisIndex].VertexBuffer != NULL) { Elements.Add(AccessStreamComponent(Data.TangentBasisComponents[AxisIndex],TangentBasisAttributes[AxisIndex])); } } if (Data.ColorComponentsSRV == nullptr) { Data.ColorComponentsSRV = GNullColorVertexBuffer.VertexBufferSRV; Data.ColorIndexMask = 0; } // 顶点颜色 ColorStreamIndex = -1; if(Data.ColorComponent.VertexBuffer) { Elements.Add(AccessStreamComponent(Data.ColorComponent,3)); ColorStreamIndex = Elements.Last().StreamIndex; } else { FVertexStreamComponent NullColorComponent(&GNullColorVertexBuffer, 0, 0, VET_Color, EVertexStreamUsage::ManualFetch); Elements.Add(AccessStreamComponent(NullColorComponent, 3)); ColorStreamIndex = Elements.Last().StreamIndex; } // 纹理坐标 if(Data.TextureCoordinates.Num()) { const int32 BaseTexCoordAttribute = 4; for(int32 CoordinateIndex = 0;CoordinateIndex < Data.TextureCoordinates.Num();CoordinateIndex++) { Elements.Add(AccessStreamComponent( Data.TextureCoordinates[CoordinateIndex], BaseTexCoordAttribute + CoordinateIndex )); } for (int32 CoordinateIndex = Data.TextureCoordinates.Num(); CoordinateIndex < MAX_STATIC_TEXCOORDS / 2; CoordinateIndex++) { Elements.Add(AccessStreamComponent( Data.TextureCoordinates[Data.TextureCoordinates.Num() - 1], BaseTexCoordAttribute + CoordinateIndex )); } } // 光照图 if(Data.LightMapCoordinateComponent.VertexBuffer) { Elements.Add(AccessStreamComponent(Data.LightMapCoordinateComponent,15)); } else if(Data.TextureCoordinates.Num()) { Elements.Add(AccessStreamComponent(Data.TextureCoordinates[0],15)); } // 初始化顶点声明 InitDeclaration(Elements); const int32 DefaultBaseVertexIndex = 0; const int32 DefaultPreSkinBaseVertexIndex = 0; if (RHISupportsManualVertexFetch(GMaxRHIShaderPlatform) || bCanUseGPUScene) { SCOPED_LOADTIMER(FLocalVertexFactory_InitRHI_CreateLocalVFUniformBuffer); UniformBuffer = CreateLocalVFUniformBuffer(this, Data.LODLightmapDataIndex, nullptr, DefaultBaseVertexIndex, DefaultPreSkinBaseVertexIndex); } } // 实现FLocalVertexFactory的参数类型. IMPLEMENT_VERTEX_FACTORY_PARAMETER_TYPE(FLocalVertexFactory, SF_Vertex, FLocalVertexFactoryShaderParameters); // 实现FLocalVertexFactory. IMPLEMENT_VERTEX_FACTORY_TYPE_EX(FLocalVertexFactory,"/Engine/Private/LocalVertexFactory.ush",true,true,true,true,true,true,true); ``` 下面进入CableComponent相关类型关于FLocalVertexFactory的使用: ```c++ // Engine\Plugins\Runtime\CableComponent\Source\CableComponent\Private\CableComponent.cpp class FCableSceneProxy final : public FPrimitiveSceneProxy { public: FCableSceneProxy(UCableComponent* Component) : FPrimitiveSceneProxy(Component) , Material(NULL) // 构造顶点工厂. , VertexFactory(GetScene().GetFeatureLevel(), "FCableSceneProxy") (......) { // 利用顶点工厂初始化缓冲区. VertexBuffers.InitWithDummyData(&VertexFactory, GetRequiredVertexCount()); (......) } virtual ~FCableSceneProxy() { // 释放顶点工厂. VertexFactory.ReleaseResource(); (......) } // 构建Cable网格. void BuildCableMesh(const TArray& InPoints, TArray& OutVertices, TArray& OutIndices) { (......) } // 设置动态数据(渲染线程调用) void SetDynamicData_RenderThread(FCableDynamicData* NewDynamicData) { // 释放旧数据. if(DynamicData) { delete DynamicData; DynamicData = NULL; } DynamicData = NewDynamicData; // 从Cable点构建顶点. TArray Vertices; TArray Indices; BuildCableMesh(NewDynamicData->CablePoints, Vertices, Indices); // 填充顶点缓冲区数据. for (int i = 0; i < Vertices.Num(); i++) { const FDynamicMeshVertex& Vertex = Vertices[i]; VertexBuffers.PositionVertexBuffer.VertexPosition(i) = Vertex.Position; VertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(i, Vertex.TangentX.ToFVector(), Vertex.GetTangentY(), Vertex.TangentZ.ToFVector()); VertexBuffers.StaticMeshVertexBuffer.SetVertexUV(i, 0, Vertex.TextureCoordinate[0]); VertexBuffers.ColorVertexBuffer.VertexColor(i) = Vertex.Color; } // 更新顶点缓冲区数据到RHI. { auto& VertexBuffer = VertexBuffers.PositionVertexBuffer; void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly); FMemory::Memcpy(VertexBufferData, VertexBuffer.GetVertexData(), VertexBuffer.GetNumVertices() * VertexBuffer.GetStride()); RHIUnlockVertexBuffer(VertexBuffer.VertexBufferRHI); } (......) } virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override { (......) for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { if (VisibilityMap & (1 << ViewIndex)) { const FSceneView* View = Views[ViewIndex]; // 构造FMeshBatch实例. FMeshBatch& Mesh = Collector.AllocateMesh(); // 将顶点工厂实例传给FMeshBatch实例. Mesh.VertexFactory = &VertexFactory; (......) Collector.AddMesh(ViewIndex, Mesh); } } } (......) private: // 材质 UMaterialInterface* Material; // 顶点和索引缓冲. FStaticMeshVertexBuffers VertexBuffers; FCableIndexBuffer IndexBuffer; // 顶点工厂. FLocalVertexFactory VertexFactory; // 动态数据. FCableDynamicData* DynamicData; (......) }; ``` 主要步骤: 1. 在构造函数中初始化LocalVertexFactory成员变量。 2. SetDynamicData_RenderThread() 1. 通过控制点构建CableMesh顶点数据。 2. 填充顶点缓冲区数据。FStaticMeshVertexBuffers VertexBuffers 1. PositionVertexBuffer.VertexPosition 2. StaticMeshVertexBuffer.SetVertexTangents 3. StaticMeshVertexBuffer.SetVertexUV 4. ColorVertexBuffer.VertexColor 3. 更新IndexBufferData到RHI。 3. GetDynamicMeshElements():将顶点工厂实例传给FMeshBatch实例 1. FMeshBatch& Mesh = Collector.AllocateMesh(); 2. Mesh.VertexFactory = &VertexFactory; 3. Collector.AddMesh(ViewIndex, Mesh); 不过,无论是使用已有的还是自定义的顶点工厂,顶点工厂的顶点声明的顺序、类型、组件数量和插槽需要和HLSL层的FVertexFactoryInput保持一致。比如说FLocalVertexFactory::InitRHI的顶点声明顺序是位置、切线、颜色、纹理坐标、光照图,那么我们进入FLocalVertexFactory对应的HLSL文件(由IMPLEMENT_VERTEX_FACTORY_TYPE等宏指定)看看: ```c++ // Engine\Shaders\Private\LocalVertexFactory.ush // 局部顶点工厂对应的输入结构体. struct FVertexFactoryInput { // 位置 float4 Position : ATTRIBUTE0; // 切线和颜色 #if !MANUAL_VERTEX_FETCH #if METAL_PROFILE float3 TangentX : ATTRIBUTE1; // TangentZ.w contains sign of tangent basis determinant float4 TangentZ : ATTRIBUTE2; float4 Color : ATTRIBUTE3; #else half3 TangentX : ATTRIBUTE1; // TangentZ.w contains sign of tangent basis determinant half4 TangentZ : ATTRIBUTE2; half4 Color : ATTRIBUTE3; #endif #endif // 纹理坐标 #if NUM_MATERIAL_TEXCOORDS_VERTEX #if !MANUAL_VERTEX_FETCH #if GPUSKIN_PASS_THROUGH // These must match GPUSkinVertexFactory.usf float2 TexCoords[NUM_MATERIAL_TEXCOORDS_VERTEX] : ATTRIBUTE4; #if NUM_MATERIAL_TEXCOORDS_VERTEX > 4 #error Too many texture coordinate sets defined on GPUSkin vertex input. Max: 4. #endif #else #if NUM_MATERIAL_TEXCOORDS_VERTEX > 1 float4 PackedTexCoords4[NUM_MATERIAL_TEXCOORDS_VERTEX/2] : ATTRIBUTE4; #endif #if NUM_MATERIAL_TEXCOORDS_VERTEX == 1 float2 PackedTexCoords2 : ATTRIBUTE4; #elif NUM_MATERIAL_TEXCOORDS_VERTEX == 3 float2 PackedTexCoords2 : ATTRIBUTE5; #elif NUM_MATERIAL_TEXCOORDS_VERTEX == 5 float2 PackedTexCoords2 : ATTRIBUTE6; #elif NUM_MATERIAL_TEXCOORDS_VERTEX == 7 float2 PackedTexCoords2 : ATTRIBUTE7; #endif #endif #endif #elif USE_PARTICLE_SUBUVS && !MANUAL_VERTEX_FETCH float2 TexCoords[1] : ATTRIBUTE4; #endif (......) }; ``` 因此可知,FVertexFactoryInput结构体的数据顺序和FLocalVertexFactory的顶点声明是一一对应的。 # ShaderMap ShaderMap的作用是**存储编译后的shader代码**,分为FGlobalShaderMap、FMaterialShaderMap、FMeshMaterialShaderMap三种类型。 - FGlobalShaderMap:FGlobalShaderMap保存并管理着所有编译好的FGlobalShader代码。(**没有材质和顶点工程的**) - FMaterialShaderMap:存储和管理着一组FMaterialShader实例的对象。(**额外关联一个材质和一个顶点工厂**) - 因此可以找到,每个FMaterial都有一个FMaterialShaderMap(游戏线程一个,渲染线程一个),如果要获取FMaterial的指定类型的Shader,就需要从该FMaterial的FMaterialShaderMap实例中获取,从而完成了它们之间的链接。 - FMeshMaterialShaderMap:存储和管理FMeshMaterialShader。 ### 编译相关代码 如果需要了解编译过程可以查看`RecompileShaders`命令。 ```c++ // Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp // 引擎预初始化. int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine) { (......) // 是否开启shader编译, 一般情况下都会开启. bool bEnableShaderCompile = !FParse::Param(FCommandLine::Get(), TEXT("NoShaderCompile")); (......) if (bEnableShaderCompile && !IsRunningDedicatedServer() && !bIsCook) { (......) // 编译GlobalShaderMap CompileGlobalShaderMap(false); (......) } (......) } // Engine\Source\Runtime\Engine\Private\ShaderCompiler\ShaderCompiler.cpp void CompileGlobalShaderMap(EShaderPlatform Platform, const ITargetPlatform* TargetPlatform, bool bRefreshShaderMap) { (......) // 如果对应平台的GlobalShaderMap未创建, 则创建之. if (!GGlobalShaderMap[Platform]) { (......) // 创建对应平台的FGlobalShaderMap. GGlobalShaderMap[Platform] = new FGlobalShaderMap(Platform); // Cooked模式. if (FPlatformProperties::RequiresCookedData()) { (......) } // Uncooked模式 else { // FGlobalShaderMap的id. FGlobalShaderMapId ShaderMapId(Platform); const int32 ShaderFilenameNum = ShaderMapId.GetShaderFilenameToDependeciesMap().Num(); const float ProgressStep = 25.0f / ShaderFilenameNum; TArray AsyncDDCRequestHandles; AsyncDDCRequestHandles.SetNum(ShaderFilenameNum); int32 HandleIndex = 0; // 提交DDC请求. for (const auto& ShaderFilenameDependencies : ShaderMapId.GetShaderFilenameToDependeciesMap()) { SlowTask.EnterProgressFrame(ProgressStep); const FString DataKey = GetGlobalShaderMapKeyString(ShaderMapId, Platform, TargetPlatform, ShaderFilenameDependencies.Value); AsyncDDCRequestHandles[HandleIndex] = GetDerivedDataCacheRef().GetAsynchronous(*DataKey, TEXT("GlobalShaderMap"_SV)); ++HandleIndex; } // 处理已经结束的DDC请求. TArray CachedData; HandleIndex = 0; for (const auto& ShaderFilenameDependencies : ShaderMapId.GetShaderFilenameToDependeciesMap()) { SlowTask.EnterProgressFrame(ProgressStep); CachedData.Reset(); GetDerivedDataCacheRef().WaitAsynchronousCompletion(AsyncDDCRequestHandles[HandleIndex]); if (GetDerivedDataCacheRef().GetAsynchronousResults(AsyncDDCRequestHandles[HandleIndex], CachedData)) { FMemoryReader MemoryReader(CachedData); GGlobalShaderMap[Platform]->AddSection(FGlobalShaderMapSection::CreateFromArchive(MemoryReader)); } else { // 没有在DDC中找到, 忽略之. } ++HandleIndex; } } // 如果有shader没有被加载, 编译之. VerifyGlobalShaders(Platform, bLoadedFromCacheFile); // 创建所有着色器. if (GCreateShadersOnLoad && Platform == GMaxRHIShaderPlatform) { GGlobalShaderMap[Platform]->BeginCreateAllShaders(); } } } ``` # Shader调试 修改`Engine\Config\ConsoleVariables.ini`配置 - r.ShaderDevelopmentMode=1 获得关于着色器编译的详细日志和错误重试的机会。 - r.DumpShaderDebugInfo=1 将编译的所有着色器的文件保存到磁盘ProjectName/Saved/ShaderDebugInfo的目录。包含源文件、预处理后的版本、一个批处理文件(用于使用编译器等效的命令行选项来编译预处理版本)。 - r.DumpShaderDebugShortNames=1 保存的Shader路径将被精简。 - r.Shaders.Optimize=0 禁用着色器优化,使得shader的调试信息被保留。 - r.Shaders.KeepDebugInfo=1 保留调试信息,配合RenderDoc等截帧工具时特别有用。 - r.Shaders.SkipCompression=1 忽略shader压缩,可以节省调试shader的时间。 另外,如果修改了Shader的某些文件(如BasePassPixelShader.ush),不需要重启UE编辑器,可以在控制台输入`RecompileShaders`命令重新编译指定的shader文件。其中`RecompileShaders`的具体含义如下: - RecompileShaders all 编译源码有修改的所有shader,包含global、material、meshmaterial。 - RecompileShaders changed 编译源码有修改的shader。 - RecompileShaders global 编译源码有修改的global shader。 - RecompileShaders material 编译源码有修改的material shader。 - RecompileShaders material 编译指定名称的材质。 - RecompileShaders 编译指定路径的shader源文件。 # 案例 新增加FVertexFactory子类的过程如下: ```c++ // FMyVertexFactory.h // 声明顶点工厂着色器参数. BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FMyVertexFactoryParameters, ) SHADER_PARAMETER(FVector4, Color) END_GLOBAL_SHADER_PARAMETER_STRUCT() // 声明类型. typedef TUniformBufferRef FMyVertexFactoryBufferRef; // 索引缓冲. class FMyMeshIndexBuffer : public FIndexBuffer { public: FMyMeshIndexBuffer(int32 InNumQuadsPerSide) : NumQuadsPerSide(InNumQuadsPerSide) {} void InitRHI() override { if (NumQuadsPerSide < 256) { IndexBufferRHI = CreateIndexBuffer(); } else { IndexBufferRHI = CreateIndexBuffer(); } } int32 GetIndexCount() const { return NumIndices; }; private: template FIndexBufferRHIRef CreateIndexBuffer() { TResourceArray Indices; // 分配顶点索引内存. Indices.Reserve(NumQuadsPerSide * NumQuadsPerSide * 6); // 用Morton顺序构建索引缓冲, 以更好地重用顶点. for (int32 Morton = 0; Morton < NumQuadsPerSide * NumQuadsPerSide; Morton++) { int32 SquareX = FMath::ReverseMortonCode2(Morton); int32 SquareY = FMath::ReverseMortonCode2(Morton >> 1); bool ForwardDiagonal = false; if (SquareX % 2) { ForwardDiagonal = !ForwardDiagonal; } if (SquareY % 2) { ForwardDiagonal = !ForwardDiagonal; } int32 Index0 = SquareX + SquareY * (NumQuadsPerSide + 1); int32 Index1 = Index0 + 1; int32 Index2 = Index0 + (NumQuadsPerSide + 1); int32 Index3 = Index2 + 1; Indices.Add(Index3); Indices.Add(Index1); Indices.Add(ForwardDiagonal ? Index2 : Index0); Indices.Add(Index0); Indices.Add(Index2); Indices.Add(ForwardDiagonal ? Index1 : Index3); } NumIndices = Indices.Num(); const uint32 Size = Indices.GetResourceDataSize(); const uint32 Stride = sizeof(IndexType); // Create index buffer. Fill buffer with initial data upon creation FRHIResourceCreateInfo CreateInfo(&Indices); return RHICreateIndexBuffer(Stride, Size, BUF_Static, CreateInfo); } int32 NumIndices = 0; const int32 NumQuadsPerSide = 0; }; // 顶点索引. class FMyMeshVertexBuffer : public FVertexBuffer { public: FMyMeshVertexBuffer(int32 InNumQuadsPerSide) : NumQuadsPerSide(InNumQuadsPerSide) {} virtual void InitRHI() override { const uint32 NumVertsPerSide = NumQuadsPerSide + 1; NumVerts = NumVertsPerSide * NumVertsPerSide; FRHIResourceCreateInfo CreateInfo; void* BufferData = nullptr; VertexBufferRHI = RHICreateAndLockVertexBuffer(sizeof(FVector4) * NumVerts, BUF_Static, CreateInfo, BufferData); FVector4* DummyContents = (FVector4*)BufferData; for (uint32 VertY = 0; VertY < NumVertsPerSide; VertY++) { FVector4 VertPos; VertPos.Y = (float)VertY / NumQuadsPerSide - 0.5f; for (uint32 VertX = 0; VertX < NumVertsPerSide; VertX++) { VertPos.X = (float)VertX / NumQuadsPerSide - 0.5f; DummyContents[NumVertsPerSide * VertY + VertX] = VertPos; } } RHIUnlockVertexBuffer(VertexBufferRHI); } int32 GetVertexCount() const { return NumVerts; } private: int32 NumVerts = 0; const int32 NumQuadsPerSide = 0; }; // 顶点工厂. class FMyVertexFactory : public FVertexFactory { DECLARE_VERTEX_FACTORY_TYPE(FMyVertexFactory); public: using Super = FVertexFactory; FMyVertexFactory(ERHIFeatureLevel::Type InFeatureLevel); ~FMyVertexFactory(); virtual void InitRHI() override; virtual void ReleaseRHI() override; static bool ShouldCompilePermutation(const FVertexFactoryShaderPermutationParameters& Parameters); static void ModifyCompilationEnvironment(const FVertexFactoryShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment); static void ValidateCompiledResult(const FVertexFactoryType* Type, EShaderPlatform Platform, const FShaderParameterMap& ParameterMap, TArray& OutErrors); inline const FUniformBufferRHIRef GetMyVertexFactoryUniformBuffer() const { return UniformBuffer; } private: void SetupUniformData(); FMyMeshVertexBuffer* VertexBuffer = nullptr; FMyMeshIndexBuffer* IndexBuffer = nullptr; FMyVertexFactoryBufferRef UniformBuffer; }; // FMyVertexFactory.cpp #include "ShaderParameterUtils.h" // 实现FMyVertexFactoryParameters, 注意在shader的名字是MyVF. IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FMyVertexFactoryParameters, "MyVF"); // 顶点工厂着色器参数. class FMyVertexFactoryShaderParameters : public FVertexFactoryShaderParameters { DECLARE_TYPE_LAYOUT(FMyVertexFactoryShaderParameters, NonVirtual); public: void Bind(const FShaderParameterMap& ParameterMap) { } void GetElementShaderBindings( const class FSceneInterface* Scene, const class FSceneView* View, const class FMeshMaterialShader* Shader, const EVertexInputStreamType InputStreamType, ERHIFeatureLevel::Type FeatureLevel, const class FVertexFactory* InVertexFactory, const struct FMeshBatchElement& BatchElement, class FMeshDrawSingleShaderBindings& ShaderBindings, FVertexInputStreamArray& VertexStreams) const { // 强制转换成FMyVertexFactory. FMyVertexFactory* VertexFactory = (FMyVertexFactory*)InVertexFactory; // 增加shader帮定到表格. ShaderBindings.Add(Shader->GetUniformBufferParameter(), VertexFactory->GetMyVertexFactoryUniformBuffer()); // 填充顶点流. if (VertexStreams.Num() > 0) { // 处理顶点流索引. for (int32 i = 0; i < 2; ++i) { FVertexInputStream* InstanceInputStream = VertexStreams.FindByPredicate([i](const FVertexInputStream& InStream) { return InStream.StreamIndex == i+1; }); // 绑定顶点流索引. InstanceInputStream->VertexBuffer = InstanceDataBuffers->GetBuffer(i); } // 处理偏移. if (InstanceOffsetValue > 0) { VertexFactory->OffsetInstanceStreams(InstanceOffsetValue, InputStreamType, VertexStreams); } } } }; // ----------- 实现顶点工厂 ----------- FMyVertexFactory::FMyVertexFactory(ERHIFeatureLevel::Type InFeatureLevel) { VertexBuffer = new FMyMeshVertexBuffer(16); IndexBuffer = new FMyMeshIndexBuffer(16); } FMyVertexFactory::~FMyVertexFactory() { delete VertexBuffer; delete IndexBuffer; } void FMyVertexFactory::InitRHI() { Super::InitRHI(); // 设置Uniform数据. SetupUniformData(); VertexBuffer->InitResource(); IndexBuffer->InitResource(); // 顶点流: 位置 FVertexStream PositionVertexStream; PositionVertexStream.VertexBuffer = VertexBuffer; PositionVertexStream.Stride = sizeof(FVector4); PositionVertexStream.Offset = 0; PositionVertexStream.VertexStreamUsage = EVertexStreamUsage::Default; // 简单的实例化顶点流数据 其中VertexBuffer在绑定时设置. FVertexStream InstanceDataVertexStream; InstanceDataVertexStream.VertexBuffer = nullptr; InstanceDataVertexStream.Stride = sizeof(FVector4); InstanceDataVertexStream.Offset = 0; InstanceDataVertexStream.VertexStreamUsage = EVertexStreamUsage::Instancing; FVertexElement VertexPositionElement(Streams.Add(PositionVertexStream), 0, VET_Float4, 0, PositionVertexStream.Stride, false); // 顶点声明. FVertexDeclarationElementList Elements; Elements.Add(VertexPositionElement); // 添加索引顶点流. for (int32 StreamIdx = 0; StreamIdx < NumAdditionalVertexStreams; ++StreamIdx) { FVertexElement InstanceElement(Streams.Add(InstanceDataVertexStream), 0, VET_Float4, 8 + StreamIdx, InstanceDataVertexStream.Stride, true); Elements.Add(InstanceElement); } // 初始化声明. InitDeclaration(Elements); } void FMyVertexFactory::ReleaseRHI() { UniformBuffer.SafeRelease(); if (VertexBuffer) { VertexBuffer->ReleaseResource(); } if (IndexBuffer) { IndexBuffer->ReleaseResource(); } Super::ReleaseRHI(); } void FMyVertexFactory::SetupUniformData() { FMyVertexFactoryParameters UniformParams; UniformParams.Color = FVector4(1,0,0,1); UniformBuffer = FMyVertexFactoryBufferRef::CreateUniformBufferImmediate(UniformParams, UniformBuffer_MultiFrame); } void FMyVertexFactory::ShouldCompilePermutation(const FVertexFactoryShaderPermutationParameters& Parameters) { return true; } void FMyVertexFactory::ModifyCompilationEnvironment(const FVertexFactoryShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { OutEnvironment.SetDefine(TEXT("MY_MESH_FACTORY"), 1); } void FMyVertexFactory::ValidateCompiledResult(const FVertexFactoryType* Type, EShaderPlatform Platform, const FShaderParameterMap& ParameterMap, TArray& OutErrors) { } ``` C++层的逻辑已经完成,但HLSL层也需要编写对应的代码: ```c++ #include "/Engine/Private/VertexFactoryCommon.ush" // VS插值到PS的结构体。 struct FVertexFactoryInterpolantsVSToPS { #if NUM_TEX_COORD_INTERPOLATORS float4 TexCoords[(NUM_TEX_COORD_INTERPOLATORS+1)/2] : TEXCOORD0; #endif #if VF_USE_PRIMITIVE_SCENE_DATA nointerpolation uint PrimitiveId : PRIMITIVE_ID; #endif #if INSTANCED_STEREO nointerpolation uint EyeIndex : PACKED_EYE_INDEX; #endif }; struct FVertexFactoryInput { float4 Position : ATTRIBUTE0; float4 InstanceData0 : ATTRIBUTE8; float4 InstanceData1 : ATTRIBUTE9; #if VF_USE_PRIMITIVE_SCENE_DATA uint PrimitiveId : ATTRIBUTE13; #endif }; struct FPositionOnlyVertexFactoryInput { float4 Position : ATTRIBUTE0; float4 InstanceData0 : ATTRIBUTE8; float4 InstanceData1 : ATTRIBUTE9; #if VF_USE_PRIMITIVE_SCENE_DATA uint PrimitiveId : ATTRIBUTE1; #endif }; struct FPositionAndNormalOnlyVertexFactoryInput { float4 Position : ATTRIBUTE0; float4 Normal : ATTRIBUTE2; float4 InstanceData0 : ATTRIBUTE8; float4 InstanceData1 : ATTRIBUTE9; #if VF_USE_PRIMITIVE_SCENE_DATA uint PrimitiveId : ATTRIBUTE1; #endif }; struct FVertexFactoryIntermediates { float3 OriginalWorldPos; uint PrimitiveId; }; uint GetPrimitiveId(FVertexFactoryInterpolantsVSToPS Interpolants) { #if VF_USE_PRIMITIVE_SCENE_DATA return Interpolants.PrimitiveId; #else return 0; #endif } void SetPrimitiveId(inout FVertexFactoryInterpolantsVSToPS Interpolants, uint PrimitiveId) { #if VF_USE_PRIMITIVE_SCENE_DATA Interpolants.PrimitiveId = PrimitiveId; #endif } #if NUM_TEX_COORD_INTERPOLATORS float2 GetUV(FVertexFactoryInterpolantsVSToPS Interpolants, int UVIndex) { float4 UVVector = Interpolants.TexCoords[UVIndex / 2]; return UVIndex % 2 ? UVVector.zw : UVVector.xy; } void SetUV(inout FVertexFactoryInterpolantsVSToPS Interpolants, int UVIndex, float2 InValue) { FLATTEN if (UVIndex % 2) { Interpolants.TexCoords[UVIndex / 2].zw = InValue; } else { Interpolants.TexCoords[UVIndex / 2].xy = InValue; } } #endif FMaterialPixelParameters GetMaterialPixelParameters(FVertexFactoryInterpolantsVSToPS Interpolants, float4 SvPosition) { // GetMaterialPixelParameters is responsible for fully initializing the result FMaterialPixelParameters Result = MakeInitializedMaterialPixelParameters(); #if NUM_TEX_COORD_INTERPOLATORS UNROLL for (int CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++) { Result.TexCoords[CoordinateIndex] = GetUV(Interpolants, CoordinateIndex); } #endif //NUM_MATERIAL_TEXCOORDS Result.TwoSidedSign = 1; Result.PrimitiveId = GetPrimitiveId(Interpolants); return Result; } FMaterialVertexParameters GetMaterialVertexParameters(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float3 WorldPosition, half3x3 TangentToLocal) { FMaterialVertexParameters Result = (FMaterialVertexParameters)0; Result.WorldPosition = WorldPosition; Result.TangentToWorld = float3x3(1,0,0,0,1,0,0,0,1); Result.PreSkinnedPosition = Input.Position.xyz; Result.PreSkinnedNormal = float3(0,0,1); #if NUM_MATERIAL_TEXCOORDS_VERTEX UNROLL for(int CoordinateIndex = 0; CoordinateIndex < NUM_MATERIAL_TEXCOORDS_VERTEX; CoordinateIndex++) { Result.TexCoords[CoordinateIndex] = Intermediates.MorphedWorldPosRaw.xy; } #endif //NUM_MATERIAL_TEXCOORDS_VERTEX return Result; } FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input) { FVertexFactoryIntermediates Intermediates; // Get the packed instance data float4 Data0 = Input.InstanceData0; float4 Data1 = Input.InstanceData1; const float3 Translation = Data0.xyz; const float3 Scale = float3(Data1.zw, 1.0f); const uint PackedDataChannel = asuint(Data1.x); // Lod level is in first 8 bits and ShouldMorph bit is in the 9th bit const float LODLevel = (float)(PackedDataChannel & 0xFF); const uint ShouldMorph = ((PackedDataChannel >> 8) & 0x1); // Calculate the world pos Intermediates.OriginalWorldPos = float3(Input.Position.xy, 0.0f) * Scale + Translation; #if VF_USE_PRIMITIVE_SCENE_DATA Intermediates.PrimitiveId = Input.PrimitiveId; #else Intermediates.PrimitiveId = 0; #endif return Intermediates; } half3x3 VertexFactoryGetTangentToLocal(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates) { return half3x3(1,0,0,0,1,0,0,0,1); } float4 VertexFactoryGetRasterizedWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float4 InWorldPosition) { return InWorldPosition; } float3 VertexFactoryGetPositionForVertexLighting(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float3 TranslatedWorldPosition) { return TranslatedWorldPosition; } FVertexFactoryInterpolantsVSToPS VertexFactoryGetInterpolantsVSToPS(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, FMaterialVertexParameters VertexParameters) { FVertexFactoryInterpolantsVSToPS Interpolants; Interpolants = (FVertexFactoryInterpolantsVSToPS)0; #if NUM_TEX_COORD_INTERPOLATORS float2 CustomizedUVs[NUM_TEX_COORD_INTERPOLATORS]; GetMaterialCustomizedUVs(VertexParameters, CustomizedUVs); GetCustomInterpolators(VertexParameters, CustomizedUVs); UNROLL for (int CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++) { SetUV(Interpolants, CoordinateIndex, CustomizedUVs[CoordinateIndex]); } #endif #if INSTANCED_STEREO Interpolants.EyeIndex = 0; #endif SetPrimitiveId(Interpolants, Intermediates.PrimitiveId); return Interpolants; } float4 VertexFactoryGetWorldPosition(FPositionOnlyVertexFactoryInput Input) { return Input.Position; } float4 VertexFactoryGetPreviousWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates) { float4x4 PreviousLocalToWorldTranslated = GetPrimitiveData(Intermediates.PrimitiveId).PreviousLocalToWorld; PreviousLocalToWorldTranslated[3][0] += ResolvedView.PrevPreViewTranslation.x; PreviousLocalToWorldTranslated[3][1] += ResolvedView.PrevPreViewTranslation.y; PreviousLocalToWorldTranslated[3][2] += ResolvedView.PrevPreViewTranslation.z; return mul(Input.Position, PreviousLocalToWorldTranslated); } float4 VertexFactoryGetTranslatedPrimitiveVolumeBounds(FVertexFactoryInterpolantsVSToPS Interpolants) { float4 ObjectWorldPositionAndRadius = GetPrimitiveData(GetPrimitiveId(Interpolants)).ObjectWorldPositionAndRadius; return float4(ObjectWorldPositionAndRadius.xyz + ResolvedView.PreViewTranslation.xyz, ObjectWorldPositionAndRadius.w); } uint VertexFactoryGetPrimitiveId(FVertexFactoryInterpolantsVSToPS Interpolants) { return GetPrimitiveId(Interpolants); } float3 VertexFactoryGetWorldNormal(FPositionAndNormalOnlyVertexFactoryInput Input) { return Input.Normal.xyz; } float3 VertexFactoryGetWorldNormal(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates) { return float3(0.0f, 0.0f, 1.0f); } ``` 由此可见,如果新增加了FVertexFactory的自定义类型,需要在HLSL实现以下接口: - FVertexFactoryInput 定义输入到VS的数据布局,需要匹配c++侧的FVertexFactory的类型。 - FVertexFactoryIntermediates 用于存储将在多个顶点工厂函数中使用的缓存中间数据,比如TangentToLocal。 - FVertexFactoryInterpolantsVSToPS 从VS传递到PS的顶点工厂数据。 - VertexFactoryGetWorldPosition 从顶点着色器调用来获得世界空间的顶点位置。 - VertexFactoryGetInterpolantsVSToPS 转换FVertexFactoryInput到FVertexFactoryInterpolants,在硬件光栅化插值之前计算需要插值或传递到PS的数据。 - GetMaterialPixelParameters 由PS调用,根据FVertexFactoryInterpolants计算并填充FMaterialPixelParameters结构体。