Graphics.Blit with HDRP Shadergraph

As MR-Heavy originally asked on the shadergraph thread - with no answer as far as I know:

"Is it any good way to force “Graphics.Blit(Texture2D, RenderTexture, Material); " work with shader graph’s unlit shader?”

@MR-Heavy , have you found a solution yet? I tried wyatttt 's method and got your results.

@wyatttt , do you have any more ideas?

All suggestions are appreciated.
HDRP and Shadergraph on 6.9.1 in 2019.2.0b9. Can upgrade if necessary.

I’m not sure if this is related to the issue I had with Graphics.Blit not working, but in the end I used Unity’s CustomRenderTexture to get shadergraph to write to a texture.

To elaborate, I was making a Snow Track shader for car wheels, and was using one shader graph to paint to a texture(aka splatmap) based on wheel positions, then another shader graph to read from that texture and displace the snow.

The Custom Render Texture takes in a material that uses the ShaderGraph you’re wanting to blit with. You can then use that Custom Render Texture as an input to another ShaderGraph (in my case, a Snow Displacement Shadergraph).

On the Custom Render Texture, you can set it’s updateMode to “OnDemand”, and tell it to Update whenever you would call Graphics.Blit.

In my case of SnowTracks, this would be each fixed update frame, raycasting wheel positions then sending those positions to the CustomRenderTexture DrawTracks shadergraph, which updates the RenderTexture and is then used to the SnowTracks Displacement shadergraph.

I uploaded the project to github also: GitHub - tim-neville/Snow-Tracks-ShaderGraph-Unity-2019.1: Vehicle snow tracks shader graph test using unity "custom render texture", unable to get Graphics.Blit to work for a render texture/

7 Likes

Thank you so much! This is exactly what I was looking for.

1 Like

No problem, glad I could help!

So, for some reason, the HDRP unlit shadergraph, as well as any other material created by the default HDRP shaders, fails to update a custom render texture.
This is probably due to these specifications:

The only mandatory steps when writing a shader for a custom texture are:
* #include “UnityCustomRenderTexture.cginc”
- Use the provided Vertex Shader
- CustomRenderTextureVertexShader*
- Use the provided input structure v2f_customrendertexture for the pixel shader

When I use the example custom render texture shader; however, the render texture works perfectly.

Thank you for the help. I will ask around to see if someone has a workaround.

Thanks for the sample, I did note however that repo didn’t have any license attached, are the scripts and shaders free to use?

@rz_0lento Yes they’re free to use, I just added the MIT licence to the repo aswell for clarity. Thanks for asking!

1 Like

You can use regular (nonHDRP) unlit shadergraph with HDRP. I quickly ported nikefootbag’s sample to HDRP by just changing the SRP asset to HDRP asset from graphics settings, swapping the regular shaders to HDRP/Lit and recompiled/resaved the unlit shadergraphs used for drawing the tracks (didn’t port them to HDRP unlit at all).

Just to clarify this: those regular shadergraphs are all compatible with HDRP, you can think them as generic/universal graphs that work on both SRP pipelines, only HDRP shadergraphs are specific to one SRP.

1 Like

Thank you rizu. Since nikefootbag’s solution did not work in my HDRP-based project, I had incorrectly assumed that HDRP did not yet work with custom render textures. I have tried with the unlit (non-HDRP specific) shadergraph as well - so it must be an issue with my project.

Update: Custom render textures only work with LWRP, not HDRP, unless both packages are installed in the same project and the unlit master node is used.

1 Like

I had the same issue. It’s clearly a bug in Unity, so I’ve reported it. Now you can vote for fixing it here!

No comment on Graphics.Blit but CRT definitely works on both URP and HDRP. This is relatively old thread but wanted to give some update now that the thread has been bumped. Basically URP Unlit and old style CRT shaders work as is on HDRP but it’s challenging to get URP shader itself to HDRP as you need to have both URP and HDRP installed for this to work.

You can still use old style CRT shaders as shown in official docs for CRT. If you want node graph shader for CRT you can use Amplify Shader Editor as it has special CRT support. This also works with HDRP.

