[Plugin]Custom Texture to Render Target Shader Plugin Issues

Hi in my journey to learn to script more complicated plugins, I am currently trying to have an input texture display as a Render Target when I play the game. I first got a single color working and that works like a charm but now I am having issues with loading an input texture onto a render target.

Below is my github to the complete project.
https://github.com/som1990/UE4_20ShaderPluginDev

Below is a step by step guide to my process.

I have double checked my custom HLSL Pixel Shader (By running it through a custom HLSL node in Material) and it works there but It doesn’t display the texture when I hit play(as seen in figure 1).

RenderTextureScreenShot.PNG.jpg
Figure 1: Screenshot of RenderTexture when hiting Play.

Stage 1)
I have attached the hlsl code Below (Figure 2). Here I try to blend a constant color with a texture. I got my Constant color to display but cannot get the texture to display so the blend is from black to MyColor value that I have set.


Figure 2: My HLSL PixelShader and Vertex Shaders. PSVariable is a Variable Buffer which holds my Blend Value data.

Stage2) Taking Input and Passing it from my class to my shading plugin.

Below is how I’m Converting my UTexture2D* “TextureToDisplay” to a FTexture2DRHIRef “InputTexture” so that it can be passed to my plugin from my custom c++ class.


 InputTexture = static_cast<FTexture2DResource*>(TextureToDisplay->Resource)->GetTexture2DRHI();

Below is how I’m passing the Texture.


if (InputTexture && RenderTarget)
        TexPixelShading->ExecutePixelShader(RenderTarget, InputTexture, ColorToBlend, BlendAmount);

Stage3) Sending my input texture to my shader class


void FTexPSImplementation::ExecutePixelShader(UTextureRenderTarget2D* RenderTarget, FTexture2DRHIRef InputTexture, FColor InputColor, float BlendAmount)
{
    check(IsInGameThread());

    if (bisUnloading || bIsPixelShaderExecuting)
        return;
    if (!RenderTarget)
        return;

    bIsPixelShaderExecuting = true;

    if (TextureParameter != InputTexture)
        bMustRegenerateSRV = true;

    VariableParameters.BlendFactor = BlendAmount;

    CurrentRenderTarget = RenderTarget;
    TextureParameter = InputTexture;
    m_startCol = FLinearColor(InputColor.R / 255.0, InputColor.G / 255.0, InputColor.B / 255.0, InputColor.A / 255.0);

    ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
        FPixelShaderRunner,
        FTexPSImplementation*, MyShader, this,
        {
            MyShader->ExecutePixelShaderInternal();
        }
    );

}

