【从0开始的Unreal图形编程】GlobalShader参数的三种写法

上篇文章有些没处理好,里面的Shader类小编多继承了一个FMyShaderBase,不过摆烂了不想改了。


茴字的第一种写法

一个最普通的GlobalShader定义如下

////////////////////////////////////////////////////////////
// XXX.h
////////////////////////////////////////////////////////////
class FShader_VS : public FGlobalShader {
    DECLARE_GLOBAL_SHADER(FShader_VS);
public:
    FShader_VS() {}
    FShader_VS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
        : FGlobalShader(Initializer) {       
    }
};

class FShader_PS : public FGlobalShader {
    DECLARE_GLOBAL_SHADER(FShader_PS);
public:
    FShader_PS() {}
    FShader_PS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
        :  FGlobalShader(Initializer) {
        MainColorVal.Bind(Initializer.ParameterMap, TEXT("MainColor"));
    }
    void SetParameters(FRHICommandList& RHICmdList,
                       const FLinearColor& MyColor) {
        SetShaderValue(RHICmdList, RHICmdList.GetBoundPixelShader(),
                       MainColorVal, MyColor);
    }
private:
    LAYOUT_FIELD(FShaderParameter, MainColorVal);
};

////////////////////////////////////////////////////////////
// XXX.cpp
////////////////////////////////////////////////////////////
IMPLEMENT_SHADER_TYPE(, FShader_VS,
                      TEXT("/GlobalShaderPlug/MyGlobalShader.usf"),
                      TEXT("MainVS"), SF_Vertex)
IMPLEMENT_SHADER_TYPE(, FShader_PS,
                      TEXT("/GlobalShaderPlug/MyGlobalShader.usf"),
                      TEXT("MainPS"), SF_Pixel)
					  
////////////////////////////////////////////////////////////
// MyGlobalShader.usf
////////////////////////////////////////////////////////////
#include "/Engine/Public/Platform.ush"
float4 MainColor;
void MainVS(
in float4 InPosition : ATTRIBUTE0,
out float4 OutPosition : SV_POSITION)
{
    OutPosition = InPosition;
}

void MainPS(out float4 OutColor : SV_Target0)
{
    OutColor = MainColor;
}

通过MainColorVal.Bind(Initializer.ParameterMap, TEXT("MainColor"));绑定参数(挖个坑,后续再分析内部到底是如何绑定的)

茴字的第二种写法

第二种方法是利用SHADER_USE_PARAMETER_STRUCT,BEGIN_SHADER_PARAMETER_STRUCT,不过在文档中写道Notes: Long term, this macro will no longer be needed. Instead, parameter binding will become the default behavior for shader declarations,不过至少现在,引擎中还存在997Shader使用这种方法。
下面给出源码例子

class FRasterizeToRectsVS : public FGlobalShader
{
	DECLARE_EXPORTED_SHADER_TYPE(FRasterizeToRectsVS, Global, RENDERCORE_API);
	SHADER_USE_PARAMETER_STRUCT(FRasterizeToRectsVS, FGlobalShader);

	BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
		SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint4>, RectCoordBuffer)
		SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float4>, RectUVBuffer)
		SHADER_PARAMETER(FVector2f, InvViewSize)
		SHADER_PARAMETER(FVector2f, InvTextureSize)
		SHADER_PARAMETER(float, DownsampleFactor)
		SHADER_PARAMETER(uint32, NumRects)
	END_SHADER_PARAMETER_STRUCT()

	class FRectUV : SHADER_PERMUTATION_BOOL("RECT_UV");
	using FPermutationDomain = TShaderPermutationDomain<FRectUV>;

	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters);
};
	
// 对应Shader
Buffer<uint4> RectCoordBuffer; // [MinX, MinY, MaxX, MaxY]
Buffer<uint4> RectUVBuffer;

float DownsampleFactor;
float2 InvViewSize;
float2 InvTextureSize;

bool2 VertMax(uint VertexId)
{
	bool2 bVertMax;
	bVertMax.x = VertexId == 1 || VertexId == 2 || VertexId == 4;
	bVertMax.y = VertexId == 2 || VertexId == 4 || VertexId == 5;
	return bVertMax;
}

