虚幻引擎之Rendering Dependency Graph(一)

虚幻引擎之Rendering Dependency Graph(一)

一、前言

这篇文章主要包含了使用RDG的一些内容。

有了这些基础,才有可能对虚幻的渲染管线进行熟练的修改。

之前笔者一直是东一榔头西一棒子地修修改改,没有整理,总感觉会但不完全会,甚是遗憾。

主要包含以下内容:

  • 顶点工厂
  • 着色器
  • RDG和着色器参数

本文基于的引擎代码为4.26版本,管线为延迟渲染管线。

二、顶点工厂(VertexFactory)

虚幻引擎之自定义MeshPass中讨论了FMeshBatch通过FMeshPassProcessor转换为FMeshDrawCommand。

但我们还没讨论过Mesh的顶点数据是如何从C++传入到Shader中的?

要完成这个工作要有两方面:

  1. Shader端声明顶点的数据结构。
  2. C++端根据顶点的数据结构将将数据以对应的格式进行输入。

通常,如果是笔者自己实现的话,可能会想写一个Vertex所可能包含的所有数据的Class,然后进行填充。这样的做法明显是不够好的!

虚幻采用VertexFactory的方式实现,让你可以上传VertexBuffer需要的数据,而非全部的数据!

2.1 FVertexFactory

FVertexFactory类的定义如下所示:

  • 一个VertexFactory包含Vertex的数据源,并且被链接到一个**VertexShader(VS)**上。
/**
 * Encapsulates a vertex data source which can be linked into a vertex shader.
 */
class RENDERCORE_API FVertexFactory : public FRenderResource
{
    // ...省略部分源码
};

为了更了解VertexFactory,应该知道具体的两个例子:

  • FLocalVertexFactory
  • FGPUBasedSkinVertexFactory

FLocalVertexFactory用于很多地方,它的功能是将LocalSpace的顶点属性转换到world space中去。其中,Static Mesh、Cable和Procedural Mesh都使用这个。

FGPUBasedSkinVertexFactory用于Skeletal Mesh。

下面以FLocalVertexFactory进行介绍。

FLocalVertexFactory

FLocalVertexFactory中主要的宏和类数据定义如下:

BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FLocalVertexFactoryUniformShaderParameters,ENGINE_API)
	SHADER_PARAMETER(FIntVector4,VertexFetch_Parameters)
	SHADER_PARAMETER(int32, PreSkinBaseVertexIndex)
	SHADER_PARAMETER(uint32,LODLightmapDataIndex)
	SHADER_PARAMETER_SRV(Buffer<float2>, VertexFetch_TexCoordBuffer)
	SHADER_PARAMETER_SRV(Buffer<float>, VertexFetch_PositionBuffer)
	SHADER_PARAMETER_SRV(Buffer<float>, VertexFetch_PreSkinPositionBuffer)
	SHADER_PARAMETER_SRV(Buffer<float4>, VertexFetch_PackedTangentsBuffer)
	SHADER_PARAMETER_SRV(Buffer<float4>, VertexFetch_ColorComponentsBuffer)
END_GLOBAL_SHADER_PARAMETER_STRUCT()

IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FLocalVertexFactoryUniformShaderParameters, "LocalVF");
    
/**
 * A vertex factory which simply transforms explicit vertex attributes from local to world space.
 */
class ENGINE_API FLocalVertexFactory : public FVertexFactory
{
    // 声明一个顶点工厂类型
	DECLARE_VERTEX_FACTORY_TYPE(FLocalVertexFactory);
public:
	// ... 省略部分源码
protected:
	const FDataType& GetData() const { return Data; }
	FDataType Data;
    // 
	TUniformBufferRef<FLocalVertexFactoryUniformShaderParameters> UniformBuffer;
};

IMPLEMENT_VERTEX_FACTORY_PARAMETER_TYPE(FLocalVertexFactory, SF_Vertex, FLocalVertexFactoryShaderParameters);
// 实现顶点工厂类型
IMPLEMENT_VERTEX_FACTORY_TYPE_EX(FLocalVertexFactory,"/Engine/Private/LocalVertexFactory.ush",true,true,true,true,true,true,true);

DECLARE_VERTEX_FACTORY_TYPEIMPLEMENT_VERTEX_FACTORY_TYPE_EX 宏定义

DECLARE_VERTEX_FACTORY_TYPE宏,用来声明一个新的顶点类型:

其中,用FVertexFactoryType定义了一个全局的静态变量

并声明了一个GetType的接口函数!

/**
 * A macro for declaring a new vertex factory type, for use in the vertex factory class's definition body.
 */
#define DECLARE_VERTEX_FACTORY_TYPE(FactoryClass) \
	public: \
	static FVertexFactoryType StaticType; \
	virtual FVertexFactoryType* GetType() const override;

IMPLEMENT_VERTEX_FACTORY_TYPE_EX宏的定义如下:

  • 又使用了IMPLEMENT_TEMPLATE_VERTEX_FACTORY_TYPE_EX宏
#define IMPLEMENT_VERTEX_FACTORY_TYPE_EX(FactoryClass,ShaderFilename,bUsedWithMaterials,bSupportsStaticLighting,bSupportsDynamicLighting,bPrecisePrevWorldPos,bSupportsPositionOnly,bSupportsCachingMeshDrawCommands,bSupportsPrimitiveIdStream) \
	IMPLEMENT_TEMPLATE_VERTEX_FACTORY_TYPE_EX(,FactoryClass,ShaderFilename,bUsedWithMaterials,bSupportsStaticLighting,bSupportsDynamicLighting,bPrecisePrevWorldPos,bSupportsPositionOnly,bSupportsCachingMeshDrawCommands,bSupportsPrimitiveIdStream)
  • IMPLEMENT_TEMPLATE_VERTEX_FACTORY_TYPE_EX宏定义如下:
#define IMPLEMENT_TEMPLATE_VERTEX_FACTORY_TYPE_EX(TemplatePrefix, FactoryClass,ShaderFilename,bUsedWithMaterials,bSupportsStaticLighting,bSupportsDynamicLighting,bPrecisePrevWorldPos,bSupportsPositionOnly,bSupportsCachingMeshDrawCommands,bSupportsPrimitiveIdStream) \
	PREPROCESSOR_REMOVE_OPTIONAL_PARENS(TemplatePrefix) FVertexFactoryType FactoryClass::StaticType( \
		TEXT(#FactoryClass), \
		TEXT(ShaderFilename), \
		bUsedWithMaterials, \
		bSupportsStaticLighting, \
		bSupportsDynamicLighting, \
		bPrecisePrevWorldPos, \
		bSupportsPositionOnly, \
		bSupportsCachingMeshDrawCommands, \
		bSupportsPrimitiveIdStream, \
		IMPLEMENT_VERTEX_FACTORY_VTABLE(FactoryClass) \
		); \
		PREPROCESSOR_REMOVE_OPTIONAL_PARENS(TemplatePrefix) FVertexFactoryType* FactoryClass::GetType() const { return &StaticType; }

可以看出对这个StaticType变量进行了定义,同时实现了GetType接口。其中,还用到了IMPLEMENT_VERTEX_FACTORY_VTABLE这个宏。

#define IMPLEMENT_VERTEX_FACTORY_VTABLE(FactoryClass) \
	&ConstructVertexFactoryParameters<FactoryClass>, \
	&GetVertexFactoryParametersLayout<FactoryClass>, \
	&GetVertexFactoryParametersElementShaderBindings<FactoryClass>, \
	FactoryClass::ShouldCompilePermutation, \
	FactoryClass::ModifyCompilationEnvironment, \
	FactoryClass::ValidateCompiledResult, \
	FactoryClass::SupportsTessellationShaders

FVertexFactoryType类就是这个宏的核心。

  • 一个代表顶点工厂种类的类
/**
 * An object used to represent the type of a vertex factory.
 */
class FVertexFactoryType
{
	// ...省略其他源码
}

我们重点来看一下他的构造函数,传入的有:

  • 有新顶点工厂的类名(如FLocalVertexFactory)
  • 着色器文件的路径(如"/Engine/Private/LocalVertexFactory.ush")
  • 还有一系列标记。
  • 以及一系列函数指针。
FVertexFactoryType::FVertexFactoryType(
    // 新顶点工厂的类名
	const TCHAR* InName,
    // 着色器文件的路径
	const TCHAR* InShaderFilename,
    // 标记
	bool bInUsedWithMaterials,
	bool bInSupportsStaticLighting,
	bool bInSupportsDynamicLighting,
	bool bInSupportsPrecisePrevWorldPos,
	bool bInSupportsPositionOnly,
	bool bInSupportsCachingMeshDrawCommands,
	bool bInSupportsPrimitiveIdStream,
    // 函数指针
	ConstructParametersType InConstructParameters,
	GetParameterTypeLayoutType InGetParameterTypeLayout,
	GetParameterTypeElementShaderBindingsType InGetParameterTypeElementShaderBindings,
	ShouldCacheType InShouldCache,
	ModifyCompilationEnvironmentType InModifyCompilationEnvironment,
	ValidateCompiledResultType InValidateCompiledResult,
	SupportsTessellationShadersType InSupportsTessellationShaders
	):
	Name(InName),
	ShaderFilename(InShaderFilename),
	TypeName(InName),
	HashedName(TypeName),
	bUsedWithMaterials(bInUsedWithMaterials),
	bSupportsStaticLighting(bInSupportsStaticLighting),
	bSupportsDynamicLighting(bInSupportsDynamicLighting),
	bSupportsPrecisePrevWorldPos(bInSupportsPrecisePrevWorldPos),
	bSupportsPositionOnly(bInSupportsPositionOnly),
	bSupportsCachingMeshDrawCommands(bInSupportsCachingMeshDrawCommands),
	bSupportsPrimitiveIdStream(bInSupportsPrimitiveIdStream),
	ConstructParameters(InConstructParameters),
	GetParameterTypeLayout(InGetParameterTypeLayout),
	GetParameterTypeElementShaderBindings(InGetParameterTypeElementShaderBindings),
	ShouldCacheRef(InShouldCache),
	ModifyCompilationEnvironmentRef(InModifyCompilationEnvironment),
	ValidateCompiledResultRef(InValidateCompiledResult),
	SupportsTessellationShadersRef(InSupportsTessellationShaders),
	GlobalListLink(this)
{
	// Make sure the format of the source file path is right.
    // 检查输入的着色器文件路径是否正确!
	check(CheckVirtualShaderFilePath(InShaderFilename));
    // 检查扩展名是否为ush
	checkf(FPaths::GetExtension(InShaderFilename) == TEXT("ush"),
		TEXT("Incorrect virtual shader path extension for vertex factory shader header '%s': Only .ush files should be included."),
		InShaderFilename);

	bCachedUniformBufferStructDeclarations = false;

	// This will trigger if an IMPLEMENT_VERTEX_FACTORY_TYPE was in a module not loaded before InitializeShaderTypes
	// Vertex factory types need to be implemented in modules that are loaded before that
	checkf(!bInitializedSerializationHistory, TEXT("VF type was loaded after engine init, use ELoadingPhase::PostConfigInit on your module to cause it to load earlier."));

	// Add this vertex factory type to the global list.
    // 将自身加入到全局的顶点工厂列表中
	GlobalListLink.LinkHead(GetTypeList());
    // 将哈希值和顶点工厂类型加入Map中
	GetVFTypeMap().Add(HashedName, this);

	if (bUsedWithMaterials)
	{
		TArray<FVertexFactoryType*>& SortedTypes = GetSortedMaterialVertexFactoryTypes();
		const int32 SortedIndex = Algo::LowerBoundBy(SortedTypes, HashedName, [](const FVertexFactoryType* InType) { return InType->GetHashedName(); });
		SortedTypes.Insert(this, SortedIndex);
	}
	
    // 顶点工厂数量++
    // static RENDERCORE_API uint32 NumVertexFactories;是一个静态变量
	++NumVertexFactories;
}

GetTypeList:返回全局的静态顶点工厂列表对象。

static TLinkedList<FVertexFactoryType*>* GVFTypeList = nullptr;
/**
 * @return The global shader factory list.
 */
TLinkedList<FVertexFactoryType*>*& FVertexFactoryType::GetTypeList()
{
	return GVFTypeList;
}

以上就是DECLARE_VERTEX_FACTORY_TYPE 和 IMPLEMENT_VERTEX_FACTORY_TYPE_EX两个宏的作用。

让我们看一下着色器文件LocalVertexFactory.ush,打开一看1000+多行,各种宏定义。那么这文件到底做了什么?

最核心的,在这个文件中定义了一个FVertexFactoryInput的类:

  • 通过各种宏定义的条件,有不同属性定义情况。
/**
 * Per-vertex inputs from bound vertex buffers
 */
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

#if USE_INSTANCING && !MANUAL_VERTEX_FETCH
	float4 InstanceOrigin : ATTRIBUTE8;  // per-instance random in w 
	half4 InstanceTransform1 : ATTRIBUTE9;  // hitproxy.r + 256 * selected in .w
	half4 InstanceTransform2 : ATTRIBUTE10; // hitproxy.g in .w
	half4 InstanceTransform3 : ATTRIBUTE11; // hitproxy.b in .w
	float4 InstanceLightmapAndShadowMapUVBias : ATTRIBUTE12; 
#endif //USE_INSTANCING

#if VF_USE_PRIMITIVE_SCENE_DATA
	uint PrimitiveId : ATTRIBUTE13;
#endif

#if NEEDS_LIGHTMAP_COORDINATE && !MANUAL_VERTEX_FETCH
	float2	LightMapCoordinate : ATTRIBUTE15;
#endif

#if USE_INSTANCING
	uint InstanceId	: SV_InstanceID;
#endif

#if GPUSKIN_PASS_THROUGH || MANUAL_VERTEX_FETCH
	uint VertexId : SV_VertexID;
#endif
};