2 Likes

If it helps anyone, CommandBuffer.Blit does work with shader graph shaders, at least in URP.

1 Like

@rz_0lento What do you mean by “CRT shaders”? I don’t recognize the term :frowning:
@Oxeren thanks for sharing this! How do you use it? Do you add the custom render pass? Maybe you would have some code to share, please?

I’ve used it in scriptable renderer features/passes. Here is an example of a pass that uses it. Check out the rest of the repository to see how the pass is used in the scriptable renderer feature.

scroll to the bottom of this page to section “Writing a shader for a Custom Render Texture”: https://docs.unity3d.com/Manual/class-CustomRenderTexture.html

1 Like

In case it helps someone, I eventually cobbled together (from various tips including the ones on this thread) a CustomRenderTexture solution for URP blitting. I expect it should work with HDRP too although I haven’t tested that.

My use case was that I needed to copy the texture out of a material with a ShaderGraph shader I wrote that produces fBM noise, but Graphics.Blit wasn’t working and I couldn’t figure out how to make CommandBuffer.Blit work either. Finally I got it working with the CustomRenderTexture tip. The script below has some extra guff for timings and storing the resulting texture into a canvas RawImage I was using to test; those can be safely stripped out if all you care about is the GetTextureFromShaderGraphBasedMaterial method. You could probably also keep the CRT around and use Update() instead of recreating it for each refresh, if memory isn’t an issue.

using System.Diagnostics;
using UnityEngine;
using UnityEngine.UI;
using Debug = UnityEngine.Debug;

public class ShaderGrab : MonoBehaviour
{
    public int width = 100;
    public int height = 100;
    public RawImage displayImage;
   
    private Material _material;
    private Stopwatch _sw;
    private bool _isProcessing;
    private readonly int _mainTex = Shader.PropertyToID ("_MainTex");
    private const bool ShowDebugTimings = true;

    private void Start ()
    {
        if (ShowDebugTimings)
        {
            _sw = new Stopwatch ();
        }
        MeshRenderer meshRenderer = GetComponent<MeshRenderer> ();
        _material = meshRenderer.sharedMaterial;
        SetDisplayTexture ();
    }

    private void Update ()
    {
        if (Input.GetKeyDown (KeyCode.Space))
        {
            SetDisplayTexture ();
        }
    }

    private void SetDisplayTexture ()
    {
        if (ShowDebugTimings)
        {
            _sw.Restart ();
        }
        displayImage.texture = GetTextureFromShaderGraphBasedMaterial (_material, width, height);
        if (ShowDebugTimings)
        {
            _sw.Stop ();
            Debug.Log ("Processing completed in " + _sw.ElapsedMilliseconds.ToString ("N0") + " ms");
        }
    }

    private Texture GetTextureFromShaderGraphBasedMaterial (
        Material shaderBasedMaterial, int outputTextureWidth, int outputTextureHeight)
    {
        CustomRenderTexture crt = new CustomRenderTexture (
            outputTextureWidth, outputTextureHeight, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear)
        {
            updateMode = CustomRenderTextureUpdateMode.OnDemand,
            material = shaderBasedMaterial,
            initializationSource = CustomRenderTextureInitializationSource.Material,
            initializationMaterial = shaderBasedMaterial
        };
        shaderBasedMaterial.SetTexture (_mainTex, crt);
        crt.Initialize ();
        crt.Release ();
        return crt.material.mainTexture;
    }
}

For those interested:

Unity 2021.2 - Preferred solution

Blitting a BuiltIn Unlit Shader Graph to a RenderTexture can work with the High Definition Render Pipeline since Unity 2021.2, without any trickery. At least as far as I know, it also works with URP, HDRP and the BuiltIn pipeline, on Windows.

8099039--1048148--upload_2022-5-3_14-46-9.png

Then, you can just call Graphics.Blit() with a material using that Unlit Shader Graph.
e.g.

Graphics.Blit(null, targetRenderTexture, myShaderGraphMaterial, 0);

