RenderTexture with transparent URP-based ShaderGraph auto-blends with black

Hi, I followed [this thread](http:// Graphics.Blit with a material is just giving me a big blue box ) where someone suggests to use pass=0 when using Blit with URP or HDRP ShaderGraph based materials. This seems to work really well, I’ve run a bunch of tests and the Blit results are very accurate.

This unfortunately breaks-down when using bright transparent materials.

If you have a blank RenderTexture and you use a regular shader to render 1,1,1,0.5 using Blit, that RenderTexture contains the exact transparent white you drew.

If you try to do the same using a ShaderGraph, the white is unfortunately blended with black by default so you end up with a 0.8, 0.8, 0.8, 0.5 grey. I haven’t been able to find a solution given the limited number of base shaders available in the graph, currently I’m using a Sprite Unlit when alpha blending mode and that seems to give the best results. (I’m trying to do some shape boolean operations on some basic 2D stuff… very UI).

Anyways, I have found that you can create the RenderTexture and pre-Clear with a fully transparent white GL.Clear(true, true, new Color(1,1,1,0f)) and you’ll get the correct result when following up with a transparent white alpha shape from a shader graph.

However, this approach isn’t enough because other colors (like a transparent green or anything) will become more white than they were supposed to be.

I haven’t figured out a solution to get around this, so I guess I’ll be using old fashioned shaders for the time-being. Has anyone encountered this, or have any tips? Is this a bug?

I found this post going into some of the details of properly rendering transparent materials to and from RenderTextures, and it’s been incredibly helpful.

It turns out, the Transparent Alpha Blending-Mode URP ShaderGraph templates are all doing the correct thing, but it causes the RenderTexture to look incorrect when directly applied to a standard material, previewed in-editor, saved to a png texture, or sampled for color.

I’m not sure how to exactly describe the state of transparency and color in these results but they are only intermediate.
All you need to do is use any premultiply shader to then render the RenderTexture. Boom, looks perfect again.

So my shader was doing the correct thing in order to blend all along. You should start with a (0,0,0,0) black transparent background, the default state of a new RenderTexture. From there, multiple transparent ShaderGraph materials can be rendered to the texture over one another using: Graphics.Blit(null, renderTexture, material, 0);

The resulting image has everything blended together exactly as one would expect from alpha blending, It just needs to go through premultiply, so you’ll need a premultiply material, there are some options below.

Don’t be scared of shaders, be scared of ShaderGraph shaders. In my case, it’s way easier to write these simple 2D UI operations with Unity’s Base shaders and GPT’s help. They are likely way more performant too.

Options for premultiply material:

  • Regular Shader

  • Create a transparent unlit shader through Unity

  • Make sure it has Blend One OneMinusSrcAlpha, representing premultiply blending.

  • Create argument for a texture, ideally “_MainTex”

  • (this is 3 lines of code)

  • Frag method samples the texture: return tex2D(_MainTex, i.uv)

  • ShaderGraph

  • Setup a transparent ShaderGraph material using a premultiply alpha blending mode

  • Create Texture2D input, ideally, “MainTex”.

  • Sample Texture and return color & alpha

  • Old Legacy/Particles/Premultiply shader

  • search for “premultiply” in the shader drop-down in Inspector, under a material.

  • This seems to do okay for a quick test.

Verify original ShaderGraph uses alpha blending mode:

  • Save the Graph and select its asset in the Project pane

  • Click “Regenerate” or “View Generated Code” from the Inspector

  • Search for instances of "Blend "

  • Verify each instance uses Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha

1 Like

Final clarifications on stuff I was confused by:

The description for the state of the RenderTexture is that it is premultiplied or in premultiply state, where the colors have been adjusted by the alpha, by it’s own nature.

A premultiply shader renders a premultiplied translucent texture correctly.

If you then want to finalize a translucent RenderTexture to a PNG, you need to use an undo-premultiply shader/material and blit to another double-buffered RenderTexture. The result is a png that looks good in editor (for testing) or a RenderTexture that can be used as a texture in any other regular material.

Shader "UndoPreMultiply"
{
    Properties
    {
        _MainTex ("Main Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {

            ...

            sampler2D _MainTex;
            float4 _MainTex_ST;

           ...

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                if (col.a > 0.0) // Avoid division by zero
                    col.rgb /= col.a;
                return col;
            }
            ENDCG
        }
    }
}

You can also use Blend One Zero to create a shader that writes exactly the alpha value from frag() to the RenderTexture which is incredibly helpful if you’re looking to avoid all the alpha blending stuff all together. You could theoretically use this exclusively and do all the math for alpha blending inside of frag() given two input textures.