当你尝试搜索VertexFactory.ush ,你会发现虚幻中有如此多顶点工厂的着色器文件。

这些顶点工厂的着色器文件同样也都定义了FVertexFactoryInput这个结构体。

在这里插入图片描述

例如,BasePass Vertex Shader将FvertexFactoryInput作为输入。

/** Entry point for the base pass vertex shader. */
void Main(
	FVertexFactoryInput Input,
    // ...省略部分源码
    )

Unreal根据正在使用的VertexFactory类型修改数据结构和函数调用,同时使用一样的名字让公共代码也能工作。

这个部分涉及到Shader的编译组合。

到此,通过这个方式,算大体解决了第一个问题,即Shader端声明顶点的数据结构

2.2 FPrimitiveSceneProxy

Unreal如何知道Mesh使用哪个Vertex Factory?顶点的数据如何上传到GPU?

通过FPrimitiveSceneProxy类!

下面以FCableSceneProxy为例子,进行介绍。

FCableSceneProxy继承自FPrimitiveSceneProxy,先看一下其成员变量!

可以看出其使用了FLocalVertexFactory 类型的顶点工厂!

class FCableSceneProxy final : public FPrimitiveSceneProxy
{
    // ... 省略部分源码
private:
    // 材质
	UMaterialInterface* Material;
    // 顶点数据缓冲
	FStaticMeshVertexBuffers VertexBuffers;
    // 顶点索引缓冲
	FCableIndexBuffer IndexBuffer;
    // 顶点工厂
	FLocalVertexFactory VertexFactory;

	FCableDynamicData* DynamicData;
	FMaterialRelevance MaterialRelevance;
	int32 NumSegments;
	float CableWidth;
	int32 NumSides;
	float TileMaterial;
};

在FCableSceneProxy的构造函数中有:

// 将顶点工厂和顶点缓冲进行了绑定!
VertexBuffers.InitWithDummyData(&VertexFactory, GetRequiredVertexCount());

SetDynamicData_RenderThread 函数中,利用新的数据进行构造Cable,并更新顶点缓冲的数据!

/** Called on render thread to assign new dynamic data */
void SetDynamicData_RenderThread(FCableDynamicData* NewDynamicData)
{
    check(IsInRenderingThread());

    // Free existing data if present
    if(DynamicData)
    {
        delete DynamicData;
        DynamicData = NULL;
    }
    DynamicData = NewDynamicData;

    // Build mesh from cable points
    TArray<FDynamicMeshVertex> Vertices;
    TArray<int32> Indices;
    BuildCableMesh(NewDynamicData->CablePoints, Vertices, Indices);

    check(Vertices.Num() == GetRequiredVertexCount());
    check(Indices.Num() == GetRequiredIndexCount());

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

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

    {
        auto& VertexBuffer = VertexBuffers.ColorVertexBuffer;
        void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly);
        FMemory::Memcpy(VertexBufferData, VertexBuffer.GetVertexData(), VertexBuffer.GetNumVertices() * VertexBuffer.GetStride());
        RHIUnlockVertexBuffer(VertexBuffer.VertexBufferRHI);
    }

    {
        auto& VertexBuffer = VertexBuffers.StaticMeshVertexBuffer;
        void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.TangentsVertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetTangentSize(), RLM_WriteOnly);
        FMemory::Memcpy(VertexBufferData, VertexBuffer.GetTangentData(), VertexBuffer.GetTangentSize());
        RHIUnlockVertexBuffer(VertexBuffer.TangentsVertexBuffer.VertexBufferRHI);
    }

    {
        auto& VertexBuffer = VertexBuffers.StaticMeshVertexBuffer;
        void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.TexCoordVertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetTexCoordSize(), RLM_WriteOnly);
        FMemory::Memcpy(VertexBufferData, VertexBuffer.GetTexCoordData(), VertexBuffer.GetTexCoordSize());
        RHIUnlockVertexBuffer(VertexBuffer.TexCoordVertexBuffer.VertexBufferRHI);
    }

    void* IndexBufferData = RHILockIndexBuffer(IndexBuffer.IndexBufferRHI, 0, Indices.Num() * sizeof(int32), RLM_WriteOnly);
    FMemory::Memcpy(IndexBufferData, &Indices[0], Indices.Num() * sizeof(int32));
    RHIUnlockIndexBuffer(IndexBuffer.IndexBufferRHI);
}

通过FPrimitiveSceneProxy,完成了顶点数据到GPU的上传或更新!

更多细节请读者对源码进行阅读。

三、着色器 (Shader)

UE中所有Shader的基类是FShader。

两个主要Shader类别:GlobalShader(全局着色器)和 MaterialShader(材质着色器)。

其中,FGlobalShader只有一个实例存在。FMaterialShader与Materail绑定。

FShader

  • FShader是Shader在CPU上C++表达。
/** A compiled shader and its parameter bindings. */
class RENDERCORE_API FShader
{
	friend class FShaderType;
	DECLARE_TYPE_LAYOUT(FShader, NonVirtual);
   	// ...省略部分源码
};

FShaderType类:

/**
 * An object which is used to serialize/deserialize, compile, and cache a particular shader class.
 *
 * A shader type can manage multiple instance of FShader across mutiple dimensions such as EShaderPlatform, or permutation id.
 * The number of permutation of a shader type is simply given by GetPermutationCount().
 */
class RENDERCORE_API FShaderType
{
    // ...省略部分源码
};

3.1 FGlobalShader

FGlobalShader直接继承自FShader。

Global Shader只有一个实例,也就是无法为每个实例设置参数。但是可以有全局参数。

/**
 * FGlobalShader
 * 
 * Global shaders derive from this class to set their default recompile group as a global one
 */
class FGlobalShader : public FShader
{
	DECLARE_EXPORTED_TYPE_LAYOUT(FGlobalShader, RENDERCORE_API, NonVirtual);
public:
	using ShaderMetaType = FGlobalShaderType;

	FGlobalShader() : FShader() {}

	RENDERCORE_API FGlobalShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer);
	
	template<typename TViewUniformShaderParameters, typename ShaderRHIParamRef, typename TRHICmdList>
	inline void SetParameters(TRHICmdList& RHICmdList, const ShaderRHIParamRef ShaderRHI, FRHIUniformBuffer* ViewUniformBuffer)
	{
		const auto& ViewUniformBufferParameter = static_cast<const FShaderUniformBufferParameter&>(GetUniformBufferParameter<TViewUniformShaderParameters>());
		SetUniformBufferParameter(RHICmdList, ShaderRHI, ViewUniformBufferParameter, ViewUniformBuffer);
	}
};

举个例子:FDeferredLightPS。

/** A pixel shader for rendering the light in a deferred pass. */
class FDeferredLightPS : public FGlobalShader
{
   	// 声明着色器类型
	DECLARE_SHADER_TYPE(FDeferredLightPS, Global)
   	// ...省略部分源码
}; 

// 
IMPLEMENT_GLOBAL_SHADER(FDeferredLightPS, "/Engine/Private/DeferredLightPixelShaders.usf", "DeferredLightPixelMain", SF_Pixel);

DECLARE_SHADER_TYPE

  • 用于声明一个Shader类型。