void RasterizeToRectsVS(
	in uint InstanceId : SV_InstanceID,
	in uint VertexId : SV_VertexID,
	out float4 OutPosition : SV_POSITION,
	out float2 OutUV : TEXCOORD0,
	out float2 OutRectUV : TEXCOORD1,
	out float OutRectIndex : RECT_INDEX)
{
	uint4 RectCoord = RectCoordBuffer[InstanceId] * DownsampleFactor;
	uint2 VertexCoord = VertMax(VertexId) ? RectCoord.zw : RectCoord.xy;

	float4 RectUV = RectCoord * InvViewSize.xyxy;
	#if RECT_UV
	{
		RectUV = RectUVBuffer[InstanceId] * DownsampleFactor * InvTextureSize.xyxy;
	}
	#endif
	float2 VertexUV = VertMax(VertexId) ? RectUV.zw : RectUV.xy;

	OutPosition = float4(float2(VertexCoord) * InvViewSize * float2(2.0f, -2.0f) + float2(-1.0, 1.0f), 0.0f, 1.0f);
	OutUV = VertexUV;
	OutRectUV.x = VertMax(VertexId).x ? 1.0f : 0.0f;
	OutRectUV.y = VertMax(VertexId).y ? 1.0f : 0.0f;
	OutRectIndex = InstanceId;
}

关于SHADER_USE_PARAMETER_STRUCT(FRasterizeToRectsVS, FGlobalShader);的展开如下,

	FRasterizeToRectsVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
		: FGlobalShader(Initializer)
	{
		BindForLegacyShaderParameters<FParameters>(this, Initializer.PermutationId, Initializer.ParameterMap, true);
	}
	FRasterizeToRectsVS() { }
	
	// 这个是配合下面BEGIN_SHADER_PARAMETER_STRUCT和END_SHADER_PARAMETER_STRUCT为传参服务的
	static inline const FShaderParametersMetadata* GetRootParametersMetadata()
	{
		return FParameters::FTypeInfo::GetStructMetadata();
	};

可以看到,实际上就是生成了我们自己写的那几个构造函数,也就是说,我们就不用自己写构造函数啦
然后终于到了BEGIN_SHADER_PARAMETER_STRUCT这里了,大概展开就是一个带反射信息的结构体

__declspec(align(16))
class FParameters {
public:
    FParameters() {}
    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;
        static constexpr const ANSICHAR* const FileName = "PixelShaderUtils.h";
        static constexpr int32 FileLine                 = 34;
        using TAlignedType                              = FParameters;
        static inline const FShaderParametersMetadata* GetStructMetadata() {
            static FShaderParametersMetadata StaticStructMetadata(
                FShaderParametersMetadata::EUseCase::ShaderParameterStruct,
                EUniformBufferBindingFlags::Shader, L"FParameters",
                L"FParameters", nullptr, nullptr, FTypeInfo::FileName,
                FTypeInfo::FileLine, sizeof(FParameters),
                FParameters::zzGetMembers());
            return &StaticStructMetadata;
        }
    };
	
    // 挖坑,以后讲
    static FUniformBufferRHIRef CreateUniformBuffer(const FParameters& InContents, EUniformBufferUsage InUsage) {
        return nullptr;
    }

private:
    typedef FParameters 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 
        SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint4>,RectCoordBuffer)
        SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float4>, RectUVBuffer)
        SHADER_PARAMETER(FVector2f, InvViewSize)
        SHADER_PARAMETER(FVector2f, InvTextureSize)
        SHADER_PARAMETER(float, DownsampleFactor)
        SHADER_PARAMETER(uint32, NumRects) 
    zzLastMemberId;
    // 结束声明结构体成员变量

public:
    static TArray<FShaderParametersMetadata::FMember> zzGetMembers() {
        TArray<FShaderParametersMetadata::FMember> Members;
        zzFuncPtr (*LastFunc)(zzLastMemberId,
                              TArray<FShaderParametersMetadata::FMember>*);
        LastFunc      = zzAppendMemberGetPrev;
        zzFuncPtr Ptr = (zzFuncPtr)LastFunc;
        do {
            Ptr = reinterpret_cast<zzMemberFunc>(Ptr)(zzFirstMemberId(),
                                                      &Members);
        } while (Ptr);
        Algo::Reverse(Members);
        return Members;
    }
};

可以看到,第一个public里面只是声明了一个FTypeInfo作为反射的metadata,这个里面最有用的是通过FParameters::zzGetMembers()传入了这个struct的所有成员变量到metadata里面,然后SHADER_PARAMETER的展开实际上就是利用typedef不断的对zzAppendMemberGetPrev函数进行重载,最终形成一个函数链表,比较有趣的反射思路

点击SHADER_PARAMETER代码
typedef zzFirstMemberId zzMemberIdRectCoordBuffer;

public:
    TShaderResourceParameterTypeInfo<FRDGBufferSRV*>::TAlignedType
        RectCoordBuffer = nullptr;
    static_assert(UBMT_RDG_BUFFER_SRV != UBMT_INVALID,
                  "Invalid type "
                  "FRDGBufferSRV*"
                  " of member "
                  "RectCoordBuffer"
                  ".");