void FTexPSImplementation::ExecutePixelShaderInternal()
{
    check(IsInRenderingThread());
    if (bisUnloading)
    {
        if (NULL != TextureParameterSRV)
        {
            TextureParameterSRV.SafeRelease();
            TextureParameterSRV = NULL;
        }
        return;
    }

    FRHICommandListImmediate& RHICmdList = GRHICommandList.GetImmediateCommandList();

    //If our input texture reference has changed, we need to recreate our SRV
    if (bMustRegenerateSRV)
    {
        bMustRegenerateSRV = false;

        if (NULL != TextureParameterSRV)
        {
            TextureParameterSRV.SafeRelease();
            TextureParameterSRV = NULL;
        }

        TextureParameterSRV = RHICreateShaderResourceView(TextureParameter, 0);
    }

    TShaderMapRef<FMyTestVS> VertexShader(GetGlobalShaderMap(FeatureLevel));
    TShaderMapRef<FMyTestPS> PixelShader(GetGlobalShaderMap(FeatureLevel));

    //Way to get renderTarget Texture
    CurrentTexture = CurrentRenderTarget->GetRenderTargetResource()->GetRenderTargetTexture();
    SetRenderTarget(RHICmdList, CurrentTexture, FTextureRHIRef());

    FGraphicsPipelineStateInitializer PSOInitializer;
    RHICmdList.ApplyCachedRenderTargets(PSOInitializer);

    PSOInitializer.PrimitiveType = PT_TriangleStrip;
    PSOInitializer.BoundShaderState.VertexDeclarationRHI = GQuadVertexDeclaration.VertexDeclarationRHI;
    PSOInitializer.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
    PSOInitializer.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
    PSOInitializer.BlendState = TStaticBlendState<>::GetRHI();
    PSOInitializer.RasterizerState = TStaticRasterizerState<>::GetRHI();
    PSOInitializer.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();

    SetGraphicsPipelineState(RHICmdList, PSOInitializer);
    //TextureParameter is a FTexture2DRHIRef
    PixelShader->SetParameters(RHICmdList, m_startCol, TextureParameterSRV,TextureParameter);
    PixelShader->SetUniformBuffers(RHICmdList, VariableParameters);
    DrawPrimitiveUP(RHICmdList, PT_TriangleStrip, 2, m_pQuadVB, sizeof(m_pQuadVB[0]));

    PixelShader->UnbindBuffers(RHICmdList);
    RHICmdList.CopyToResolveTarget(
        CurrentRenderTarget->GetRenderTargetResource()->GetRenderTargetTexture(),
        CurrentRenderTarget->GetRenderTargetResource()->TextureRHI,
        FResolveParams());

    if (bSave)
    {
        bSave = false;
        SaveScreenshot(RHICmdList);
    }

    bIsPixelShaderExecuting = false;

}

Stage 4) Sending my Incoming to data to the Pixel Shader. Below is my Pixel Shader class


class FMyTestPS : public FGlobalShader
{
    DECLARE_EXPORTED_SHADER_TYPE(FMyTestPS, Global, TEXPIXELSHADER_API);

public:
    FMyTestPS() {}
    FMyTestPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
        :FGlobalShader(Initializer)
    {
        //This is used to let the shader system have parameters be available in the shader.
        //The second Parameter has the name of the Parameter in the shader
        //The third Parameter determines if it's optional or mandatory.
        MyColorParameter.Bind(Initializer.ParameterMap, TEXT("MyColor"));
        TextureParameter.Bind(Initializer.ParameterMap, TEXT("TextureParameter"));
        TexMapSampler.Bind(Initializer.ParameterMap, TEXT("TexMapSampler"));
    }

    //This method is used to set non buffer(Texture) parameters.
    void SetParameters(
        FRHICommandList& RHICmdList,
        const FLinearColor& Color,
        FShaderResourceViewRHIRef TextureParameterSRV,
        FTextureRHIParamRef InputTextureRef
    )
    {
        FPixelShaderRHIParamRef PixelShaderRHI = GetPixelShader();
        // Stores incoming color parameter to MyColorParameter
        SetShaderValue(RHICmdList, PixelShaderRHI, MyColorParameter, Color);
        //Store the shader resource view to the texture parameter
        /*if (TextureParameter.IsBound())
        {
            RHICmdList.SetShaderResourceViewParameter(PixelShaderRHI,
                TextureParameter.GetBaseIndex(), TextureParameterSRV);
        }
        */
        FSamplerStateRHIParamRef SamplerStateLinear = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();

        SetSamplerParameter(RHICmdList, PixelShaderRHI, TexMapSampler, SamplerStateLinear);
        SetTextureParameter(RHICmdList, PixelShaderRHI, TextureParameter, InputTextureRef);
    }

    void SetUniformBuffers(FRHICommandList& RHICmdList, FPixelShaderVariableParameters& VariableParameters)
    {
        FPixelShaderVariableParameterRef VariableParametersBuffer;

        VariableParametersBuffer =
            FPixelShaderVariableParameterRef::CreateUniformBufferImmediate(
                VariableParameters, UniformBuffer_SingleDraw);

        SetUniformBufferParameter(RHICmdList, GetPixelShader(),
            GetUniformBufferParameter<FPixelShaderVariableParameters>(),
            VariableParametersBuffer);
    }