#define DECLARE_SHADER_TYPE(ShaderClass,ShaderMetaTypeShortcut,...) \
	DECLARE_EXPORTED_SHADER_TYPE(ShaderClass,ShaderMetaTypeShortcut,, ##__VA_ARGS__)

/**
 * A macro to declare a new shader type.  This should be called in the class body of the new shader type.
 * @param ShaderClass - The name of the class representing an instance of the shader type.
 * @param ShaderMetaTypeShortcut - The shortcut for the shader meta type: simple, material, meshmaterial, etc.  The shader meta type
 *	controls 
 */
#define DECLARE_EXPORTED_SHADER_TYPE(ShaderClass,ShaderMetaTypeShortcut,RequiredAPI, ...) \
	INTERNAL_DECLARE_SHADER_TYPE_COMMON(ShaderClass, ShaderMetaTypeShortcut, RequiredAPI); \
	DECLARE_EXPORTED_TYPE_LAYOUT(ShaderClass, RequiredAPI, NonVirtual); \
	public:

#define INTERNAL_DECLARE_SHADER_TYPE_COMMON(ShaderClass,ShaderMetaTypeShortcut,RequiredAPI) \
	public: \
	using ShaderMetaType = F##ShaderMetaTypeShortcut##ShaderType; \
	using ShaderMapType = F##ShaderMetaTypeShortcut##ShaderMap; \
	\
	static RequiredAPI ShaderMetaType StaticType; \
	\
	SHADER_DECLARE_VTABLE(ShaderClass)

#define SHADER_DECLARE_VTABLE(ShaderClass) \
	static FShader* ConstructSerializedInstance() { return new ShaderClass(); } \
	static FShader* ConstructCompiledInstance(const typename FShader::CompiledShaderInitializerType& Initializer) \
	{ return new ShaderClass(static_cast<const typename ShaderMetaType::CompiledShaderInitializerType&>(Initializer)); }\
	static void ModifyCompilationEnvironmentImpl(const FShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) \
	{ \
		const typename ShaderClass::FPermutationDomain PermutationVector(Parameters.PermutationId); \
		PermutationVector.ModifyCompilationEnvironment(OutEnvironment); \
		ShaderClass::ModifyCompilationEnvironment(static_cast<const typename ShaderClass::FPermutationParameters&>(Parameters), OutEnvironment); \
	} \
	static bool ShouldCompilePermutationImpl(const FShaderPermutationParameters& Parameters) \
	{ return ShaderClass::ShouldCompilePermutation(static_cast<const typename ShaderClass::FPermutationParameters&>(Parameters)); }

和顶点工厂的DECLARE_VERTEX_FACTORY_TYPE 宏类似,其说明了一个全局变量static ShaderMetaType StaticType

ShaderMetaType为:FGlobalShaderType。这是一个已经定义好的Shader类型。

/**
 * A shader meta type for the simplest shaders; shaders which are not material or vertex factory linked.
 * There should only a single instance of each simple shader type.
 */
class FGlobalShaderType : public FShaderType
{
	// ...省略部分源码
};

IMPLEMENT_GLOBAL_SHADER

  • 用于定义(实例化)声明的Shader类型。
#define IMPLEMENT_GLOBAL_SHADER(ShaderClass,SourceFilename,FunctionName,Frequency) IMPLEMENT_SHADER_TYPE(,ShaderClass,TEXT(SourceFilename),TEXT(FunctionName),Frequency)

/** A macro to implement a shader type. */
#define IMPLEMENT_SHADER_TYPE(TemplatePrefix,ShaderClass,SourceFilename,FunctionName,Frequency) \
	IMPLEMENT_UNREGISTERED_TEMPLATE_TYPE_LAYOUT(TemplatePrefix, ShaderClass); \
	TemplatePrefix \
	ShaderClass::ShaderMetaType ShaderClass::StaticType( \
		ShaderClass::StaticGetTypeLayout(), \
		TEXT(#ShaderClass), \
		SourceFilename, \
		FunctionName, \
		Frequency, \
		ShaderClass::FPermutationDomain::PermutationCount, \
		SHADER_TYPE_VTABLE(ShaderClass), \
		sizeof(ShaderClass), \
		ShaderClass::GetRootParametersMetadata() \
		);

主要就是调用了ShaderMetaType的构造函数。在这里即FGlobalShaderType的构造函数!

FGlobalShaderType(
    FTypeLayoutDesc& InTypeLayout,
    const TCHAR* InName,
    const TCHAR* InSourceFilename,
    const TCHAR* InFunctionName,
    uint32 InFrequency,
    int32 InTotalPermutationCount,
    ConstructSerializedType InConstructSerializedRef,
    ConstructCompiledType InConstructCompiledRef,
    ModifyCompilationEnvironmentType InModifyCompilationEnvironmentRef,
    ShouldCompilePermutationType InShouldCompilePermutationRef,
    ValidateCompiledResultType InValidateCompiledResultRef,
    uint32 InTypeSize,
    const FShaderParametersMetadata* InRootParametersMetadata = nullptr
):
FShaderType(EShaderTypeForDynamicCast::Global, InTypeLayout, InName, InSourceFilename, InFunctionName, InFrequency, InTotalPermutationCount,
            InConstructSerializedRef,
            InConstructCompiledRef,
            InModifyCompilationEnvironmentRef,
            InShouldCompilePermutationRef,
            InValidateCompiledResultRef,
            InTypeSize,
            InRootParametersMetadata)
{
    checkf(FPaths::GetExtension(InSourceFilename) == TEXT("usf"),
           TEXT("Incorrect virtual shader path extension for global shader '%s': Only .usf files should be "
                "compiled."),
           InSourceFilename);
}

又FGlobalShaderType的构造函数调用了其父类FShaderType的构造函数。

和顶点工厂的类似,会将自身加入到全局的着色器类型列表中(register this shader type)。

FShaderType::FShaderType(
	EShaderTypeForDynamicCast InShaderTypeForDynamicCast,
	FTypeLayoutDesc& InTypeLayout,
	const TCHAR* InName,
	const TCHAR* InSourceFilename,
	const TCHAR* InFunctionName,
	uint32 InFrequency,
	int32 InTotalPermutationCount,
	ConstructSerializedType InConstructSerializedRef,
	ConstructCompiledType InConstructCompiledRef,
	ModifyCompilationEnvironmentType InModifyCompilationEnvironmentRef,
	ShouldCompilePermutationType InShouldCompilePermutationRef,
	ValidateCompiledResultType InValidateCompiledResultRef,
	uint32 InTypeSize,
	const FShaderParametersMetadata* InRootParametersMetadata
	):
	ShaderTypeForDynamicCast(InShaderTypeForDynamicCast),
	TypeLayout(&InTypeLayout),
	Name(InName),
	TypeName(InName),
	HashedName(TypeName),
	HashedSourceFilename(InSourceFilename),
	SourceFilename(InSourceFilename),
	FunctionName(InFunctionName),
	Frequency(InFrequency),
	TypeSize(InTypeSize),
	TotalPermutationCount(InTotalPermutationCount),
	ConstructSerializedRef(InConstructSerializedRef),
	ConstructCompiledRef(InConstructCompiledRef),
	ModifyCompilationEnvironmentRef(InModifyCompilationEnvironmentRef),
	ShouldCompilePermutationRef(InShouldCompilePermutationRef),
	ValidateCompiledResultRef(InValidateCompiledResultRef),
	RootParametersMetadata(InRootParametersMetadata),
	GlobalListLink(this)
{
	FTypeLayoutDesc::Register(InTypeLayout);

	bCachedUniformBufferStructDeclarations = false;

	// This will trigger if an IMPLEMENT_SHADER_TYPE was in a module not loaded before InitializeShaderTypes
	// Shader types need to be implemented in modules that are loaded before that
	checkf(!bInitializedSerializationHistory, TEXT("Shader type was loaded after engine init, use ELoadingPhase::PostConfigInit on your module to cause it to load earlier."));

	//make sure the name is shorter than the maximum serializable length
	check(FCString::Strlen(InName) < NAME_SIZE);

	// Make sure the format of the source file path is right.
	check(CheckVirtualShaderFilePath(InSourceFilename));

	// register this shader type
	GlobalListLink.LinkHead(GetTypeList());
	GetNameToTypeMap().Add(HashedName, this);

	TArray<FShaderType*>& SortedTypes = GetSortedShaderTypes(InShaderTypeForDynamicCast);
	const int32 SortedIndex = Algo::LowerBoundBy(SortedTypes, HashedName, [](const FShaderType* InType) { return InType->GetHashedName(); });
	SortedTypes.Insert(this, SortedIndex);
}

这个全局着色器类型列表如下:

static TLinkedList<FShaderType*>*			GShaderTypeList = nullptr;

TLinkedList<FShaderType*>*& FShaderType::GetTypeList()
{
	return GShaderTypeList;
}

3.2 FMaterialShader/FMeshMaterialShader

FMaterialShader添加一个SetParameter函数,允许你自己shader中C++代码来修改HLSL中参数。

参数绑定通过FShaderParameter/FShaderResourceParameter完成,能在Shader构造函数中完成。

SetParameters函数在使用Shader渲染前被调用,并传递一些信息下去,包括material,这为你想修改的参数提供一部分用于计算的信息。

/** Base class of all shaders that need material parameters. */
// 所有需要材质参数的着色器基础类
class RENDERER_API FMaterialShader : public FShader
{
	DECLARE_TYPE_LAYOUT(FMaterialShader, NonVirtual);
public:
	using FPermutationParameters = FMaterialShaderPermutationParameters;
	using ShaderMetaType = FMaterialShaderType;
	
    // ...省略部分源码
}; 

FMeshMaterialShader,这为我们提供了在绘制每个Mesh之前,在我们的Shader中设置参数的能力。

大量Shader派生于这个类,因为它是所有需要Material和Vertex Factory参数Shader的基类。

可以在每个Mesh绘制前,修改GPU上的参数来适应特定的Mesh。

例子:TDepthOnlyVS/TBasePassVS/TBasePassPS

/** Base class of all shaders that need material and vertex factory parameters. */
// 所有需要材质和顶点工厂参数的Shader的基类
class RENDERER_API FMeshMaterialShader : public FMaterialShader
{
	DECLARE_TYPE_LAYOUT(FMeshMaterialShader, NonVirtual);
public:
	using FPermutationParameters = FMeshMaterialShaderPermutationParameters;
	using ShaderMetaType = FMeshMaterialShaderType;
    
   	// ...省略部分源码
};   

同样的,让我们来看一个例子TDepthOnlyVS

/**
 * A vertex shader for rendering the depth of a mesh.
 */
template <bool bUsePositionOnlyStream>
class TDepthOnlyVS : public FMeshMaterialShader
{
	DECLARE_SHADER_TYPE(TDepthOnlyVS,MeshMaterial);
};

IMPLEMENT_MATERIAL_SHADER_TYPE(template<>,TDepthOnlyVS<true>,TEXT("/Engine/Private/PositionOnlyDepthVertexShader.usf"),TEXT("Main"),SF_Vertex);
IMPLEMENT_MATERIAL_SHADER_TYPE(template<>,TDepthOnlyVS<false>,TEXT("/Engine/Private/DepthOnlyVertexShader.usf"),TEXT("Main"),SF_Vertex);

同样通过宏来声明和实现,同理其对应的着色器类型就FMeshMaterialShaderType。

其也是通过调用FShaderType的构造函数,将着色器类型和相关的着色器文件进行注册!

/**
 * A shader meta type for material-linked shaders which use a vertex factory.
 */
class FMeshMaterialShaderType : public FShaderType
{
	// ...省略部分源码 
};

小结:

  • 通过3.1和3.2的介绍,Unreal有两种不同的着色器类型,以及可以了解着色器和对应的着色器文件是如何绑定的。

四、RDG和Shader参数

虚幻4中存在两套渲染框架:

  • 一套是传统的以RHICmdList为核心构建RenderPass,从RHICmdList.BeginRenderPass(…)开始,以RHICmdList.EndRenderPass()结束的框架。
  • 另一套是以新的GraphBuilder为核心来构建RenderGraph。通过GraphBuilder.AddPass(…),以回调的方式来设置每个渲染Pass的逻辑。

目前虚幻正以RenderGraph的渲染框架重构整个渲染管线。

因此,了解和学会使用RDG是学习和修改虚幻的必由之路。

4.1 RDG

官方文档-渲染依赖性图表 告知我们,这是一个基于图表的调度系统,用于执行渲染管线的整帧优化。它利用DirectX 12这样的现代API,使用自动异步计算调度以及更高效的内存管理和屏障管理来提高性能。

RDG的概念如下:

在GPU上并非立即执行通道,而是延迟到整个帧已记录到依赖性图表数据结构之中后再执行。当完成了对所有通道的收集之后,则按照依赖性的排序顺序对图表进行编译和执行。可以参考游戏引擎随笔 0x03:可扩展的渲染管线架构加深概念的理解。

也就说RDG会构建出一个Pass的执行依赖图表,进行编译(Setup 所定义的资源和裁剪)再执行渲染(提交渲染指令)。

在这里插入图片描述

在虚幻的RDG中,有两个最基本也是最重要的组件:RDGBuilderRDGResource

前者一般用来创建渲染资源(比如RT和Buffer)等以及添加一个RenderPass到这个Graph内。

后者为可被RDG管理的资源所有的RDGBuffer和RDGTexture等都继承自它。

执行一个Graph的基本步骤:

  1. 使用一个RHICmdList来初始化一个Graph(这个CmdList既为整个Graph的CommandList)。
FRDGBuilder GraphBuilder(RHICmdList);
  1. 使用**GraphBuilder.AllocParameters()**来分配一个当前Pass的参数结构体;
FOpaqueBasePassUniformParameters* BasePassParameters = GraphBuilder.AllocParameters<FOpaqueBasePassUniformParameters>();
  1. 使用GraphBuilder.AddPass()结合当前Pass参数和一个Lambda表达式来创建一个Pass,Lambda表达式主要传递一些Pass的参数比如用到的Shader指针。
GraphBuilder.AddPass(
    RDG_EVENT_NAME("BasePassParallel"),
    // Pass参数
    PassParameters,
    ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass,
    // Lambda表达式
    [this, &View, PassParameters](FRHICommandListImmediate& RHICmdList)
    {
        Scene->UniformBuffers.UpdateViewUniformBuffer(View);
        FRDGParallelCommandListSet ParallelCommandListSet(RHICmdList, GET_STATID(STAT_CLP_BasePass), *this, View, FParallelCommandListBindings(PassParameters));
        View.ParallelMeshDrawCommandPasses[EMeshPass::BasePass].DispatchDraw(&ParallelCommandListSet, RHICmdList);
    });
  1. 在AddPass内执行当前Pass的计算,如果是CS就直接Dispatch,如果是VS/PS就设置一些PSO等内容然后Draw即可。
  2. 使用**GraphBuilder.QueueTex/Buffer()来把RGPass内的结果提取到外部的一个PoolRT或者RHIBuffer内,使用GraphBuilder.Execute()**去真正执行整个Graph的所有Pass。(前面AddPass一大堆只是DeferredContext)。

4.2 Shader参数

在第三章中,笔者了介绍了着色器(全局着色器和材质着色器),但还没有介绍过如何声明着色器参数,以及对参数进行传递实参!

接下来,将介绍如何在RDG的渲染框架下实现Shader参数的声明定义、创建赋值、提交(上传至GPU)

以下的笔记都是笔者自己的理解划分,如有错误还望见谅!

下面的介绍是一些较为通用的基础内容。

如果使用上遇到问题,最好的方法是参考虚幻中相似用途的源代码,进行模仿借鉴,这个方法更加有效!

4.2.1 参数定义相关宏

按照笔者的理解,着色器参数大体可以分为两种:

  1. Global UniformBuffer(全局着色器参数)
  2. Local UniformBuffer(局部着色器参数)

二者的区别在于:

  • 全局着色器参数:通过宏定义,Unreal会自动帮你生成对应的HLSL代码,无须你自己在着色器声明,可以被任一着色器引用包含。
  • 局部着色参数:仅限于当前着色器使用,需要自己手动在HLSL代码中加上对应的参数定义!

首先,介绍几个和着色器参数定义相关的宏。

这里以虚幻引擎中的一个后处理Shader(FUpscalePS)为例,进行介绍:

BEGIN_SHADER_PARAMETER_STRUCT(FUpscaleParameters, )
	SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
	SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Input)
	SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Output)
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneColorTexture)
	SHADER_PARAMETER_SAMPLER(SamplerState, SceneColorSampler)
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, PointSceneColorTexture)
	SHADER_PARAMETER_RDG_TEXTURE(Texture2DArray, PointSceneColorTextureArray)
	SHADER_PARAMETER_SAMPLER(SamplerState, PointSceneColorSampler)
	SHADER_PARAMETER_STRUCT(FPaniniProjectionParameters, Panini)
	SHADER_PARAMETER(float, UpscaleSoftness)
	RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()