Not sure if that solution works on all platforms or with all possible render pipeline settings, please comment below if you happen to know.

I could create a repo with a working solution demo, if someone asks for it.

Previous versions (Unity 2019 - 2021.1) - Alternate solution

In previous versions, a simple (yet hackish) solution for Graphics.Blit to work with HDRP Unlit Shader Graphs can be found here: GitHub - Bers1504/unity-hdrp-shadergraph-blit: Demo of a patch to fix Graphics.Blit with HDRP Shadergraphs in Unity 2019.4

That repo contains a custom function node that can be added to an unlit HDRP Shader Graph, which works as a patch for fixing the broken HDRP ShaderGraph Blit. The problem is related to model / view matrices, which aren’t properly set up for the blit quad to correctly fill the viewport. It seems the values from the last active camera are used in the model view matrix when blitting, which is probably wrong. The camera-relative rendering setting also seems to be part of the problem, as the blit quad vertices are incorrectly changed when that feature is active in the project rendering settings.

As a workaround, the HDRP model, view and projection matrices as well as the functions to transform vertices to clip space are overridden with values that are known to work with blitting. To achieve this, a custom function patch node is added in the shader graph, and its sole purpose is to provide an injection point for overriding matrices in the final actual shader, generated from the Shader Graph (yes, that is super hacky and fragile as it assumes a certain shader code expansion order and also assumes undocumented transformation function names - use at your own risks!).

Anecdotally, without this patch, it is sometimes possible to see a tiny misplaced quad being rendered, when calling Graphics.Blit() while the last active camera happens to be at (0,0,0).

Finally, a reminder that Built-In Unlit Shader Graph are known to work in Unity 2021.2 without this ugly patch, so you should really use the first, preferred solution if you can.[/code]

2 Likes

Not really, but…

For Unity 2021.3.4, the line Graphics.Blit(null, targetRenderTexture, myShaderGraphMaterial, pass);

  • HDRP - Unlit shadergraph - does not work

  • HDRP Lit shadergraph - does not work

  • URP Unlit shadergraph - works for parameter pass = 0

  • URP Lit shadergraph - works for parameter pass = -1 and also for pass = 1

  • Built-in Unlit shadergraph - works for parameter pass = 0

  • Built-in Lit shadergraph - works for parameter pass = 2

Disclaimer: it does work like mentioned above, but I have not found any documentation telling why. No idea why it fails for HDRP either. If someone would know, please tell

1 Like

HDRP shadergraphs fail to render properly with Graphics.Blit() mainly because object and view projection matrices used by these shaders are improperly set by Unity (it is unsupported, for now). Additionally, the camera-relative rendering option can also mess with these matrices.

I was able to verify this by generating the shader code of an unlit HDRP shader graph, then hard-coding the matrices and transformation functions as follow:

#define UNITY_MATRIX_VP float4x4(2, 0, 0, -1, 0, -2, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1)
#define UNITY_MATRIX_M  float4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)

float3 TransformObjectToWorld_NotCameraRelative(float3 positionOS)
{
  float4x4 m = UNITY_MATRIX_M;
  return mul(UNITY_MATRIX_M, float4(positionOS, 1.0)).xyz;
}
#define TransformObjectToWorld TransformObjectToWorld_NotCameraRelative

float4 TransformWorldToHClip_BlitSupport(float3 positionWS)
{
  // Transforms position from world space to homogenous space
  return mul(UNITY_MATRIX_VP, float4(positionWS, 1.0));
}
#define TransformWorldToHClip TransformWorldToHClip_BlitSupport

With these matrices, blitting an unlit HDRP shadergraph works with pass = 6 “ForwardOnly” (successfully tested in 2019.1, 2019.3, 2021.3.4f1).

DISCLAIMER: I’m not suggesting this is a proper fix, just giving out the reason why Graphics.Blit() it is not working. The test above only proves that Blit() could work with HDRP shadergraphs, if shader matrices were set properly by Unity.
Check this repo if you want to test it for yourself, the example HDRP shadergraph works with 2021.3.4f1 on windows with a matrix hack).

1 Like