Flickering textures on objects with a LightBlend shader when moving camera {SOLVED}

Dear Hivemind!

I just took my first steps into the wonderful world of shaders and immediately stepped into the bear trap of confusion. Anyway I was creating a shader that could blend between two textures based on the light information.

To be more precise:

  • if an object is in the shadows it should show Texture A

  • if an object is fully lit it should show Texture B

  • if an object is in between it should blend it with an amount of 50%

I approached this shader is by using a grabpass to write the lighting information to a texture and then using that texture to blend between two textures. This works without a hitch with a directional light and it also ALMOST works with a point light except for this issue:

  • When you move the camera the texture flickers between the two textures. As you can see it does not appear to happen randomly since it happens at some fixed points in space.
  • It does not happen when I only have one object in the space.
  • The framedebugger shows that the render order of the object changes.
  • The flickering is caused by the grab pass.

Here is my shadercode:

Shader "Custom/LightBlend"
{
    Properties
    {
        _LightTex("Light Texture", 2D) = "white" {}
        _LightColor("Light Tint", Color) = (1,1,1,1)
        _ShadowTex("Shadow Texture", 2D) = "white" {}
        _ShadowColor("Shadow Tint", Color) = (1,1,1,1)
        _BlendStrength("Blend Strength", Range(1,8))= 1
    }

    // Calculate lighting in this pass
    SubShader{
    Tags{ "RenderType" = "Opaque""Queue" = "Background"}
   
    CGPROGRAM
    #pragma surface surf LightPass fullforwardshadows  
           
    struct Input
    {
        float2 uv_LightTex;
    };

    float _BlendStrength;

    float4 LightingLightPass(SurfaceOutput s, half3 lightDir, half atten) {       
        half NdotL = dot(s.Normal, lightDir);
        half4 c;
        c.rgb = s.Albedo * (NdotL * atten * _BlendStrength);
        return c;
    }

    void surf(Input IN, inout SurfaceOutput o) {
        o.Albedo = float4(1,1,1,1);
    }
    ENDCG
   
    //Calculate blending in this pass

    GrabPass{ "_LightingGrab" }
    Tags{"RenderType" = "Opaque" "Queue" = "Background" }
   
    Lighting Off
   
    CGPROGRAM       
    #pragma surface surf Unlit vertex:vert
    #pragma debug  

    sampler2D _LightingGrab;
    sampler2D _LightTex;
    sampler2D _ShadowTex;
    half4 _LightColor;
    half4 _ShadowColor;

    struct Input {
        float2 uv_LightTex;
        float2 uv_ShadowTex;
        float4 grabUV;
    };
   
    void vert(inout appdata_full v, out Input o) {
        // Calculate grab uv's
        UNITY_INITIALIZE_OUTPUT(Input, o);
        float4 hpos = UnityObjectToClipPos(v.vertex);
        o.grabUV = ComputeGrabScreenPos(hpos);
    }

    half4 LightingUnlit(SurfaceOutput s, half3 lightDir, half atten) {
        half4 c;
        c.rgb = s.Albedo;
        //c.a = s.Alpha;
        return c;
    }

    void surf(Input IN, inout SurfaceOutput o) {
        half4 _GrabTex = tex2Dproj(_LightingGrab, UNITY_PROJ_COORD(IN.grabUV));
        half4 lightTex = tex2D(_LightTex, IN.uv_LightTex)*_LightColor;
        half4 shadowTex = tex2D(_ShadowTex, IN.uv_ShadowTex) * _ShadowColor;
        o.Albedo = lerp(shadowTex, lightTex, _GrabTex);
    }
    ENDCG
    }
        Fallback "Diffuse"
}

Now I’m not sure if this is a bug or that I’m doing something wrong or a bit of both. But if anyone could point me in the right direction it would be much appreciated even if someone has a better way to go about this!

THANKS!
Daniel

PS it’s a shader I’m using for my VR projects which is why I use forward rendering.

If you don’t want any of the actual lighting, just the shadow, using a surface shader is kind of overkill. You should look at doing this in a single pass using a custom vertex / fragment shader that reads the shadow directly instead, especially since grab passes are quite slow, even on PC.

Edit: Actually, if you want this to work with multiple lighting types you may be stuck using a grab pass, because Unity does each light as a separate pass and there’s no real way to combine them with out replacing Unity’s entire lighting system (ala using Valve’s The Lab Renderer).

As for what’s happening, I believe you’re running into a problem with the grabpass being “grabbed” before all parts of your object have rendered. Using a named grab pass means it’ll only be rendered once with the first object that uses it, but you might not have the control you need to ensure that happens after the entire area has been rendered with all lighting passes. You may need to break the grab pass into it’s own shader and use a queue of Geometry+1 to ensure it only gets rendered after all parts. Then to use this you’ll want to append that material to the mesh renderer component’s materials list. That will cause an object to be rendered with both materials in the order you need.

1 Like

Heya bgolus,

Thanks for your quick reply and info! I was using shaderforge and ran into a similar problem with point and spotlights like this: CG Programming, Points Lights attenuation and light range - Questions & Answers - Unity Discussions . Which is why I wanted to use a surface shader in the hopes I could bypass these lighting problems.

Should I use Unity - Scripting API: Graphics.Blit for this?
If not could you perhaps point me towards an example or scripting reference about how I should implement your solution?
Would your solution also work in VR?

That’s something built into Unity’s lighting system, not something caused by ShaderForge. So unfortunately that’s unavoidable if you try to modify the falloff of point or spot lights. Again, the solution here is to, unfortunately, not use any of Unity’s lighting system at all.

My solution doesn’t require any blit or any scripting at all. Just copy your grab pass and everything after (sans the Fallback) into it’s own shader and change “Queue” = “Background” to “Queue” = “Background+1” (said “Geometry+1” before because I didn’t realize you were rendering this in the background queue). After that just go to your renderer component and double the size of the materials list and fill the new slots with a material using the grab pass shader. And yes it would work in VR as well as your current system, hopefully sans the current bug.

From a scripting side setting the materials would be using, well, .materials.

The problem with blit, or most of the manual rendering tools Unity exposes is you’re fundamentally trying to use Unity’s built in lighting and shadows system, and those only work if you don’t use a lot of the more manual ways of rendering stuff.

1 Like

Thanks going to try that out tomorrow! Ow and it should have been geometry in de render queue, but I was trying things out as a desperste measure :). Also tried +1 geometry but it did not work. Hopefully by splitting it, it will! Will report back!


AWESOME IT WORKS! If I could crawl to you screen and give you a big kiss on the forehead I would! THANKS!
And in the process learned what a grabpass actually is. So much still left to learn! If the performance hit is unwieldy I will set my next goal on writing my own lighting solution and grow a long white beard in the process :slight_smile: Thanks!

1 Like