class FUpscalePS : public FGlobalShader
{
public:
    // 声明着色器类型
	DECLARE_GLOBAL_SHADER(FUpscalePS);
	SHADER_USE_PARAMETER_STRUCT(FUpscalePS, FGlobalShader);
	using FParameters = FUpscaleParameters;

	class FMethodDimension : SHADER_PERMUTATION_ENUM_CLASS("METHOD", EUpscaleMethod);
	using FPermutationDomain = TShaderPermutationDomain<FMethodDimension>;

	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
	{
		const FPermutationDomain PermutationVector(Parameters.PermutationId);
		const EUpscaleMethod UpscaleMethod = PermutationVector.Get<FMethodDimension>();

		// Always allow point and bilinear upscale. (Provides upscaling for mobile emulation)
		if (UpscaleMethod == EUpscaleMethod::Nearest || UpscaleMethod == EUpscaleMethod::Bilinear)
		{
			return true;
		}

		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
	}
};

IMPLEMENT_GLOBAL_SHADER(FUpscalePS, "/Engine/Private/PostProcessUpscale.usf", "MainPS", SF_Pixel);

FUpscalePS为一个全局着色器!

  • DECLARE_GLOBAL_SHADER和上面介绍了声明全局着色器类型使用的宏是等价的!
#define DECLARE_GLOBAL_SHADER(ShaderClass) DECLARE_SHADER_TYPE(ShaderClass, Global)
DECLARE_GLOBAL_SHADER(FUpscalePS);
BEGIN_SHADER_PARAMETER_STRUCT、END_SHADER_PARAMETER_STRUCT 宏

用两个宏定义包围其着色器参数的定义!使用方法如下:

/** Begins & ends a shader parameter structure.
 *
 * Example:
 *	BEGIN_SHADER_PARAMETER_STRUCT(FMyParameterStruct, RENDERER_API)
 *	END_SHADER_PARAMETER_STRUCT()
 */

看一下BEGIN宏!

  • 内部使用了INTERNAL_SHADER_PARAMETER_STRUCT_BEGIN等宏
// Begin宏
#define BEGIN_SHADER_PARAMETER_STRUCT(StructTypeName, PrefixKeywords) \
	INTERNAL_SHADER_PARAMETER_STRUCT_BEGIN(StructTypeName, PrefixKeywords, {}, INTERNAL_SHADER_PARAMETER_GET_STRUCT_METADATA(StructTypeName), INTERNAL_SHADER_PARAMETER_STRUCT_CREATE_UNIFORM_BUFFER)

注:为了方便对应代码使用了 /***/ 在下面进行注释,帮助理解!真正宏定义里可不能这么干哦!

