1764 lines
67 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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<FString>& OutError) { return true; }
// 取得RayTracingPayloadType
static ERayTracingPayloadType GetRayTracingPayloadType(const int32 PermutationId) { return static_cast<ERayTracingPayloadType>(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<typename UniformBufferStructType>
FORCEINLINE_DEBUGGABLE const TShaderUniformBufferParameter<UniformBufferStructType>& GetUniformBufferParameter() const
{
const FShaderUniformBufferParameter& FoundParameter = GetUniformBufferParameter(UniformBufferStructType::FTypeInfo::GetStructMetadata());
return static_cast<const TShaderUniformBufferParameter<UniformBufferStructType>&>(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<const FHashedName> 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<FString, FParameterAllocation>& ParameterMap);
public:
// 着色器参数绑定.
LAYOUT_FIELD(FShaderParameterBindings, Bindings);
// 着色器参数绑定的映射信息.
LAYOUT_FIELD(FShaderParameterMapInfo, ParameterMapInfo);
protected:
LAYOUT_FIELD(TMemoryImageArray<FHashedName>, UniformBufferParameterStructs);
LAYOUT_FIELD(TMemoryImageArray<FShaderUniformBufferParameter>, UniformBufferParameters);
// 下面3个是编辑器参数.
// 着色器的编译输出和结果参数映射的哈希值, 用于查找匹配的资源.
LAYOUT_FIELD_EDITORONLY(FSHAHash, OutputHash);
// 顶点工厂资源哈希值
LAYOUT_FIELD_EDITORONLY(FSHAHash, VFSourceHash);
// Shader资源哈希值.
LAYOUT_FIELD_EDITORONLY(FSHAHash, SourceHash);
private:
// 着色器类型.
LAYOUT_FIELD(TIndexedPtr<FShaderType>, Type);
// 顶点工厂类型.
LAYOUT_FIELD(TIndexedPtr<FVertexFactoryType>, 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<FRHIUniformBuffer> FUniformBufferRHIRef;
// Engine\Source\Runtime\RenderCore\Public\ShaderParameterMacros.h
// 引用了指定类型的FRHIUniformBuffer的实例资源. 注意是继承了FUniformBufferRHIRef.
template<typename TBufferStruct>
class TUniformBufferRef : public FUniformBufferRHIRef
{
public:
TUniformBufferRef();
// 根据给定的值创建Uniform Buffer, 并返回结构体引用. (模板)
static TUniformBufferRef<TBufferStruct> 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<typename TBufferStruct2>
friend class TUniformBuffer;
friend class TRDGUniformBuffer<TBufferStruct>;
};
```
最后TUniformBuffer和TRDGUniformBuffer引用了FUniformBufferRHIRef。TUniformBuffer为`TUniformBufferRef<TBufferStruct> UniformBufferRHI`成员变量TRDGUniformBuffer为`TRefCountPtr<FRHIUniformBuffer> 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<EVertexElementType> 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<FVertexElement,TFixedAllocator<MaxVertexElementCount> > 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<FVertexInputStream, TInlineAllocator<4>> 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<EVertexElementType> 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<FVertexFactoryType*>*& GetTypeList();
// 获取已存的材质类型列表.
static RENDERCORE_API const TArray<FVertexFactoryType*>& GetSortedMaterialTypes();
// 通过名字查找FVertexFactoryType
static RENDERCORE_API FVertexFactoryType* GetVFByName(const FHashedName& VFName);
// 初始化FVertexFactoryType静态成员, 必须在VF类型创建之前调用.
static void Initialize(const TMap<FString, TArray<const TCHAR*> >& 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<const TCHAR*, FCachedUniformBufferDeclaration>& 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<FVertexFactoryType*> GlobalListLink;
// 缓存引用的Uniform Buffer的包含.
TMap<const TCHAR*, FCachedUniformBufferDeclaration> 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<uint8>(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<FVertexStream,TInlineAllocator<8> > Streams;
// VF(顶点工厂)可以显式地将此设置为false以避免在没有声明的情况下出现错误. 主要用于需要直接从缓冲区获取数据的VF(如Niagara).
bool bNeedsDeclaration = true;
bool bSupportsManualVertexFetch = false;
int8 PrimitiveIdStreamIndex[3] = { -1, -1, -1 };
private:
// 只有位置的顶点流, 用于渲染深度Pass的顶点工厂.
TArray<FVertexStream,TInlineAllocator<2> > PositionStream;
// 只有位置和法线的顶点流.
TArray<FVertexStream, TInlineAllocator<3> > 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几何缓存顶点的顶点工厂常用于预生成的布料、动作等网格类型。
- FGPUBaseSkinVertexFactoryGPU蒙皮骨骼网格的父类它的子类有
- 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<FString>& 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<FLocalVertexFactoryUniformShaderParameters> 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<uint8>(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<uint8>(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<FVector>& InPoints, TArray<FDynamicMeshVertex>& OutVertices, TArray<int32>& OutIndices)
{
(......)
}
// 设置动态数据(渲染线程调用)
void SetDynamicData_RenderThread(FCableDynamicData* NewDynamicData)
{
// 释放旧数据.
if(DynamicData)
{
delete DynamicData;
DynamicData = NULL;
}
DynamicData = NewDynamicData;
// 从Cable点构建顶点.
TArray<FDynamicMeshVertex> Vertices;
TArray<int32> 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<const FSceneView*>& 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三种类型。
- FGlobalShaderMapFGlobalShaderMap保存并管理着所有编译好的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<uint32> 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<uint8> 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<FMyVertexFactoryParameters> FMyVertexFactoryBufferRef;
// 索引缓冲.
class FMyMeshIndexBuffer : public FIndexBuffer
{
public:
FMyMeshIndexBuffer(int32 InNumQuadsPerSide) : NumQuadsPerSide(InNumQuadsPerSide) {}
void InitRHI() override
{
if (NumQuadsPerSide < 256)
{
IndexBufferRHI = CreateIndexBuffer<uint16>();
}
else
{
IndexBufferRHI = CreateIndexBuffer<uint32>();
}
}
int32 GetIndexCount() const { return NumIndices; };
private:
template <typename IndexType>
FIndexBufferRHIRef CreateIndexBuffer()
{
TResourceArray<IndexType, INDEXBUFFER_ALIGNMENT> 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<FString>& 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<FMyVertexFactoryShaderParameters>(), 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<FString>& 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结构体。