private:
    struct zzNextMemberIdRectCoordBuffer {
        enum {
            HasDeclaredResource =
                zzMemberIdRectCoordBuffer::HasDeclaredResource ||
                !TShaderResourceParameterTypeInfo<
                    FRDGBufferSRV*>::bIsStoredInConstantBuffer
        };
    };
    static zzFuncPtr zzAppendMemberGetPrev(
        zzNextMemberIdRectCoordBuffer,
        TArray<FShaderParametersMetadata::FMember>* Members) {
        static_assert(
            TShaderResourceParameterTypeInfo<
                FRDGBufferSRV*>::bIsStoredInConstantBuffer ||
                TIsArrayOrRefOfType<decltype(L"Buffer<uint4>"), TCHAR>::Value,
            "No shader type for "
            "RectCoordBuffer"
            ".");
        static_assert(
            (((::size_t) & reinterpret_cast<char const volatile&>(
                               (((zzTThisStruct*)0)->RectCoordBuffer))) &
             (TShaderResourceParameterTypeInfo<FRDGBufferSRV*>::Alignment -
              1)) == 0,
            "Misaligned uniform buffer struct member "
            "RectCoordBuffer"
            ".");
        Members->Add(FShaderParametersMetadata::FMember(
            L"RectCoordBuffer", L"Buffer<uint4>", 35,
            ((::size_t) & reinterpret_cast<char const volatile&>(
                              (((zzTThisStruct*)0)->RectCoordBuffer))),
            EUniformBufferBaseType(UBMT_RDG_BUFFER_SRV),
            EShaderPrecisionModifier::Float,
            TShaderResourceParameterTypeInfo<FRDGBufferSRV*>::NumRows,
            TShaderResourceParameterTypeInfo<FRDGBufferSRV*>::NumColumns,
            TShaderResourceParameterTypeInfo<FRDGBufferSRV*>::NumElements,
            TShaderResourceParameterTypeInfo<
                FRDGBufferSRV*>::GetStructMetadata()));
        zzFuncPtr (*PrevFunc)(zzMemberIdRectCoordBuffer,
                              TArray<FShaderParametersMetadata::FMember>*);
        PrevFunc = zzAppendMemberGetPrev;
        return (zzFuncPtr)PrevFunc;
    }
    typedef zzNextMemberIdRectCoordBuffer zzMemberIdRectUVBuffer;

public:
    TShaderResourceParameterTypeInfo<FRDGBufferSRV*>::TAlignedType
        RectUVBuffer = nullptr;
    static_assert(UBMT_RDG_BUFFER_SRV != UBMT_INVALID,
                  "Invalid type "
                  "FRDGBufferSRV*"
                  " of member "
                  "RectUVBuffer"
                  ".");

private:
    struct zzNextMemberIdRectUVBuffer {
        enum {
            HasDeclaredResource = zzMemberIdRectUVBuffer::HasDeclaredResource ||
                                  !TShaderResourceParameterTypeInfo<
                                      FRDGBufferSRV*>::bIsStoredInConstantBuffer
        };
    };
    static zzFuncPtr zzAppendMemberGetPrev(
        zzNextMemberIdRectUVBuffer,
        TArray<FShaderParametersMetadata::FMember>* Members) {
        static_assert(
            TShaderResourceParameterTypeInfo<
                FRDGBufferSRV*>::bIsStoredInConstantBuffer ||
                TIsArrayOrRefOfType<decltype(L"Buffer<float4>"), TCHAR>::Value,
            "No shader type for "
            "RectUVBuffer"
            ".");
        static_assert(
            (((::size_t) & reinterpret_cast<char const volatile&>(
                               (((zzTThisStruct*)0)->RectUVBuffer))) &
             (TShaderResourceParameterTypeInfo<FRDGBufferSRV*>::Alignment -
              1)) == 0,
            "Misaligned uniform buffer struct member "
            "RectUVBuffer"
            ".");
        Members->Add(FShaderParametersMetadata::FMember(
            L"RectUVBuffer", L"Buffer<float4>", 36,
            ((::size_t) & reinterpret_cast<char const volatile&>(
                              (((zzTThisStruct*)0)->RectUVBuffer))),
            EUniformBufferBaseType(UBMT_RDG_BUFFER_SRV),
            EShaderPrecisionModifier::Float,
            TShaderResourceParameterTypeInfo<FRDGBufferSRV*>::NumRows,
            TShaderResourceParameterTypeInfo<FRDGBufferSRV*>::NumColumns,
            TShaderResourceParameterTypeInfo<FRDGBufferSRV*>::NumElements,
            TShaderResourceParameterTypeInfo<
                FRDGBufferSRV*>::GetStructMetadata()));
        zzFuncPtr (*PrevFunc)(zzMemberIdRectUVBuffer,
                              TArray<FShaderParametersMetadata::FMember>*);
        PrevFunc = zzAppendMemberGetPrev;
        return (zzFuncPtr)PrevFunc;
    }
    typedef zzNextMemberIdRectUVBuffer zzMemberIdInvViewSize;

