BlueRose
文章97
标签28
分类7
(虚幻4Shader篇)开始编写ComputeShader

(虚幻4Shader篇)开始编写ComputeShader

本文的大部分代码参考@YivanLee的文章,特此说明

有关ComputeShader的原理解释可以参考:https://zhuanlan.zhihu.com/p/36697128
这从此文可以得知ComputeShader是并行渲染的,所以需要设定UnbindBuffers。他与别的Shader通过RenderTarget交换数据。

声明ComputeShader

ComputeShader大部分代码与别的Shader相同。需要注意的是:自定义的SetParameters函数与自定义的UnbindBuffers函数。

我github上的代码在这里设定Struct并没有特殊意义,完全可以在PixelShader中添加,所以在这里为了不误导读者我将这段去掉了。

大致代码如下:

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

    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
    {
        return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
    }

    static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    {
        FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
        OutEnvironment.CompilerFlags.Add(CFLAG_StandardOptimization);
    }

public:
    FSimpleComputeShader() {}
    FSimpleComputeShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
        : FGlobalShader(Initializer)
    {
        OutTexture.Bind(Initializer.ParameterMap, TEXT("OutTexture"));
    }

    virtual bool Serialize(FArchive& Ar) override
    {
        bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
        Ar << OutTexture;
        return bShaderHasOutdatedParameters;
    }

    //这里使用了FUnorderedAccessViewRHIParamRef(UAV)类型的形参,用于设定RenderTarget(UAC)。
    void SetParameters(FRHICommandList& RHICmdList, FUnorderedAccessViewRHIParamRef InOutUAV)
    {
        FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();
        if (OutTexture.IsBound())
            RHICmdList.SetUAVParameter(ComputeShaderRHI, OutTexture.GetBaseIndex(), InOutUAV);
    }

    //将UAV设置为空
    void UnbindBuffers(FRHICommandList& RHICmdList)
    {
        FComputeShaderRHIParamRef ComputeShaderRHI = GetComputeShader();
        if (OutTexture.IsBound())
            RHICmdList.SetUAVParameter(ComputeShaderRHI, OutTexture.GetBaseIndex(), FUnorderedAccessViewRHIParamRef());
    }
protected:
    //用于进行数据交换的RenderTarget(UAC)变量
    FShaderResourceParameter OutTexture;
};

IMPLEMENT_SHADER_TYPE(, FSimpleComputeShader, TEXT("/Plugin/BRPlugins/Private/SimpleComputeShader.usf"), TEXT("MainCS"), SF_Compute);

在渲染函数中设置并且使用ComputeShader

我们需要重新定义一个ComputeShader所用的渲染函数,并且通过PixelShader将计算结果渲染出来:

static void UseComputeShader_RenderThread(
    FRHICommandListImmediate& RHICmdList,
    FTextureRenderTargetResource* OutputRenderTargetResource,
    FSimpleUniformStruct UniformStruct,
    ERHIFeatureLevel::Type FeatureLevel
)
{
    check(IsInRenderingThread());

    //为RHICmdList设置ComputeShader
    TShaderMapRef<FSimpleComputeShader> ComputeShader(GetGlobalShaderMap(FeatureLevel));
    RHICmdList.SetComputeShader(ComputeShader->GetComputeShader());

    int32 SizeX = OutputRenderTargetResource->GetSizeX();
    int32 SizeY = OutputRenderTargetResource->GetSizeY();
    FRHIResourceCreateInfo CreateInfo;

    //设置RenderTarget格式与属性,并以此创建UAC
    FTexture2DRHIRef Texture = RHICreateTexture2D(SizeX, SizeY, PF_A32B32G32R32F, 1, 1, TexCreate_ShaderResource | TexCreate_UAV, CreateInfo);
    FUnorderedAccessViewRHIRef TextureUAV = RHICreateUnorderedAccessView(Texture);

    //通过自定义函数设置ComputeShader变量=》分发计算任务进行并行运行=》计算完成后解除绑定
    ComputeShader->SetParameters(RHICmdList, TextureUAV);
    DispatchComputeShader(RHICmdList, *ComputeShader, SizeX / 32, SizeY / 32, 1);
    ComputeShader->UnbindBuffers(RHICmdList);

    //通过PixelShader的渲染函数将计算结果渲染出来
    DrawTestShaderRenderTarget_RenderThread(RHICmdList, OutputRenderTargetResource, FeatureLevel, FLinearColor(), Texture, UniformStruct);
}

编写usf

这里就直接贴代码了:

#include "/Engine/Public/Platform.ush"
#include "/Engine/Private/Common.ush"  

RWTexture2D<float4> OutTexture;  

[numthreads(32, 32, 1)]
void MainCS(uint3 ThreadId : SV_DispatchThreadID)
{
    //Set up some variables we are going to need
    float sizeX, sizeY;
    OutTexture.GetDimensions(sizeX, sizeY);

    float2 iResolution = float2(sizeX, sizeY);
    float2 uv = (ThreadId.xy / iResolution.xy) - 0.5;
    float iGlobalTime = SimpleUniformStruct.Color1.r;

    //This shader code is from www.shadertoy.com, converted to HLSL by me. If you have not checked out shadertoy yet, you REALLY should!!
    float t = iGlobalTime * 0.1 + ((0.25 + 0.05 * sin(iGlobalTime * 0.1)) / (length(uv.xy) + 0.07)) * 2.2;
    float si = sin(t);
    float co = cos(t);
    float2x2 ma = { co, si, -si, co };

    float v1, v2, v3;
    v1 = v2 = v3 = 0.0;

    float s = 0.0;
    for (int i = 0; i < 90; i++)
    {
        float3 p = s * float3(uv, 0.0);
        p.xy = mul(p.xy, ma);
        p += float3(0.22, 0.3, s - 1.5 - sin(iGlobalTime * 0.13) * 0.1);

        for (int i = 0; i < 8; i++)    
            p = abs(p) / dot(p, p) - 0.659;

        v1 += dot(p, p) * 0.0015 * (1.8 + sin(length(uv.xy * 13.0) + 0.5 - iGlobalTime * 0.2));
        v2 += dot(p, p) * 0.0013 * (1.5 + sin(length(uv.xy * 14.5) + 1.2 - iGlobalTime * 0.3));
        v3 += length(p.xy * 10.0) * 0.0003;
        s += 0.035;
    }

    float len = length(uv);
    v1 *= lerp(0.7, 0.0, len);
    v2 *= lerp(0.5, 0.0, len);
    v3 *= lerp(0.9, 0.0, len);

    float3 col = float3(v3 * (1.5 + sin(iGlobalTime * 0.2) * 0.4), (v1 + v3) * 0.3, v2)
                    + lerp(0.2, 0.0, len) * 0.85
                    + lerp(0.0, 0.6, v3) * 0.3;

    float3 powered = pow(abs(col), float3(1.2, 1.2, 1.2));
    float3 minimized = min(powered, 1.0);
    float4 outputColor = float4(minimized, 1.0);

    OutTexture[ThreadId.xy] = outputColor;
}

PS.https://zhuanlan.zhihu.com/p/36697128 该文中通过RHI取得计算结果的方法好像失效了,所以采用了https://zhuanlan.zhihu.com/p/36697483 的方法。