Set fixed alpha value for final pixel

Hi there!

I’m working on a shader which acts like an intermediary, only good for storing raw values in the RGBA channels of each pixel. For this, I have the following requirements:

  • The shader for some reason only works as a Surface shader, not a legacy fragment shader.
  • The shader has to support writing alpha values.
  • The fragment’s alpha value has to override any already present alpha value in the texture, there cannot be any blending of clear color/colors from previously rendered fragments.
  • The fragment’s albedo cannot be blended with previously rendered fragments.
  • The shader renders to a RenderTexture (but that shouldn’t matter, right?)

An example output of this shader would be:

initial_clear_color_of_camera = RGBA(0, 0, 0, 0);
albedo = RGB(255, 128, 64);
alpha = 182;

final_pixel_on_rendertexture = RGBA(255, 128, 64, 182);

What I currently have is this shader:

Shader "Custom/Selection Id" {
    SubShader {
        Tags { "RenderType"="Transparent" }
        ZWrite on
        LOD 200
       
        CGPROGRAM
        #pragma surface surf Lambert alpha

        struct Input {
            float2 uv;
        };
        
      UNITY_INSTANCING_CBUFFER_START(Props)
         UNITY_DEFINE_INSTANCED_PROP(fixed4, _SelectionColor)
      UNITY_INSTANCING_CBUFFER_END

        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 c = UNITY_ACCESS_INSTANCED_PROP(_SelectionColor);
            
            o.Emission = c.rgb;
            o.Alpha = c.a;
        }
        
        ENDCG
    }
    FallBack "Diffuse"
}

I can see that ZWrite on doesn’t seem to have any positive effect on the resulting output color.

So in short, do you have any idea on how to use my fragment’s alpha as the final alpha of the pixel without blending my fragment’s albedo with the underlying clear color?

Cheers
Philipp

What do you mean by “legacy” fragment shader? Surface shaders are vertex fragment shaders, and do a lot of work you don’t want or need. I would suggest you post the vertex fragment shader you were trying to use and we can go from there, as what you want should be pretty close to the simpliest, most basic, default behavior for a shader.

Are you using Blit(), or rendering with a camera that has the render texture as it’s target, or using SetRenderTarget & Draw commands? Honestly, shouldn’t really matter, but can change some minor things in the way you handle this.

Going from your surface shader, this is what I assume you want.

Shader "Custom/Selection Id"
{
    SubShader
    {
        Tags { "RenderType"="Transparent" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
          
            #include "UnityCG.cginc"

            UNITY_INSTANCING_CBUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(fixed4, _SelectionColor)
            UNITY_INSTANCING_CBUFFER_END
          
            float4 vert (appdata_full v) : SV_POSITION
            {
                return UnityObjectToClipPos(v.vertex);
            }
          
            fixed4 frag (v2f i) : SV_Target
            {
                return UNITY_ACCESS_INSTANCED_PROP(_SelectionColor);
            }
            ENDCG
        }
    }
}

I was just referring to regular vertex and fragment shaders. I know that surface shaders are compiled down to regular vf shaders, but for some reason, when initially testing this idea, I used a fragment shader that wasn’t properly rendering at all. I don’t have it in my VC at all, so unfortunately am unable to procure it.

The great news is that your proposed shader is working (almost) perfectly! I made some small additions since Unity was throwing errors regarding v2f not existing, but kept the rest to your suggestion.

I’m rendering certain objects in my scene using a second camera with Replaced Shaders to a RenderTexture. Atm, I don’t really care about rendering order, so I’m just grabbing what’s currently available on the texture during Update().

Now the only issue that persists with your solution is the fact that the shader doesn’t properly write to the Z-Buffer. Check out the GIF I made to see the issue:

What should happen is that as soon as the cursor moves over the foremost object, it should get highlighted instead of the object behind it. I tried adding ZWrite On to the shader, but unfortunately to no effect.

This is what I currently have:

Shader "Custom/Selection Id"
{
    SubShader
    {
        Tags { "RenderType"="Transparent" }
        Pass
        {
            ZWrite On
          
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
       
            #include "UnityCG.cginc"
            UNITY_INSTANCING_CBUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(fixed4, _SelectionColor)
            UNITY_INSTANCING_CBUFFER_END
       
            struct v2f {
                float4 pos : SV_POSITION;
            };
       
            v2f vert (appdata_full v) : SV_POSITION
            {
                v2f o;
              
                o.pos = UnityObjectToClipPos(v.vertex);
              
                return o;
            }
       
            fixed4 frag (v2f i) : SV_Target
            {
                return UNITY_ACCESS_INSTANCED_PROP(_SelectionColor);
            }
            ENDCG
        }
    }
}

Again, thanks so much for your help!

You should just delete the v2f i in the frag() rather than adding a v2f. It’s unnecessary and now you have two things writing to SV_POSITION. Honestly surprised your edit compiles either.

Shaders default to writing to the Z buffer, so adding it to a shader that doesn’t have it will make no difference.

I think you forgot something. :wink:

Oh, random thought, does your RenderTexture have a depth buffer enabled?

It doesn’t compile without v2f added. I get that passing a single fixed4 to the fragment shader would be beneficial, but Unity complains about semantics when switching v2f to fixed4.

Afaik, RenderType Transparent shaders do not write to the Z-Buffer by default, hence I turned it on manually.

My RenderTexture has a 24bit Depth Buffer enabled, so that can’t be it.

Sorry about the missing GIF, here it is:

https://gfycat.com/ValuableWeeklyArthropods

You don’t actually want to pass anything from the vertex function to the fragment function. There’s no benefit here as you don’t want any of that data and it’ll cost more to read the _SelectionColor in the vert() and pass it to the frag() than just read it directly in the frag().

This should compile with out an issue.

Shader "Custom/Selection Id"
{
    SubShader
    {
        Tags { "RenderType"="Transparent" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
       
            #include "UnityCG.cginc"
            UNITY_INSTANCING_CBUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(fixed4, _SelectionColor)
            UNITY_INSTANCING_CBUFFER_END
       
            float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(v.vertex);
            }
       
            fixed4 frag () : SV_Target
            {
                return UNITY_ACCESS_INSTANCED_PROP(_SelectionColor);
            }
            ENDCG
        }
    }
}

Note I removed the v2f and the appdata in this one. They’re both extraneous since we only need one input and one output from the vert().

The “RenderType” Tag in itself doesn’t do anything. That’s there purely for replacement shaders, like what you’re doing. You could add that line to a normal opaque shader and see no change apart from how it shows up in certain camera textures (specifically the _CameraDepthNormalTexture) and replacement shader passes. It has no affect on the other rendering qualities. “Queue”=“Transparent” does affect rendering to a degree, but still does not disable Zwrite or blend modes, just changes when it is rendered and the order it sorts meshes to render in.

Have you confirmed there is actually anything else rendering in the replacement shader pass? Maybe have stuff other than the highlighted objects render out green, or some other random color, then use the frame debugger to step through?

Again, thank you so much for your help! Looking over my setup code for the RenderTexture, I noticed that the Inspector was showing me “At least 24bit” for Depth, but I wasn’t setting it through my code. Setting the depth explicitly solved the issue.

I was always under the impression that you were required to pass at least something to the fragment shader, but your solution removing v2f and appdata works perfectly fine.