public:
    TShaderParameterTypeInfo<FVector2f>::TAlignedType InvViewSize;
    static_assert(TShaderParameterTypeInfo<FVector2f>::BaseType != UBMT_INVALID,
                  "Invalid type "
                  "FVector2f"
                  " of member "
                  "InvViewSize"
                  ".");

private:
    struct zzNextMemberIdInvViewSize {
        enum {
            HasDeclaredResource =
                zzMemberIdInvViewSize::HasDeclaredResource ||
                !TShaderParameterTypeInfo<FVector2f>::bIsStoredInConstantBuffer
        };
    };
    static zzFuncPtr zzAppendMemberGetPrev(
        zzNextMemberIdInvViewSize,
        TArray<FShaderParametersMetadata::FMember>* Members) {
        static_assert(
            TShaderParameterTypeInfo<FVector2f>::bIsStoredInConstantBuffer ||
                TIsArrayOrRefOfType<decltype(L""), TCHAR>::Value,
            "No shader type for "
            "InvViewSize"
            ".");
        static_assert(
            (((::size_t) & reinterpret_cast<char const volatile&>(
                               (((zzTThisStruct*)0)->InvViewSize))) &
             (TShaderParameterTypeInfo<FVector2f>::Alignment - 1)) == 0,
            "Misaligned uniform buffer struct member "
            "InvViewSize"
            ".");
        Members->Add(FShaderParametersMetadata::FMember(
            L"InvViewSize", L"", 37,
            ((::size_t) & reinterpret_cast<char const volatile&>(
                              (((zzTThisStruct*)0)->InvViewSize))),
            EUniformBufferBaseType(
                TShaderParameterTypeInfo<FVector2f>::BaseType),
            EShaderPrecisionModifier::Float,
            TShaderParameterTypeInfo<FVector2f>::NumRows,
            TShaderParameterTypeInfo<FVector2f>::NumColumns,
            TShaderParameterTypeInfo<FVector2f>::NumElements,
            TShaderParameterTypeInfo<FVector2f>::GetStructMetadata()));
        zzFuncPtr (*PrevFunc)(zzMemberIdInvViewSize,
                              TArray<FShaderParametersMetadata::FMember>*);
        PrevFunc = zzAppendMemberGetPrev;
        return (zzFuncPtr)PrevFunc;
    }
    typedef zzNextMemberIdInvViewSize zzMemberIdInvTextureSize;

public:
    TShaderParameterTypeInfo<FVector2f>::TAlignedType InvTextureSize;
    static_assert(TShaderParameterTypeInfo<FVector2f>::BaseType != UBMT_INVALID,
                  "Invalid type "
                  "FVector2f"
                  " of member "
                  "InvTextureSize"
                  ".");

private:
    struct zzNextMemberIdInvTextureSize {
        enum {
            HasDeclaredResource =
                zzMemberIdInvTextureSize::HasDeclaredResource ||
                !TShaderParameterTypeInfo<FVector2f>::bIsStoredInConstantBuffer
        };
    };
    static zzFuncPtr zzAppendMemberGetPrev(
        zzNextMemberIdInvTextureSize,
        TArray<FShaderParametersMetadata::FMember>* Members) {
        static_assert(
            TShaderParameterTypeInfo<FVector2f>::bIsStoredInConstantBuffer ||
                TIsArrayOrRefOfType<decltype(L""), TCHAR>::Value,
            "No shader type for "
            "InvTextureSize"
            ".");
        static_assert(
            (((::size_t) & reinterpret_cast<char const volatile&>(
                               (((zzTThisStruct*)0)->InvTextureSize))) &
             (TShaderParameterTypeInfo<FVector2f>::Alignment - 1)) == 0,
            "Misaligned uniform buffer struct member "
            "InvTextureSize"
            ".");
        Members->Add(FShaderParametersMetadata::FMember(
            L"InvTextureSize", L"", 38,
            ((::size_t) & reinterpret_cast<char const volatile&>(
                              (((zzTThisStruct*)0)->InvTextureSize))),
            EUniformBufferBaseType(
                TShaderParameterTypeInfo<FVector2f>::BaseType),
            EShaderPrecisionModifier::Float,
            TShaderParameterTypeInfo<FVector2f>::NumRows,
            TShaderParameterTypeInfo<FVector2f>::NumColumns,
            TShaderParameterTypeInfo<FVector2f>::NumElements,
            TShaderParameterTypeInfo<FVector2f>::GetStructMetadata()));
        zzFuncPtr (*PrevFunc)(zzMemberIdInvTextureSize,
                              TArray<FShaderParametersMetadata::FMember>*);
        PrevFunc = zzAppendMemberGetPrev;
        return (zzFuncPtr)PrevFunc;
    }
    typedef zzNextMemberIdInvTextureSize zzMemberIdDownsampleFactor;