/** Begins a uniform buffer struct declaration. */
#define INTERNAL_SHADER_PARAMETER_STRUCT_BEGIN(StructTypeName,PrefixKeywords,ConstructorSuffix,GetStructMetadataScope,CreateUniformBufferImpl) \
	/** 16比特对齐,声明了一个类StructTypeName */ 
	MS_ALIGN(SHADER_PARAMETER_STRUCT_ALIGNMENT) class PrefixKeywords StructTypeName \
	{ \
	public: \
        /** StructTypeName构造函数 */
		StructTypeName () ConstructorSuffix \
        /** 内部类FTypeInfo
        *	GetStructMetadata函数:GetStructMetadataScope,是由INTERNAL_SHADER_PARAMETER_GET_STRUCT_METADATA宏实现的。
        *	CreateUniformBuffer函数:CreateUniformBufferImpl,是由INTERNAL_SHADER_PARAMETER_STRUCT_CREATE_UNIFORM_BUFFER宏实现的。
        */
		struct FTypeInfo { \
			static constexpr int32 NumRows = 1; \
			static constexpr int32 NumColumns = 1; \
			static constexpr int32 NumElements = 0; \
			static constexpr int32 Alignment = SHADER_PARAMETER_STRUCT_ALIGNMENT; \
			static constexpr bool bIsStoredInConstantBuffer = true; \
			using TAlignedType = StructTypeName; \
			static inline const FShaderParametersMetadata* GetStructMetadata() { GetStructMetadataScope } \
		}; \
		static FUniformBufferRHIRef CreateUniformBuffer(const StructTypeName& InContents, EUniformBufferUsage InUsage) \
		{ \
			CreateUniformBufferImpl \
		} \
	private: \
        /** StructTypeName取了个别名为:zzTThisStruct */
		typedef StructTypeName zzTThisStruct; \
        /** 定义一个zzFirstMemberId结构体,枚举为0 */
		struct zzFirstMemberId { enum { HasDeclaredResource = 0 }; }; \
        /** void* 取别名为 zzFuncPtr */
		typedef void* zzFuncPtr; \
        /** 定义了一个函数指针的别名 zzMemberFunc */
		typedef zzFuncPtr(*zzMemberFunc)(zzFirstMemberId, TArray<FShaderParametersMetadata::FMember>*); \
        /** 静态函数 zzAppendMemberGetPrev 返回空指针 */
		static zzFuncPtr zzAppendMemberGetPrev(zzFirstMemberId, TArray<FShaderParametersMetadata::FMember>*) \
		{ \
			return nullptr; \
		} \
		typedef zzFirstMemberId

// GetStructMetadataScope的实现:
#define INTERNAL_SHADER_PARAMETER_GET_STRUCT_METADATA(StructTypeName) \
	/** 定义了一个FShaderParametersMetadata的实例,并直接返回 */
	static FShaderParametersMetadata StaticStructMetadata(\
       	/** 输入用途类型参数为 ShaderParameterStruct */                                     
		FShaderParametersMetadata::EUseCase::ShaderParameterStruct, \
		TEXT(#StructTypeName), \
		TEXT(#StructTypeName), \
		nullptr, \
		nullptr, \
		sizeof(StructTypeName), \
        /** 调用zzGetMembers 返回所有定义的着色器参数成员 */                                              
		StructTypeName::zzGetMembers()); \
	return &StaticStructMetadata;
            
// CreateUniformBufferImpl的实现,返回空!
#define INTERNAL_SHADER_PARAMETER_STRUCT_CREATE_UNIFORM_BUFFER return nullptr;
  • 让我们继续看一下FShaderParametersMetadata类:
/** A uniform buffer struct. */
class RENDERCORE_API FShaderParametersMetadata
{
public:
	/** The use case of the uniform buffer structures. */
    // uniform buffer结构体的用途!
	enum class EUseCase
	{
		/** Stand alone shader parameter struct used for render passes and shader parameters. */
        /** 用于渲染通道和着色器参数的独立着色器参数结构 */
		ShaderParameterStruct,

		/** Uniform buffer definition authored at compile-time. */
        /** 在编译时编写的统一缓冲区定义 */
		UniformBuffer,

		/** Uniform buffer generated from assets, such as material parameter collection or Niagara. */
        /** 由资产生成的统一缓冲区,例如材质参数集合或 Niagara */
		DataDrivenUniformBuffer,
	};
    
    /** Shader binding name of the uniform buffer that contains the root shader parameters. */
	static constexpr const TCHAR* kRootUniformBufferBindingName = TEXT("_RootShaderParameters");
    
    /** A member of a shader parameter structure. */
    /** 一个着色器参数成员的代表结构体 */
	class RENDERCORE_API FMember
	{
	public:
		/** Initialization constructor. */
		FMember(
			const TCHAR* InName,
			const TCHAR* InShaderType,
			uint32 InOffset,
			EUniformBufferBaseType InBaseType,
			EShaderPrecisionModifier::Type InPrecision,
			uint32 InNumRows,
			uint32 InNumColumns,
			uint32 InNumElements,
			const FShaderParametersMetadata* InStruct
			)
		:	Name(InName)
		,	ShaderType(InShaderType)
		,	Offset(InOffset)
		,	BaseType(InBaseType)
		,	Precision(InPrecision)
		,	NumRows(InNumRows)
		,	NumColumns(InNumColumns)
		,	NumElements(InNumElements)
		,	Struct(InStruct)
		{}

		/** Returns the string of the name of the element or name of the array of elements. */
		const TCHAR* GetName() const { return Name; }

		/** Returns the string of the type. */
		const TCHAR* GetShaderType() const { return ShaderType; }

		/** Returns the offset of the element in the shader parameter struct in bytes. */
		uint32 GetOffset() const { return Offset; }

		/** Returns the type of the elements, int, UAV... */
		EUniformBufferBaseType GetBaseType() const { return BaseType; }

		/** Floating point the element is being stored. */
		EShaderPrecisionModifier::Type GetPrecision() const { return Precision; }

		/** Returns the number of row in the element. For instance FMatrix would return 4, or FVector would return 1. */
		uint32 GetNumRows() const { return NumRows; }

		/** Returns the number of column in the element. For instance FMatrix would return 4, or FVector would return 3. */
		uint32 GetNumColumns() const { return NumColumns; }

		/** Returns the number of elements in array, or 0 if this is not an array. */
		uint32 GetNumElements() const { return NumElements; }

		/** Returns the metadata of the struct. */
		const FShaderParametersMetadata* GetStructMetadata() const { return Struct; }

		/** Returns the size of the member. */
		inline uint32 GetMemberSize() const
		{
			check(BaseType == UBMT_FLOAT32 || BaseType == UBMT_INT32 || BaseType == UBMT_UINT32);
			uint32 ElementSize = sizeof(uint32) * NumRows * NumColumns;

			/** If this an array, the alignment of the element are changed. */
			if (NumElements > 0)
			{
				return Align(ElementSize, SHADER_PARAMETER_ARRAY_ELEMENT_ALIGNMENT) * NumElements;
			}
			return ElementSize;
		}

		void GenerateShaderParameterType(FString& Result, EShaderPlatform ShaderPlatform) const;

	private:

		const TCHAR* Name;
		const TCHAR* ShaderType;
		uint32 Offset;
		EUniformBufferBaseType BaseType;
		EShaderPrecisionModifier::Type Precision;
		uint32 NumRows;
		uint32 NumColumns;
		uint32 NumElements;
		const FShaderParametersMetadata* Struct;
	};
    
	/** Initialization constructor. */
	// 构造函数!
	FShaderParametersMetadata(
		EUseCase UseCase,
		const TCHAR* InLayoutName,
		const TCHAR* InStructTypeName,
		const TCHAR* InShaderVar-iableName,
		const TCHAR* InStaticSlotName,
		uint32 InSize,
		const TArray<FMember>& InMembers);

	// ...省略部分源码
};
  • 重点关注其构造函数!FShaderParametersMetadata如下:
FShaderParametersMetadata::FShaderParametersMetadata(
    // uniform buffer结构体的用途!
	EUseCase InUseCase,
	const TCHAR* InLayoutName,
	const TCHAR* InStructTypeName,
	const TCHAR* InShaderVariableName,
	const TCHAR* InStaticSlotName,
	uint32 InSize,
	const TArray<FMember>& InMembers)
	: StructTypeName(InStructTypeName)
	, ShaderVariableName(InShaderVariableName)
	, StaticSlotName(InStaticSlotName)
	, ShaderVariableHashedName(InShaderVariableName)
	, Size(InSize)
	, UseCase(InUseCase)
	, Layout(InLayoutName)
	, Members(InMembers)
	, GlobalListLink(this)
	, bLayoutInitialized(false)
{
	check(StructTypeName);
    // 如果是ShaderParameterStruct类型的用途
	if (UseCase == EUseCase::ShaderParameterStruct)
	{	
        // 检测StaticSlotName是否为null
		checkf(!StaticSlotName, TEXT("Only uniform buffers can be tagged with a static slot."));
		check(ShaderVariableName == nullptr);
	}
	else
	{
		check(ShaderVariableName);
	}

    // 如果是UniformBuffer用途
	if (UseCase == EUseCase::UniformBuffer)
	{
		// Register this uniform buffer struct in global list.
        // 那么自身记录到全局的结构体列表当中!
		GlobalListLink.LinkHead(GetStructList());

		FName StructTypeFName(StructTypeName);
		// Verify that during FName creation there's no case conversion
		checkSlow(FCString::Strcmp(StructTypeName, *StructTypeFName.GetPlainNameString()) == 0);
		GetNameStructMap().Add(ShaderVariableHashedName, this);


#if VALIDATE_UNIFORM_BUFFER_UNIQUE_NAME
		FName ShaderVariableFName(ShaderVariableName);

		// Verify that the global variable name is unique so that we can disambiguate when reflecting from shader source.
		if (FName* StructFName = GlobalShaderVariableToStructMap.Find(ShaderVariableFName))
		{
			checkf(
				false,
				TEXT("Found duplicate Uniform Buffer shader variable name %s defined by struct %s. Previous definition ")
				TEXT("found on struct %s. Uniform buffer shader names must be unique to support name-based reflection of ")
				TEXT("shader source files."),
				ShaderVariableName,
				StructTypeName,
				*StructFName->GetPlainNameString());
		}

		GlobalShaderVariableToStructMap.Add(ShaderVariableFName, StructTypeFName);
#endif
	}
	else
	{
		// We cannot initialize the layout during global initialization, since we have to walk nested struct members.
		// Structs created during global initialization will have bRegisterForAutoBinding==false, and are initialized during startup.
		// Structs created at runtime with bRegisterForAutoBinding==true can be initialized now.
		// 我们不能在全局初始化期间初始化布局,因为我们必须遍历嵌套的结构成员。
		// 在全局初始化期间创建的结构体将具有 bRegisterForAutoBinding==false,并在启动期间进行初始化。
		// 现在可以初始化在运行时使用 bRegisterForAutoBinding==true 创建的结构。
		InitializeLayout();
	}
}

static TLinkedList<FShaderParametersMetadata*>* GUniformStructList = nullptr;

TLinkedList<FShaderParametersMetadata*>*& FShaderParametersMetadata::GetStructList()
{
	return GUniformStructList;
}

看一下END宏!

// End宏
#define END_SHADER_PARAMETER_STRUCT() \
		/** 承接前面的上面的typeef(这个要和SHADER_PARAMETER宏一起看才能理解!) */
		zzLastMemberId; \
	public: \
		/** 定义了静态函数,zzGetMembers,这个函数被上面使用返回所有两个宏之间的定义的参数 */
		static TArray<FShaderParametersMetadata::FMember> zzGetMembers() { \
        	/* 定义了要返回的成员数组 */
			TArray<FShaderParametersMetadata::FMember> Members; \
			zzFuncPtr(*LastFunc)(zzLastMemberId, TArray<FShaderParametersMetadata::FMember>*); \
			LastFunc = zzAppendMemberGetPrev; \
			zzFuncPtr Ptr = (zzFuncPtr)LastFunc; \
			do \
			{ \
                /* 循环调用zzAppendMemberGetPrev:将成员添加并将前一个定义好的zzAppendMemberGetPrev指针返回 */
				Ptr = reinterpret_cast<zzMemberFunc>(Ptr)(zzFirstMemberId(), &Members); \
			} while (Ptr); \
			Algo::Reverse(Members); \
			return Members; \
		} \
	} GCC_ALIGN(SHADER_PARAMETER_STRUCT_ALIGNMENT);
SHADER_PARAMETER 宏

SHADER_PARAMETER,是基本类型的着色器参数定义使用的宏!

例如 SHADER_PARAMETER(float, UpscaleSoftness)

  • 在其中,调用了SHADER_PARAMETER_EX宏。SHADER_PARAMETER_EX宏又调用了INTERNAL_SHADER_PARAMETER_EXPLICIT宏。
#define SHADER_PARAMETER(MemberType,MemberName) \
	SHADER_PARAMETER_EX(MemberType,MemberName,EShaderPrecisionModifier::Float)

/** 
*	MemberType 是一些基础的类型,如float 
*	MemberName 是变量名
*	TShaderParameterTypeInfo<MemberType>::BaseType 作为 BaseType
*	TShaderParameterTypeInfo<MemberType> 作为 TypeInfo
*/
#define SHADER_PARAMETER_EX(MemberType,MemberName,Precision) \
	INTERNAL_SHADER_PARAMETER_EXPLICIT(TShaderParameterTypeInfo<MemberType>::BaseType, TShaderParameterTypeInfo<MemberType>, MemberType,MemberName,,,Precision,TEXT(""),false)

/** Declares a member of a uniform buffer struct. */
#define
INTERNAL_SHADER_PARAMETER_EXPLICIT(BaseType,TypeInfo,MemberType,MemberName,ArrayDecl,DefaultValue,Precision,OptionalShaderType,IsMemberStruct) \
    	/* 
    	*	若接Begin的话,有 typedef zzFirstMemberId zzMemberId##MemberName 
    	*	就有 zzMemberId##MemberName 为 struct zzFirstMemberId { enum { HasDeclaredResource = 0 }; };
    	*/
		zzMemberId##MemberName; \
	public: \
        /** 定义了一个着色器变量的成员 */
		TypeInfo::TAlignedType MemberName DefaultValue; \
        /** static_assert 用于编译期间的断言 */
		static_assert(BaseType != UBMT_INVALID, "Invalid type " #MemberType " of member " #MemberName "."); \
	private: \
        /** 定义了一个结构体 标记下一个成员变量的ID */
		struct zzNextMemberId##MemberName { enum { HasDeclaredResource = zzMemberId##MemberName::HasDeclaredResource || !TypeInfo::bIsStoredInConstantBuffer }; }; \
        /** 定义了一个函数 传入的参数为 1)zzNextMemberId##MemberName类型,2)为Members(着色器参数元数据数组) */
		static zzFuncPtr zzAppendMemberGetPrev(zzNextMemberId##MemberName, TArray<FShaderParametersMetadata::FMember>* Members) \
		{ \
			static_assert(TypeInfo::bIsStoredInConstantBuffer || TIsArrayOrRefOfType<decltype(OptionalShaderType), TCHAR>::Value, "No shader type for " #MemberName "."); \
			static_assert(\
				(STRUCT_OFFSET(zzTThisStruct, MemberName) & (TypeInfo::Alignment - 1)) == 0, \
				"Misaligned uniform buffer struct member " #MemberName "."); \
            /** 构造了一个 FShaderParametersMetadata::FMember的实例,加入到了Members中 */
			Members->Add(FShaderParametersMetadata::FMember( \
				TEXT(#MemberName), \
				OptionalShaderType, \
				STRUCT_OFFSET(zzTThisStruct,MemberName), \
				EUniformBufferBaseType(BaseType), \
				Precision, \
				TypeInfo::NumRows, \
				TypeInfo::NumColumns, \
				TypeInfo::NumElements, \
				TypeInfo::GetStructMetadata() \
				)); \
            // 定义了上一个zzAppendMemberGetPrev函数指针的类型!
			zzFuncPtr(*PrevFunc)(zzMemberId##MemberName, TArray<FShaderParametersMetadata::FMember>*); \
			PrevFunc = zzAppendMemberGetPrev; \
            /** 将前一个定义好的zzAppendMemberGetPrev函数指针返回 */
			return (zzFuncPtr)PrevFunc; \
		} \
        /** 又给 zzNextMemberId##MemberName 取别名 让后面宏进行承接*/
		typedef zzNextMemberId##MemberName

INTERNAL_SHADER_PARAMETER_EXPLICIT了什么:

  1. 通过定义了一个类的成员变量(即着色器变量);
  2. 声明了一个结构:struct zzNextMemberId##MemberName;
  3. 定义一个函数zzAppendMemberGetPrev,这个函数将该宏声明的成员构造成为FShaderParametersMetadata::FMember添加到Members;

看到这里比较困惑,举个简单的例子:

BEGIN_SHADER_PARAMETER_STRUCT(FMyStruct,)
	SHADER_PARAMETER(float, MyScalar)
    SHADER_PARAMETER(int, MyInt)
END_SHADER_PARAMETER_STRUCT()

展开后有:

__declspec(align(16)) class FMyStruct
{
public:
	FMyStruct (){}
	struct FTypeInfo 
	{
		static constexpr int32 NumRows = 1;
		static constexpr int32 NumColumns = 1;
		static constexpr int32 NumElements = 0;
		static constexpr int32 Alignment = 16;
		static constexpr bool bIsStoredInConstantBuffer = true;
		using TAlignedType = FMyStruct;
		
		static inline const FShaderParametersMetadata* GetStructMetadata()
		{ 
			static FShaderParametersMetadata StaticStructMetadata(
			/** 输入用途类型参数为 ShaderParameterStruct */                                     
			FShaderParametersMetadata::EUseCase::ShaderParameterStruct, 
			TEXT("FMyStruct"), 
			TEXT("FMyStruct"), 
			nullptr, 
			nullptr,
			sizeof(FMyStruct),
			/** 调用zzGetMembers 返回所有定义的着色器参数成员 */                                              
			FMyStruct::zzGetMembers());
			return &StaticStructMetadata;
		}
	};
	static FUniformBufferRHIRef CreateUniformBuffer(const FMyStruct& InContents, EUniformBufferUsage InUsage)
	{ 
		return nullptr; 
	} 
    
private:
	typedef FMyStruct zzTThisStruct;
	struct zzFirstMemberId { enum { HasDeclaredResource = 0 }; };
	
	typedef void* zzFuncPtr;
	typedef zzFuncPtr(*zzMemberFunc)(zzFirstMemberId, TArray<FShaderParametersMetadata::FMember>*);
	static zzFuncPtr zzAppendMemberGetPrev(zzFirstMemberId, TArray<FShaderParametersMetadata::FMember>*)
	{
		return nullptr; 
	}
	typedef zzFirstMemberId zzMemberIdMyScalar;
public:
	TShaderParameterTypeInfo<float> MyScalar;
	
private:
	struct zzNextMemberIdMyScalar { enum { HasDeclaredResource = zzMemberIdMyScalar::HasDeclaredResource || !TShaderParameterTypeInfo<float>::bIsStoredInConstantBuffer }; };
    // 函数的第一个参数为宏为 zzNextMemberIdMyScalar
	static zzFuncPtr zzAppendMemberGetPrev(zzNextMemberIdMyScalar, TArray<FShaderParametersMetadata::FMember>* Members)
	{
		Members->Add(FShaderParametersMetadata::FMember(
			TEXT("MyScalar"),
			"",
			STRUCT_OFFSET(zzTThisStruct,MyScalar),
			EUniformBufferBaseType(TShaderParameterTypeInfo<float>::BaseType),
			EShaderPrecisionModifier::Float,
			TShaderParameterTypeInfo<float>::NumRows,
			TShaderParameterTypeInfo<float>::NumColumns,
			TShaderParameterTypeInfo<float>::NumElements,
			TShaderParameterTypeInfo<float>::GetStructMetadata()
		));
        // zzMemberIdMyScalar 实则为 zzFirstMemberId类型,则可以获取到上面那个同名函数的指针!zFuncPtr zzAppendMemberGetPrev(zzFirstMemberId, TArray<FShaderParametersMetadata::FMember>*)
		zzFuncPtr(*PrevFunc)(zzMemberIdMyScalar, TArray<FShaderParametersMetadata::FMember>*);
		PrevFunc = zzAppendMemberGetPrev;
		return (zzFuncPtr)PrevFunc; 
	}
	typedef zzNextMemberIdMyScalar zzMemberIdMyInt;
	
public:
	TShaderParameterTypeInfo<int> MyInt;
	
private:
	struct zzNextMemberIdMyInt { enum { HasDeclaredResource = zzMemberIdMyInt::HasDeclaredResource || !TShaderParameterTypeInfo<int>::bIsStoredInConstantBuffer }; };
	static zzFuncPtr zzAppendMemberGetPrev(zzNextMemberIdMyInt, TArray<FShaderParametersMetadata::FMember>* Members)
	{
		Members->Add(FShaderParametersMetadata::FMember(
			TEXT("MyScalar"),
			"",
			STRUCT_OFFSET(zzTThisStruct,MyScalar),
			EUniformBufferBaseType(TShaderParameterTypeInfo<int>::BaseType),
			EShaderPrecisionModifier::Float,
			TShaderParameterTypeInfo<int>::NumRows,
			TShaderParameterTypeInfo<int>::NumColumns,
			TShaderParameterTypeInfo<int>::NumElements,
			TShaderParameterTypeInfo<int>::GetStructMetadata()
		));
        // zzMemberIdMyInt 实则为 zzNextMemberIdMyScalar类型,则可以获取到上面那个同名函数的指针!zFuncPtr zzAppendMemberGetPrev(zzNextMemberIdMyScalar, TArray<FShaderParametersMetadata::FMember>*)
		zzFuncPtr(*PrevFunc)(zzMemberIdMyInt, TArray<FShaderParametersMetadata::FMember>*);
		PrevFunc = zzAppendMemberGetPrev;
		return (zzFuncPtr)PrevFunc; 
	}
	typedef zzNextMemberIdMyInt zzLastMemberId;
	
public:
	static TArray<FShaderParametersMetadata::FMember> zzGetMembers() 
	{
		/* 定义了要返回的成员数组 */
		TArray<FShaderParametersMetadata::FMember> Members;
        // zzLastMemberId 实则为 zzNextMemberIdMyInt,则可以获取到上面那个同名函数的指针!zFuncPtr zzAppendMemberGetPrev(zzNextMemberIdMyInt, TArray<FShaderParametersMetadata::FMember>*)
		zzFuncPtr(*LastFunc)(zzLastMemberId, TArray<FShaderParametersMetadata::FMember>*); 
		LastFunc = zzAppendMemberGetPrev;
		zzFuncPtr Ptr = (zzFuncPtr)LastFunc; 
		do 
		{ 
			/* 循环调用zzAppendMemberGetPrev:将成员添加并将前一个定义好的zzAppendMemberGetPrev指针返回 */
			Ptr = reinterpret_cast<zzMemberFunc>(Ptr)(zzFirstMemberId(), &Members); 
		} while (Ptr); 
		Algo::Reverse(Members);
		return Members; 
	}		
};

通过宏定义和typedef的妙用,结合函数重载,在zzGetMembers函数中就可以将所有的FShaderParametersMetadata::FMember全部获取到。

笔者认为这做了一个收集的工作!用于后续Shader和C++参数绑定!

zzGetMembers函数是在FMyStruct::FTypeInfo::GetStructMetadata函数被调用时候,构造FShaderParametersMetadata实例时作为参数传入!

更多SHADER_PARAMETER的宏定义,如以下这些用于定义不同类型的着色器参数:

// SHADER_PARAMETER
SHADER_PARAMETER_ARRAY
SHADER_PARAMETER_TEXTURE
SHADER_PARAMETER_TEXTURE_ARRAY
SHADER_PARAMETER_SRV
SHADER_PARAMETER_SRV_ARRAY
SHADER_PARAMETER_UAV
SHADER_PARAMETER_SAMPLER
SHADER_PARAMETER_SAMPLER_ARRAY

也都是封装使用了INTERNAL_SHADER_PARAMETER_EXPLICIT 宏,传递的参数略有不同。

如下SHADER_PARAMETER_TEXTURE

#define SHADER_PARAMETER_TEXTURE(ShaderType,MemberName) \
	INTERNAL_SHADER_PARAMETER_EXPLICIT(UBMT_TEXTURE, TShaderResourceParameterTypeInfo<FRHITexture*>, FRHITexture*,MemberName,, = nullptr,EShaderPrecisionModifier::Float,TEXT(#ShaderType),false)

其中:

  • 参数1的为一个EUniformBufferBaseType枚举类型变量。(这个枚举类型非常有用!)

  • 参数2、参数3则是根据输入的类型为其对应的资源类型。

/** The base type of a value in a uniform buffer. */
enum EUniformBufferBaseType : uint8
{
	UBMT_INVALID,

	// Invalid type when trying to use bool, to have explicit error message to programmer on why
	// they shouldn't use bool in shader parameter structures.
	UBMT_BOOL,

	// Parameter types.
	UBMT_INT32,
	UBMT_UINT32,
	UBMT_FLOAT32,

	// RHI resources not tracked by render graph.
	UBMT_TEXTURE,
	UBMT_SRV,
	UBMT_UAV,
	UBMT_SAMPLER,

	// Resources tracked by render graph.
	UBMT_RDG_TEXTURE,
	UBMT_RDG_TEXTURE_ACCESS,
	UBMT_RDG_TEXTURE_SRV,
	UBMT_RDG_TEXTURE_UAV,
	UBMT_RDG_BUFFER,
	UBMT_RDG_BUFFER_ACCESS,
	UBMT_RDG_BUFFER_SRV,
	UBMT_RDG_BUFFER_UAV,
	UBMT_RDG_UNIFORM_BUFFER,

	// Nested structure.
	UBMT_NESTED_STRUCT,

	// Structure that is nested on C++ side, but included on shader side.
	UBMT_INCLUDED_STRUCT,

	// GPU Indirection reference of struct, like is currently named Uniform buffer.
	UBMT_REFERENCED_STRUCT,

	// Structure dedicated to setup render targets for a rasterizer pass.
	UBMT_RENDER_TARGET_BINDING_SLOTS,

	EUniformBufferBaseType_Num,
	EUniformBufferBaseType_NumBits = 5,
};

在RDG框架下,还有一些定义参数的宏:

// RDG类型
SHADER_PARAMETER_RDG_TEXTURE
SHADER_PARAMETER_RDG_TEXTURE_ARRAY
SHADER_PARAMETER_RDG_TEXTURE_SRV
SHADER_PARAMETER_RDG_TEXTURE_SRV_ARRAY
SHADER_PARAMETER_RDG_TEXTURE_UAV
SHADER_PARAMETER_RDG_TEXTURE_UAV_ARRAY
SHADER_PARAMETER_RDG_BUFFER
SHADER_PARAMETER_RDG_BUFFER_ARRAY
SHADER_PARAMETER_RDG_BUFFER_SRV
SHADER_PARAMETER_RDG_BUFFER_SRV_ARRAY
SHADER_PARAMETER_RDG_BUFFER_UAV
SHADER_PARAMETER_RDG_BUFFER_UAV_ARRAY
SHADER_PARAMETER_RDG_UNIFORM_BUFFER

和前面的区别就在于:枚举类型与具体RDG资源类型的使用。

#define SHADER_PARAMETER_RDG_TEXTURE(ShaderType,MemberName) \
	INTERNAL_SHADER_PARAMETER_EXPLICIT(UBMT_RDG_TEXTURE, TShaderResourceParameterTypeInfo<FRDGTexture*>, FRDGTexture*,MemberName,, = nullptr,EShaderPrecisionModifier::Float,TEXT(#ShaderType),false)

除了上述的**逐一类型(逐一变量)**的参数定义,UE还提供了一个结构体的定义方式,相关的宏:

SHADER_PARAMETER_STRUCT
SHADER_PARAMETER_STRUCT_ARRAY
SHADER_PARAMETER_STRUCT_INCLUDE
SHADER_PARAMETER_STRUCT_REF

SHADER_PARAMETER_STRUCT

/** Nests a shader parameter structure into another one, in C++ and shader code.
 *
 * Example:
 *	BEGIN_SHADER_PARAMETER_STRUCT(FMyNestedStruct,)
 *		SHADER_PARAMETER(float, MyScalar)
 *		// ...
 *	END_SHADER_PARAMETER_STRUCT()
 *
 *	BEGIN_SHADER_PARAMETER_STRUCT(FOtherStruct)
 *		SHADER_PARAMETER_STRUCT(FMyNestedStruct, MyStruct)
 *
 * C++ use case:
 *	FOtherStruct Parameters;
 *	Parameters.MyStruct.MyScalar = 1.0f;
 *
 * Shader code for a globally named shader parameter struct:
 *	float MyScalar = MyGlobalShaderBindingName.MyStruct.MyScalar;
 *
 * Shader code for a unnamed shader parameter struct:
 *	float MyScalar = MyStruct_MyScalar;
 */
#define SHADER_PARAMETER_STRUCT(StructType,MemberName) \
	INTERNAL_SHADER_PARAMETER_EXPLICIT(UBMT_NESTED_STRUCT, StructType::FTypeInfo, StructType, MemberName,,,EShaderPrecisionModifier::Float,TEXT(#StructType),true)

对于SHADER_PARAMETER_STRUCT,需要自己在着色器中定义对应的参数。

例如:

// C++端:
BEGIN_SHADER_PARAMETER_STRUCT(FPaniniProjectionParameters, )
	SHADER_PARAMETER(float, D)
	SHADER_PARAMETER(float, S)
	SHADER_PARAMETER(float, ScreenPosScale)
END_SHADER_PARAMETER_STRUCT()
    
BEGIN_SHADER_PARAMETER_STRUCT(FUpscaleParameters, )
	// ...省略部分源码
	SHADER_PARAMETER_STRUCT(FPaniniProjectionParameters, Panini)
    // ...省略部分源码
END_SHADER_PARAMETER_STRUCT()

// Shader端:
float Panini_D;
float Panini_S;
float Panini_ScreenPosScale;    

SHADER_PARAMETER_STRUCT_ARRAY

/** Nests an array of shader parameter structure into another one, in C++ and shader code.
 *
 * Example:
 *	BEGIN_SHADER_PARAMETER_STRUCT(FMyNestedStruct,)
 *		SHADER_PARAMETER(float, MyScalar)
 *		// ...
 *	END_SHADER_PARAMETER_STRUCT()
 *
 *	BEGIN_SHADER_PARAMETER_STRUCT(FOtherStruct)
 *		SHADER_PARAMETER_STRUCT_ARRAY(FMyNestedStruct, MyStructArray, [4])
 *
 * C++ use case:
 *	FOtherStruct Parameters;
 *	Parameters.MyStructArray[0].MyScalar = 1.0f;
 *
 * Shader code for a globally named shader parameter struct (UNSUPPORTED):
 *	float MyScalar = MyGlobalShaderBindingName.MyStructArray_0.MyScalar;
 *
 * Shader code for a unnamed shader parameter struct:
 *	float MyScalar = MyStructArray_0_MyScalar;
 */
#define SHADER_PARAMETER_STRUCT_ARRAY(StructType,MemberName, ArrayDecl) \
	INTERNAL_SHADER_PARAMETER_EXPLICIT(UBMT_NESTED_STRUCT, TShaderParameterStructTypeInfo<StructType ArrayDecl>, StructType, MemberName,ArrayDecl,,EShaderPrecisionModifier::Float,TEXT(#StructType),true)

SHADER_PARAMETER_STRUCT_INCLUDE

/** Include a shader parameter structure into another one in shader code.
 *
 * Example:
 *	BEGIN_SHADER_PARAMETER_STRUCT(FMyNestedStruct,)
 *		SHADER_PARAMETER(float, MyScalar)
 *		// ...
 *	END_SHADER_PARAMETER_STRUCT()
 *
 *	BEGIN_SHADER_PARAMETER_STRUCT(FOtherStruct)
 *		SHADER_PARAMETER_STRUCT_INCLUDE(FMyNestedStruct, MyStruct)
 *
 * C++ use case:
 *	FOtherStruct Parameters;
 *	Parameters.MyStruct.MyScalar = 1.0f;
 *
 * Shader code for a globally named shader parameter struct:
 *	float MyScalar = MyGlobalShaderBindingName.MyScalar;
 *
 * Shader code for a unnamed shader parameter struct:
 *	float MyScalarValue = MyScalar;
 */
#define SHADER_PARAMETER_STRUCT_INCLUDE(StructType,MemberName) \
	INTERNAL_SHADER_PARAMETER_EXPLICIT(UBMT_INCLUDED_STRUCT, StructType::FTypeInfo, StructType, MemberName,,,EShaderPrecisionModifier::Float,TEXT(#StructType),true)

对于SHADER_PARAMETER_STRUCT_INCLUDE,也需要自己在着色器中定义对应的参数。

可以参考

PostProcessSubsurface.cpp
PostProcessSubsurface.usf    

SHADER_PARAMETER_STRUCT_REF

/** Include a binding slot for a globally named shader parameter structure
 *
 * Example:
 *	BEGIN_UNIFORM_BUFFER_STRUCT(FGlobalViewParameters,)
 *		SHADER_PARAMETER(FVector4, ViewSizeAndInvSize)
 *		// ...
 *	END_UNIFORM_BUFFER_STRUCT()
 *
 *	BEGIN_SHADER_PARAMETER_STRUCT(FOtherStruct)
 *		SHADER_PARAMETER_STRUCT_REF(FMyNestedStruct, MyStruct)
 */
#define SHADER_PARAMETER_STRUCT_REF(StructType,MemberName) \
	INTERNAL_SHADER_PARAMETER_EXPLICIT(UBMT_REFERENCED_STRUCT, TShaderParameterTypeInfo<TUniformBufferRef<StructType>>, TUniformBufferRef<StructType>,MemberName,,,EShaderPrecisionModifier::Float,TEXT(#StructType),false)

使用SHADER_PARAMETER_STRUCT_REF,虚幻会自动帮你生成对应的HLSL参数定义(因为其实用了BEGIN_UNIFORM_BUFFER_STRUCT),只需包含头文件,直接使用其中的变量名即可。可参考:

//C++端:FVisualizeHDRPS
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
	SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
    // ...省略部分源码
END_SHADER_PARAMETER_STRUCT()
    
// PostProcessVisualizeHDR.usf
#include "Common.ush"

void MainPS(noperspective float4 UVAndScreenPos : TEXCOORD0, float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0)
{
    // ...省略部分源码
    #if USE_PREEXPOSURE
    	// 直接使用View.OneOverPreExposure,无需定义
    	LuminanceAvg *= View.OneOverPreExposure;
	#endif
    // ...
}

以上几种结构体的方式在虚幻中大量被使用,主要需要掌握其注释中的配套用法!

SHADER_USE_PARAMETER_STRUCT 宏

传入的参数正是通过:BEGIN和END宏包围定义的类FParameters,这个宏做了以下事情:

  • 调用了SHADER_USE_PARAMETER_STRUCT_INTERNAL,定义了着色器类的构造函数!其中一个构造函数调用了BindForLegacyShaderParameters函数。

  • 声明一个静态函数GetRootParametersMetadata,调用的正是FParameters::FTypeInfo::GetStructMetadata函数,即我们上面介绍的。

// 入口!
#define SHADER_USE_PARAMETER_STRUCT(ShaderClass, ShaderParentClass) \
	SHADER_USE_PARAMETER_STRUCT_INTERNAL(ShaderClass, ShaderParentClass, true) \
	\
	static inline const FShaderParametersMetadata* GetRootParametersMetadata() { return FParameters::FTypeInfo::GetStructMetadata(); }

#define SHADER_USE_PARAMETER_STRUCT_INTERNAL(ShaderClass, ShaderParentClass, bShouldBindEverything) \
	ShaderClass(const ShaderMetaType::CompiledShaderInitializerType& Initializer) \
		: ShaderParentClass(Initializer) \
	{ \
		BindForLegacyShaderParameters<FParameters>(this, Initializer.PermutationId, Initializer.ParameterMap, bShouldBindEverything); \
	} \
	\
	ShaderClass() \
	{ } \

让我们来看一下BindForLegacyShaderParameters接口!调用传入的模板类实参是FParameters。

template <typename FParameterStruct>
void BindForLegacyShaderParameters(FShader* Shader, int32 PermutationId, const FShaderParameterMap& ParameterMap, bool bShouldBindEverything = false)
{
	Shader->Bindings.BindForLegacyShaderParameters(Shader, PermutationId, ParameterMap, *FParameterStruct::FTypeInfo::GetStructMetadata(), bShouldBindEverything);
}

调用了FShaderParameterBindings::BindForLegacyShaderParameters函数!

FShaderParametersMetadata的数据,记录所有着色器参数绑定及其相应的偏移量和大小。

Global UniformBuffer相关宏

上述介绍了一些基础的宏,这些构成着色器参数定义的基础。

现在,我们介绍一些如何声明Global UniformBuffer。

配套宏使用方式如下:

/** Begins & ends a shader global parameter structure.
*
* Example:
*     // header
*     BEGIN_UNIFORM_BUFFER_STRUCT(FMyParameterStruct, RENDERER_API)
*     END_UNIFORM_BUFFER_STRUCT()
*
*     // C++ file
*     IMPLEMENT_UNIFORM_BUFFER_STRUCT(FMyParameterStruct, "MyShaderBindingName");
*/

BEGIN_UNIFORM_BUFFER_STRUCT宏

#define BEGIN_UNIFORM_BUFFER_STRUCT(StructTypeName, PrefixKeywords) \
	INTERNAL_SHADER_PARAMETER_STRUCT_BEGIN(StructTypeName,PrefixKeywords,{} INTERNAL_BEGIN_UNIFORM_BUFFER_STRUCT, INTERNAL_UNIFORM_BUFFER_STRUCT_GET_STRUCT_METADATA(StructTypeName), INTERNAL_UNIFORM_BUFFER_STRUCT_CREATE_UNIFORM_BUFFER)

// INTERNAL_SHADER_PARAMETER_STRUCT_BEGIN宏这个宏前面分析过!
/*
*	参数1:StructTypeName
*	参数2:PrefixKeywords
*	参数3:{} INTERNAL_BEGIN_UNIFORM_BUFFER_STRUCT
*	参数4:INTERNAL_UNIFORM_BUFFER_STRUCT_GET_STRUCT_METADATA(StructTypeName)
*	参数5:INTERNAL_UNIFORM_BUFFER_STRUCT_CREATE_UNIFORM_BUFFER
*/
#define INTERNAL_SHADER_PARAMETER_STRUCT_BEGIN(StructTypeName,PrefixKeywords,ConstructorSuffix,GetStructMetadataScope,CreateUniformBufferImpl) \

// 参数3:相当于直接在类中定义了一个成员变量!
#define INTERNAL_BEGIN_UNIFORM_BUFFER_STRUCT \
	static FShaderParametersMetadata StaticStructMetadata;

// 参数4:直接返回了静态成员
#define INTERNAL_UNIFORM_BUFFER_STRUCT_GET_STRUCT_METADATA(StructTypeName) \
	return &StructTypeName::StaticStructMetadata;

// 参数5:返回创建函数
#define INTERNAL_UNIFORM_BUFFER_STRUCT_CREATE_UNIFORM_BUFFER return RHICreateUniformBuffer(&InContents, StaticStructMetadata.GetLayout(), InUsage);

将所有的宏进行整合,得到这样的结果!

class StructTypeName
{ 
public: 
	StructTypeName () {} 
	// 定义了一个静态成员变量!
	static FShaderParametersMetadata StaticStructMetadata;
	struct FTypeInfo 
	{
		static constexpr int32 NumRows = 1;
		static constexpr int32 NumColumns = 1;
		static constexpr int32 NumElements = 0;
		static constexpr int32 Alignment = SHADER_PARAMETER_STRUCT_ALIGNMENT;
		static constexpr bool bIsStoredInConstantBuffer = true;
		using TAlignedType = StructTypeName;
		static inline const FShaderParametersMetadata* GetStructMetadata() 
		{
			// 直接静态成员!
			return &StructTypeName::StaticStructMetadata; 
		} 
	}; 
	
	static FUniformBufferRHIRef CreateUniformBuffer(const StructTypeName& InContents, EUniformBufferUsage InUsage)
	{ 
		// 调用RHICreateUniformBuffer
		return RHICreateUniformBuffer(&InContents, StaticStructMetadata.GetLayout(), InUsage);
	} 
private:
	typedef StructTypeName zzTThisStruct;
	struct zzFirstMemberId { enum { HasDeclaredResource = 0 }; };
	typedef void* zzFuncPtr; 
	typedef zzFuncPtr(*zzMemberFunc)(zzFirstMemberId, TArray<FShaderParametersMetadata::FMember>*); 
	static zzFuncPtr zzAppendMemberGetPrev(zzFirstMemberId, TArray<FShaderParametersMetadata::FMember>*) 
	{
		return nullptr;
	}
	typedef zzFirstMemberId

END_UNIFORM_BUFFER_STRUCT宏

  • END_UNIFORM_BUFFER_STRUCT和END_SHADER_PARAMETER_STRUCT完全一致,不再分析。
#define END_UNIFORM_BUFFER_STRUCT() \
	END_SHADER_PARAMETER_STRUCT()

IMPLEMENT_UNIFORM_BUFFER_STRUCT宏

  • 相当于直接在类外对静态成员StaticStructMetadata进行初始化!
  • 可以发现这里传递的EUseCase参数为EUseCase::UniformBuffer。这是BEGIN_SHADER_PARAMETER_STRUCT使用EUseCase::ShaderParameterStruct不同的地方!
#define IMPLEMENT_UNIFORM_BUFFER_STRUCT(StructTypeName,ShaderVariableName) \
	FShaderParametersMetadata StructTypeName::StaticStructMetadata( \
	FShaderParametersMetadata::EUseCase::UniformBuffer, \
	TEXT(#StructTypeName), \
	TEXT(#StructTypeName), \
	TEXT(ShaderVariableName), \
	nullptr, \
	sizeof(StructTypeName), \
	StructTypeName::zzGetMembers())

前面在FShaderParametersMetadata的构造函数也分析过了:

  • 对于EUseCase::UniformBuffer用途的,通过静态变量的初始化,会将其注册到到全局列表中(Register this uniform buffer struct in global list)。
  • 这意味着,Unreal会自动帮你生成相关的HLSL代码,而无需自己手动添加!

备注:一些旧的宏定义也是新的宏定义进行实现:

/** Legacy macro definitions. */
#define BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT \
        BEGIN_UNIFORM_BUFFER_STRUCT
#define BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT_WITH_CONSTRUCTOR \
       BEGIN_UNIFORM_BUFFER_STRUCT_WITH_CONSTRUCTOR
#define END_GLOBAL_SHADER_PARAMETER_STRUCT \
       END_UNIFORM_BUFFER_STRUCT
#define IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT \
       IMPLEMENT_UNIFORM_BUFFER_STRUCT
#define IMPLEMENT_GLOBAL_SHADER_PARAMETER_ALIAS_STRUCT \
       IMPLEMENT_UNIFORM_BUFFER_ALIAS_STRUCT

上述讲到的配套宏定义方式,产生的是全局的着色器参数结构(global parameter structure)。

下面还有一组配套宏定义的方式,实现的是绑定到一个静态插槽的UniformBuffer。

/** Implements a uniform buffer tied to a static binding slot. The third parameter is the name of the slot.
 *  Multiple uniform buffers can be associated to a slot; only one uniform buffer can be bound to a slot at one time.
 *
 * Example:
 *	BEGIN_UNIFORM_BUFFER_STRUCT(FMyParameterStruct, RENDERER_API)
 *	END_UNIFORM_BUFFER_STRUCT()
 *
 *	// C++ file
 *
 *	// Define uniform buffer slot.
 *	IMPLEMENT_STATIC_UNIFORM_BUFFER_SLOT(MySlot)
 *
 *	// Associate uniform buffer with slot.
 *	IMPLEMENT_STATIC_UNIFORM_BUFFER_STRUCT(FMyParameterStruct, "MyShaderBindingName", MySlot);
 */

其中,BEGIN_UNIFORM_BUFFER_STRUCT和END_UNIFORM_BUFFER_STRUCT这两个宏在前面已经介绍过了。

让我们来看一下另外两个宏。

全局搜索了下发现了一处使用这个的定义

IMPLEMENT_STATIC_UNIFORM_BUFFER_SLOT(SceneTextures);
IMPLEMENT_STATIC_UNIFORM_BUFFER_STRUCT(FSceneTextureUniformParameters, "SceneTexturesStruct", SceneTextures);

IMPLEMENT_STATIC_UNIFORM_BUFFER_SLOT

该宏的参数为:SlotName,插槽名称。

定义了一个全局变量实例,类型为FUniformBufferStaticSlotRegistrar

// 示例
IMPLEMENT_STATIC_UNIFORM_BUFFER_SLOT(SceneTextures);

/** Implements a uniform buffer static binding slot. */
#define IMPLEMENT_STATIC_UNIFORM_BUFFER_SLOT(SlotName) \
	static FUniformBufferStaticSlotRegistrar UniformBufferStaticSlot_##SlotName(TEXT(#SlotName));

// 等价于:如果SlotName为SceneTextures
static FUniformBufferStaticSlotRegistrar UniformBufferStaticSlot_SceneTextures(TEXT("SceneTextures"));

/** Simple class that registers a uniform buffer static slot in the constructor. */
class RENDERCORE_API FUniformBufferStaticSlotRegistrar
{
public:
	FUniformBufferStaticSlotRegistrar(const TCHAR* InName);
};

FUniformBufferStaticSlotRegistrar的构造函数如下所示:

  • FUniformBufferStaticSlotRegistry类用于记录注册的静态slot。
FUniformBufferStaticSlotRegistrar::FUniformBufferStaticSlotRegistrar(const TCHAR* InName)
{
	FUniformBufferStaticSlotRegistry::Get().RegisterSlot(InName);
}

/** Simple class that registers a uniform buffer static slot in the constructor. */
class RENDERCORE_API FUniformBufferStaticSlotRegistrar
{
public:
	FUniformBufferStaticSlotRegistrar(const TCHAR* InName);
};

/** Registry for uniform buffer static slots. */
class RENDERCORE_API FUniformBufferStaticSlotRegistry
{
public:
	static FUniformBufferStaticSlotRegistry& Get();

	void RegisterSlot(FName SlotName);

    // Slot数目接口
	inline int32 GetSlotCount() const
	{
		return SlotNames.Num();
	}

	inline FString GetDebugDescription(FUniformBufferStaticSlot Slot) const
	{
		return FString::Printf(TEXT("[Name: %s, Slot: %u]"), *GetSlotName(Slot).ToString(), Slot);
	}

	inline FName GetSlotName(FUniformBufferStaticSlot Slot) const
	{
		checkf(Slot < SlotNames.Num(), TEXT("Requesting name for an invalid slot: %u."), Slot);
		return SlotNames[Slot];
	}

	inline FUniformBufferStaticSlot FindSlotByName(FName SlotName) const
	{
		// Brute force linear search. The search space is small and the find operation should not be critical path.
		for (int32 Index = 0; Index < SlotNames.Num(); ++Index)
		{
			if (SlotNames[Index] == SlotName)
			{
				return FUniformBufferStaticSlot(Index);
			}
		}
		return MAX_UNIFORM_BUFFER_STATIC_SLOTS;
	}

private:
    // 存储了所有的SlotNames
	TArray<FName> SlotNames;
};

// 注册函数SlotName的实现
void FUniformBufferStaticSlotRegistry::RegisterSlot(FName SlotName)
{
	// Multiple definitions with the same name resolve to the same slot.
	const FUniformBufferStaticSlot Slot = FindSlotByName(SlotName);

	if (!IsUniformBufferStaticSlotValid(Slot))
	{
		SlotNames.Emplace(SlotName);
	}
}

这里笔者又查询了一下GetSlotCount接口的调用,可以发现它被各个RHI层调用,如下D3D12的使用:

// 在全局UniformBuffer数组中又开辟了对应数量的空间。
GlobalUniformBuffers.AddZeroed(FUniformBufferStaticSlotRegistry::Get().GetSlotCount());

在这里插入图片描述

IMPLEMENT_STATIC_UNIFORM_BUFFER_STRUCT

宏的输入参数如下:

  • 参数1:即为通过BEGIN和END包围定义的结构体名StructTypeName;
  • 参数2:着色器中变量名;
  • 参数3:静态的插槽名;

可以发现这个宏其实也是对静态FShaderParametersMetadata类型变量进行构造。

和IMPLEMENT_UNIFORM_BUFFER_STRUCT宏仅存在一点差别StaticSlotName,用于绑定到固定的插槽。

// 示例
IMPLEMENT_STATIC_UNIFORM_BUFFER_STRUCT(FMyParameterStruct, "MyShaderBindingName", MySlot);

#define IMPLEMENT_STATIC_UNIFORM_BUFFER_STRUCT(StructTypeName,ShaderVariableName,StaticSlotName) \
	FShaderParametersMetadata StructTypeName::StaticStructMetadata( \
	FShaderParametersMetadata::EUseCase::UniformBuffer, \
	TEXT(#StructTypeName), \
	TEXT(#StructTypeName), \
	TEXT(ShaderVariableName), \
	/** 和IMPLEMENT_UNIFORM_BUFFER_STRUCT的差别在于输入为StaticSlotName,而非空*/
	TEXT(#StaticSlotName), \
	sizeof(StructTypeName), \
	StructTypeName::zzGetMembers())

小结:

4.2.1 主要介绍了相关用于声明定义着色器参数的宏。宏的数量较多,需要进行分类,且宏为配套组合使用。

4.2.2 和 4.2.3 将分别介绍如何对全局着色器和材质着色器的参数进行创建赋值和上传

4.2.2 全局着色器的参数

// TODO

4.2.3 材质着色器的参数

// TODO

参考博文

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一些关于如何利用multiagent的rendering写一个三维坐标的窗口的指南。 首先,您需要安装并导入用于multiagent rendering的Python库。其中一个常用的库是`pygame`,它可以用于创建和渲染游戏,包括三维坐标的窗口。 接下来,您需要定义一个场景,以及该场景中的代理/实体。您可以为每个代理/实体分配一个独特的颜色和三维坐标。然后,您可以使用`pygame`库的3D绘图功能来在窗口中渲染这些代理/实体。 以下是一些示例代码,可以帮助您开始编写一个三维坐标窗口的multiagent rendering程序: ``` import pygame from pygame.locals import * from OpenGL.GL import * from OpenGL.GLU import * # 定义场景中的代理/实体 agents = [{'position': (0, 0, 0), 'color': (1, 0, 0)}, {'position': (1, 1, 1), 'color': (0, 1, 0)}, {'position': (2, 2, 2), 'color': (0, 0, 1)}] def draw_box(position, color): # 定义绘制一个方块的函数 x, y, z = position r, g, b = color glBegin(GL_QUADS) glColor3f(r, g, b) glVertex3f(x-0.5, y-0.5, z+0.5) glVertex3f(x+0.5, y-0.5, z+0.5) glVertex3f(x+0.5, y+0.5, z+0.5) glVertex3f(x-0.5, y+0.5, z+0.5) glEnd() def render(): # 渲染场景 glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) for agent in agents: draw_box(agent['position'], agent['color']) pygame.display.flip() def main(): # 初始化窗口 pygame.init() display = (800, 600) pygame.display.set_mode(display, DOUBLEBUF|OPENGL) # 设置3D视角 gluPerspective(45, (display[0]/display[1]), 0.1, 50.0) glTranslatef(0.0, 0.0, -5) # 渲染循环 while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() render() if __name__ == '__main__': main() ``` 希望这可以帮助您开始编写一个multiagent rendering程序,用于创建三维坐标窗口。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值