    void UnbindBuffers(FRHICommandList& RHICmdList)
    {
        /*FPixelShaderRHIParamRef PixelShaderRHI = GetPixelShader();
        if (TextureParameter.IsBound())
        {
            RHICmdList.SetShaderResourceViewParameter(PixelShaderRHI,
                TextureParameter.GetBaseIndex(), FShaderResourceViewRHIRef());
        }*/

    }

    static bool ShouldCache(EShaderPlatform Platform)
    {
        return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5);
    }

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

    //Required Function
    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
    {
        return true;
    }

private:
    //If using Textures use FShaderResourceParameter
    FShaderParameter MyColorParameter;

    FShaderResourceParameter TextureParameter;
    FShaderResourceParameter TexMapSampler;

};

And finally Below is how I’m connecting my class to my PixelShader


IMPLEMENT_UNIFORM_BUFFER_STRUCT(FPixelShaderVariableParameters, TEXT("PSVariable"))

IMPLEMENT_SHADER_TYPE(, FMyTestVS, TEXT("/Plugin/TexPixelShader/Private/TexVS_PS.usf"), TEXT("MainVS"), SF_Vertex);
IMPLEMENT_SHADER_TYPE(, FMyTestPS, TEXT("/Plugin/TexPixelShader/Private/TexVS_PS.usf"), TEXT("MainPS"), SF_Pixel);

IMPLEMENT_MODULE(FDefaultModuleImpl, TexPixelShader);

I apologize for the long post but I wanted to give a complete overview of the pipeline so that I can get pointers to where I might be going wrong.
My Static mesh has UVs and is fine. As shown in the figure below.

So please if anyone can help me out it’ll be great.

Thank you for taking the time and hope you hear a quick response.
Regards,

Alright figured it out. It happens to be the placement of the UV output on my Vertex Shader. It had to be called before outPos to have an effect. Anyone know why this is the case? Why does the positioning of the inputs/outputs matter? So here is the corrected hlsl file.


//Created 2019 . MIT License

#include "/Engine/Private/Common.ush"
#include "/Engine/Private/ColorUtils.ush"


//VertexShader
void MainVS(
    in float4 InPosition : ATTRIBUTE0,
    in float2 InUV : ATTRIBUTE1,
    out float2 OutUV : TEXCOORD0, // This needs to be before OutPos. Don't know why
    out float4 OutPos : SV_POSITION

    )
{
    OutPos = InPosition;
    OutUV.x = 1.0f-InUV.x;// X axis was inverted for me based on how I layed out my render quad
    OutUV.y = InUV.y;
}

//Taken from "/Engine/Shaders/Private/SimpleElementPixelShader.usf"
MaterialFloat4 ColourTexture2DSample(Texture2D Tex, SamplerState Sampler, float2 UV)
{
    //If you want to sample a particular mip level use SampleLevel
    //If you want raw unfiltered pixel data use Tex.Load
    MaterialFloat4 Sample = Tex.Sample(Sampler, UV);
#if SRGB_INPUT_TEXTURE && (FEATURE_LEVEL == FEATURE_LEVEL_ES2) // ES2 does not support sRGB sampling
    Sample.rgb = Sample.rgb * Sample.rgb;
#endif
    return Sample;
}

float4 MyColor;

Texture2D TextureParameter;
SamplerState TexMapSampler;

void MainPS(
    in float2 InUV : TEXCOORD0,
    out float4 OutColor : SV_Target0
    )
{
    float xSize, ySize;
    float4 packedValue = ColourTexture2DSample(TextureParameter, TexMapSampler, InUV);
    OutColor = MyColor * (1.0 - PSVariable.BlendFactor) + float4(packedValue.r, packedValue.g, packedValue.b,1.0) * PSVariable.BlendFactor;
    //OutColor = float4(InUV.x, InUV.y, 0.0f, 1.0f); //Testing to see if UVs are imported correctly
}

1 Like