public:
    TShaderParameterTypeInfo<float>::TAlignedType DownsampleFactor;
    static_assert(TShaderParameterTypeInfo<float>::BaseType != UBMT_INVALID,
                  "Invalid type "
                  "float"
                  " of member "
                  "DownsampleFactor"
                  ".");

private:
    struct zzNextMemberIdDownsampleFactor {
        enum {
            HasDeclaredResource =
                zzMemberIdDownsampleFactor::HasDeclaredResource ||
                !TShaderParameterTypeInfo<float>::bIsStoredInConstantBuffer
        };
    };
    static zzFuncPtr zzAppendMemberGetPrev(
        zzNextMemberIdDownsampleFactor,
        TArray<FShaderParametersMetadata::FMember>* Members) {
        static_assert(
            TShaderParameterTypeInfo<float>::bIsStoredInConstantBuffer ||
                TIsArrayOrRefOfType<decltype(L""), TCHAR>::Value,
            "No shader type for "
            "DownsampleFactor"
            ".");
        static_assert(
            (((::size_t) & reinterpret_cast<char const volatile&>(
                               (((zzTThisStruct*)0)->DownsampleFactor))) &
             (TShaderParameterTypeInfo<float>::Alignment - 1)) == 0,
            "Misaligned uniform buffer struct member "
            "DownsampleFactor"
            ".");
        Members->Add(FShaderParametersMetadata::FMember(
            L"DownsampleFactor", L"", 39,
            ((::size_t) & reinterpret_cast<char const volatile&>(
                              (((zzTThisStruct*)0)->DownsampleFactor))),
            EUniformBufferBaseType(TShaderParameterTypeInfo<float>::BaseType),
            EShaderPrecisionModifier::Float,
            TShaderParameterTypeInfo<float>::NumRows,
            TShaderParameterTypeInfo<float>::NumColumns,
            TShaderParameterTypeInfo<float>::NumElements,
            TShaderParameterTypeInfo<float>::GetStructMetadata()));
        zzFuncPtr (*PrevFunc)(zzMemberIdDownsampleFactor,
                              TArray<FShaderParametersMetadata::FMember>*);
        PrevFunc = zzAppendMemberGetPrev;
        return (zzFuncPtr)PrevFunc;
    }
    typedef zzNextMemberIdDownsampleFactor zzMemberIdNumRects;

public:
    TShaderParameterTypeInfo<uint32>::TAlignedType NumRects;
    static_assert(TShaderParameterTypeInfo<uint32>::BaseType != UBMT_INVALID,
                  "Invalid type "
                  "uint32"
                  " of member "
                  "NumRects"
                  ".");

private:
    struct zzNextMemberIdNumRects {
        enum {
            HasDeclaredResource =
                zzMemberIdNumRects::HasDeclaredResource ||
                !TShaderParameterTypeInfo<uint32>::bIsStoredInConstantBuffer
        };
    };
    static zzFuncPtr zzAppendMemberGetPrev(
        zzNextMemberIdNumRects,
        TArray<FShaderParametersMetadata::FMember>* Members) {
        static_assert(
            TShaderParameterTypeInfo<uint32>::bIsStoredInConstantBuffer ||
                TIsArrayOrRefOfType<decltype(L""), TCHAR>::Value,
            "No shader type for "
            "NumRects"
            ".");
        static_assert((((::size_t) & reinterpret_cast<char const volatile&>(
                                         (((zzTThisStruct*)0)->NumRects))) &
                       (TShaderParameterTypeInfo<uint32>::Alignment - 1)) == 0,
                      "Misaligned uniform buffer struct member "
                      "NumRects"
                      ".");
        Members->Add(FShaderParametersMetadata::FMember(
            L"NumRects", L"", 40,
            ((::size_t) & reinterpret_cast<char const volatile&>(
                              (((zzTThisStruct*)0)->NumRects))),
            EUniformBufferBaseType(TShaderParameterTypeInfo<uint32>::BaseType),
            EShaderPrecisionModifier::Float,
            TShaderParameterTypeInfo<uint32>::NumRows,
            TShaderParameterTypeInfo<uint32>::NumColumns,
            TShaderParameterTypeInfo<uint32>::NumElements,
            TShaderParameterTypeInfo<uint32>::GetStructMetadata()));
        zzFuncPtr (*PrevFunc)(zzMemberIdNumRects,
                              TArray<FShaderParametersMetadata::FMember>*);
        PrevFunc = zzAppendMemberGetPrev;
        return (zzFuncPtr)PrevFunc;
    }
    typedef zzNextMemberIdNumRects zzLastMemberId;

至此,全部解析完毕,完后就是在外面如何进行调用,回顾第一种方法,我们实际上最终绑定到GPU上是通过调用这句话

image
而我们这里,实际上只有定义没有实例,所以我们需要在外面实例化这个变量,下面给出我的例子,源码里面稍微多了一层,不便于这里的理解

class FShader_PS : public FGlobalShader {
    DECLARE_GLOBAL_SHADER(FShader_PS);
    SHADER_USE_PARAMETER_STRUCT(FShader_PS,FGlobalShader);
    
    BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
        SHADER_PARAMETER(FVector4f,MainColor)
    END_SHADER_PARAMETER_STRUCT()
};

// 外部进行传参

// 第一种GlobalShader传参
// PixelShader->SetParameters(RHICmdList, MyColor);

// 当前GlobalShader传参
FShader_PS::FParameters Parameters;
Parameters.MainColor = MyColor;

SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(),Parameters);

BEGIN_SHADER_PARAMETER_STRUCT使用指南

通过上述展开,我们知道BEGIN_SHADER_PARAMETER_STRUCT在Shader内部使用时,为了配合第一个宏的展开,类型名称必须是FParameters,实际上这里面就可以衍生出这种操作

// NaniteEditor.h
BEGIN_SHADER_PARAMETER_STRUCT(FNaniteVisualizeLevelInstanceParameters, )
    SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
    SHADER_PARAMETER(FVector2f, OutputToInputScale)
    SHADER_PARAMETER(FVector2f, OutputToInputBias)
    SHADER_PARAMETER(uint32, MaxVisibleClusters)
    
    SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<FVisibleCluster>,
                                    VisibleClustersSWHW)
    SHADER_PARAMETER(FIntVector4, PageConstants)
    SHADER_PARAMETER_SRV(ByteAddressBuffer, ClusterPageData)
    
    SHADER_PARAMETER_RDG_TEXTURE(Texture2D<uint2>, VisBuffer64)
    SHADER_PARAMETER_RDG_TEXTURE(Texture2D<uint>, MaterialResolve)
    
    SHADER_PARAMETER_SRV(ByteAddressBuffer, MaterialHitProxyTable)
END_SHADER_PARAMETER_STRUCT()

// NaniteEditor.cpp
class FEmitEditingLevelInstanceDepthPS : public FNaniteGlobalShader {
public:
    DECLARE_GLOBAL_SHADER(FEmitEditingLevelInstanceDepthPS);
    SHADER_USE_PARAMETER_STRUCT(FEmitEditingLevelInstanceDepthPS,
                                FNaniteGlobalShader);

    using FParameters = FNaniteVisualizeLevelInstanceParameters;

    ......
};

可以看到,这个时候BEGIN_SHADER_PARAMETER_STRUCT的类型名称就不局限于FParameters,只需要在GlobalShader内部利用using FParameters = XXX;就OK了。这么做的好处在于

  • 代码复用,多个GlobalShader内部如果是同一套参数只需要编写一次就可以了
  • 类型名从XXX::FParameters变为XXX,更加清晰。
  • GlobalShader可以作为一个函数Implement定义在xx.cpp,并不需要写在xx.h中暴露出去
// NaniteEditor.h
BEGIN_SHADER_PARAMETER_STRUCT(FNaniteVisualizeLevelInstanceParameters, )
    SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
    SHADER_PARAMETER(FVector2f, OutputToInputScale)
    SHADER_PARAMETER(FVector2f, OutputToInputBias)
    SHADER_PARAMETER(uint32, MaxVisibleClusters)
    
    SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<FVisibleCluster>,
                                    VisibleClustersSWHW)
    SHADER_PARAMETER(FIntVector4, PageConstants)
    SHADER_PARAMETER_SRV(ByteAddressBuffer, ClusterPageData)
    
    SHADER_PARAMETER_RDG_TEXTURE(Texture2D<uint2>, VisBuffer64)
    SHADER_PARAMETER_RDG_TEXTURE(Texture2D<uint>, MaterialResolve)
    
    SHADER_PARAMETER_SRV(ByteAddressBuffer, MaterialHitProxyTable)
END_SHADER_PARAMETER_STRUCT()

void DrawEditorVisualizeLevelInstance(
    FRHICommandList& RHICmdList, const FViewInfo& View,
    const FIntRect ViewportRect,
    const FNaniteVisualizeLevelInstanceParameters& PassParameters);
	
// NaniteEditor.cpp
class FEmitEditingLevelInstanceDepthPS : public FNaniteGlobalShader {
public:
    DECLARE_GLOBAL_SHADER(FEmitEditingLevelInstanceDepthPS);
    SHADER_USE_PARAMETER_STRUCT(FEmitEditingLevelInstanceDepthPS,
                                FNaniteGlobalShader);

    using FParameters = FNaniteVisualizeLevelInstanceParameters;

    ......
};
IMPLEMENT_GLOBAL_SHADER(FEmitEditingLevelInstanceDepthPS,
                        "/Engine/Private/Nanite/NaniteExportGBuffer.usf",
                        "EmitEditorLevelInstanceDepthPS", SF_Pixel);

// 函数实现
void DrawEditorVisualizeLevelInstance(
    FRHICommandList& RHICmdList, const FViewInfo& View,
    const FIntRect ViewportRect,
    const FNaniteVisualizeLevelInstanceParameters& PassParameters) {
	
    ......
    auto PixelShader =
        View.ShaderMap->GetShader<FEmitEditingLevelInstanceDepthPS>(
            PermutationVector.ToDimensionValueId());

    ......
}

茴字的第三种写法

在第二种写法的额外使用情况中,实际上我们在并没有完全的消除代码复用的问题,原因是我们写的struct内部的参数,必须要和Shader中的对应,否则会发生编译报错问题。其次,我们以上面哪种方式编写Shader,实际上是位于编译器自动产生的一个Global constant buffer中,为了想像dx那样编写一个Custom constant buffer于是就有了BEGIN_UNIFORM_BUFFER_STRUCT,实际上,引擎内部还是使用过时的BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT不过本质就是

 /** Legacy macro definitions. */
#define BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT \ BEGIN_UNIFORM_BUFFER_STRUCT

让我们找个栗子看一看

/*------------------------------------------------------------------------------
        Uniform buffer for passing in radix sort parameters.
------------------------------------------------------------------------------*/

BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FRadixSortParameters, )
    SHADER_PARAMETER(uint32, RadixShift)
    SHADER_PARAMETER(uint32, TilesPerGroup)
    SHADER_PARAMETER(uint32, ExtraTileCount)
    SHADER_PARAMETER(uint32, ExtraKeyCount)
    SHADER_PARAMETER(uint32, GroupCount)
END_GLOBAL_SHADER_PARAMETER_STRUCT()

IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FRadixSortParameters, "RadixSortUB");

结构体的Begin展开得到(End部分内部调用的宏就是第二种写法中的End)

__declspec(align(16)) class FRadixSortParameters {
public:
    FRadixSortParameters() {}
	
    // 新增静态变量
    static FShaderParametersMetadata StaticStructMetadata
	
    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;
        static constexpr const ANSICHAR* const FileName = "GPUSort.cpp";
        static constexpr int32 FileLine                 = 45;
        using TAlignedType                              = FRadixSortParameters;
        static inline const FShaderParametersMetadata* GetStructMetadata() {
            return &FRadixSortParameters::StaticStructMetadata;
        }
    };
    static FUniformBufferRHIRef CreateUniformBuffer(
        const FRadixSortParameters& InContents, EUniformBufferUsage InUsage) {
        // 从return nullptr变为这个
        return RHICreateUniformBuffer(
            &InContents, StaticStructMetadata.GetLayoutPtr(), InUsage);
    }

private:
    typedef FRadixSortParameters 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

实际上可以看到就新增了声明一个静态变量StaticStructMetadata,并且需要从调用StaticStructMetadata.GetLayoutPtr()方法,那么这个变量在哪里实现呢?让我们展开IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FRadixSortParameters,"RadixSortUB");看看

FShaderParametersMetadata FRadixSortParameters::StaticStructMetadata(
    FShaderParametersMetadata::EUseCase::UniformBuffer,
    EUniformBufferBindingFlags::Shader, L"FRadixSortParameters",
    L"FRadixSortParameters", L"RadixSortUB", nullptr,
    FRadixSortParameters::FTypeInfo::FileName,
    FRadixSortParameters::FTypeInfo::FileLine, sizeof(FRadixSortParameters),
    FRadixSortParameters::zzGetMembers());

原来这个IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT就是实现静态变量而已,而这里L"RadixSortUB"传入的形参名称为InShaderVariableName可以大概猜出,这里应该就对应usf中所自动生成的constant buffer的名字(马后炮)。
有了这些元数据,Engine就会利用反射自动在形如下面的代码

cbuffer RadixSortUB : register(bx) {
    uint RadixShift;
    uint TilesPerGroup;
    uint ExtraTileCount
    uint ExtraKeyCount;
    uint GroupCount;
}

然后将代码插入Common.ush中,就不用自己手动在Shader中去写了,Good Job!
讲完了他结构体的定义,下面讲一下GlobalShader中的使用,下面给出源码实例

class FRadixSortUpsweepCS : public FGlobalShader
{
	DECLARE_SHADER_TYPE(FRadixSortUpsweepCS,Global);

public:
	/** Default constructor. */
	FRadixSortUpsweepCS()
	{
	}

	/** Initialization constructor. */
	explicit FRadixSortUpsweepCS( const ShaderMetaType::CompiledShaderInitializerType& Initializer )
		: FGlobalShader(Initializer)
	{
		RadixSortParameterBuffer.Bind( Initializer.ParameterMap, TEXT("RadixSortParameterBuffer") );
		InKeys.Bind( Initializer.ParameterMap, TEXT("InKeys") );
		OutOffsets.Bind( Initializer.ParameterMap, TEXT("OutOffsets") );
	}

	/**
	 * Set parameters for this shader.
	 */
	// 这里的FRadixSortUniformBufferRef实际上可以就是一个Ref,后面会讲
	// typedef TUniformBufferRef<FRadixSortParameters> FRadixSortUniformBufferRef
	void SetParameters(FRHICommandList& RHICmdList, FRHIShaderResourceView* InKeysSRV, FRadixSortUniformBufferRef& RadixSortUniformBuffer, FRHIShaderResourceView* RadixSortParameterBufferSRV)
	{
		FRHIComputeShader* ComputeShaderRHI = RHICmdList.GetBoundComputeShader();
		// 传入参数到GPU上
		SetUniformBufferParameter(RHICmdList, ComputeShaderRHI, GetUniformBufferParameter<FRadixSortParameters>(), RadixSortUniformBuffer );
		if ( InKeys.IsBound() )
		{
			RHICmdList.SetShaderResourceViewParameter(ComputeShaderRHI, InKeys.GetBaseIndex(), InKeysSRV);
		}
		if ( RadixSortParameterBuffer.IsBound() )
		{
			RHICmdList.SetShaderResourceViewParameter(ComputeShaderRHI, RadixSortParameterBuffer.GetBaseIndex(), RadixSortParameterBufferSRV);
		}
	}

	/**
	 * Set output buffer for this shader.
	 */
	void SetOutput(FRHICommandList& RHICmdList, FRHIUnorderedAccessView* OutOffsetsUAV)
	{
		FRHIComputeShader* ComputeShaderRHI = RHICmdList.GetBoundComputeShader();
		if ( OutOffsets.IsBound() )
		{
			RHICmdList.SetUAVParameter(ComputeShaderRHI, OutOffsets.GetBaseIndex(), OutOffsetsUAV);
		}
	}
	
	......
	
private:
	/** Uniform parameters stored in a vertex buffer, used to workaround an NVIDIA driver bug. */
	LAYOUT_FIELD(FShaderResourceParameter, RadixSortParameterBuffer);
	/** The buffer containing input keys. */
	LAYOUT_FIELD(FShaderResourceParameter, InKeys);
	/** The buffer to which offsets will be written. */
	LAYOUT_FIELD(FShaderResourceParameter, OutOffsets);
};

这个时候,大家看到这个Shader的定义已经就已经非常熟了吧,至少小编是非常熟悉了。
在外部的调用实际上就是

	FRadixSortParameters SortParameters;
	FRadixSortUniformBufferRef SortUniformBufferRef;

	// Setup sort parameters.
	SortParameters.RadixShift = 0;
	SortParameters.TilesPerGroup = TilesPerGroup;
	SortParameters.ExtraTileCount = ExtraTileCount;
	SortParameters.ExtraKeyCount = ExtraKeyCount;
	SortParameters.GroupCount = GroupCount;
	
	TShaderMapRef<FRadixSortUpsweepCS> UpsweepCS(ShaderMap);
	
	SortUniformBufferRef = FRadixSortUniformBufferRef::CreateUniformBufferImmediate( SortParameters, UniformBuffer_SingleDraw );
	UpsweepCS->SetParameters(RHICmdList, xxx, SortUniformBufferRef,xxx);

大功告成

后记(个人见解)

茴字的多种写法实际只是调侃,但是也逐渐理解第二种方法备注里面写的Notes: Long term, this macro will no longer be needed. Instead, parameter binding will become the default behavior for shader declarations的含义,感觉它有些不伦不类,说是struct,但在shader中并没有struct包装起来。对于它的那些优点,第三个方法有过之而无不及。
最终的编程指南就是对于不需要struct包装的变量,直接用第一种方法就可以了,而对于需要struct包装的变量,直接采用第三种方法。PS:建议使用新的宏名称无歧义些BEGIN_UNIFORM_BUFFER_STRUCT

posted @ 2022-09-19 10:59  wk立华奏  阅读(1073)  评论(0)    收藏  举报