Projector - project to back faces problem

(Unity 3.5.5) Then using projector, it projects the mask to back side of the object, wich is not visible from the point of projector. This applyes both to custom made projectors and standard blob shadow projector, wich comes with Unity.

It’s sad, because baked and may be manually edited projective shadow through the semitransparent or colored glass looks great, and this effect most probably is not achivable by real time shadows even in Unity Pro.

Is there some way to solve backface projection problem? It’s seems that where is no simple solution, may be a custom shader is requred.

In these two images the problem example with standard blob shadow, projected from above the sphere. The shadow is on both upper and lower side of the sphere. Does anyone know how to fix this?

1035881--38421--$BlobShadow_above.jpg
1035881--38422--$BlobShadow_below.jpg

Apologize for bumping an old topic, but I am having the same issues and could not find the solution in the web/forum. Would anyone please help? Thanks in advance!

I’m interested in knowing if there’s a solution as well.
Did you solve the issue somehow in the meantime?

+1 I need a solution for this.

any solutions ???

I am also currently looking for a solution to this, I have used a falloff texture as described here: Unity - Manual: Projector component. I have also played with the settings “Alpha from Greyscale”, “Generate Mip Maps”, “Border Mip Maps” and “Wrap Mode” with no avail.

I modified the projector multiply shader’s vertex program to set the alpha of the shadow based on the vertex’s normal.

o.uvFalloff.a = max(0,sign(v.normal.y - 0.9));

In my case, i didnt want to see the shadow on anything that doesn’t have a normal.y = 1 (straight up). You could do something similar to remove the shadow from anything with a negative normal.y.

Here’s a slightly more flexible version of the @bitbotlabs solution above.

This finds the cosine of the angle between the forward vector of the projector (i.e. the light direction) and the normal of any surface the projector is hitting. (The dot product of two vectors is the standard way to find for this.) This will return 1 when the two vectors are parallel, 0 when they’re perpendicular and -1 when they are pointing in exactly opposite directions. So when this value is less than zero, it means the surface is facing toward the projector and should receive the shadow.

I multiply that value by -1 and clamp it to convert to a zero-to-one alpha range where surfaces with normals pointing directly toward the projector will return one, then smoothly reduce until any point with a normal pointing perpendicular to or away from the projector will return zero.

o.uvFalloff.a = clamp(-1*dot(normalize(_ProjectorDir), v.normal),0,1);

Note that “_ProjectorDir” is a variable I declared in the shader, e.g.:

fixed3 _ProjectorDir;

I then pass this value in from a script on my projector using something like:

material.SetVector("_ProjectorDir",transform.forward);

Not to revive a dead thread… but this actually helped me a ton, I still had to wrestle the shader in order to get it to work, with the help of an engineer.

This is a combo of the solutions above, I still have a component on my projector that sets the vector on update.

I also took out the color and the falloff texture bc they didnt really make much sense to me :).

Here is the final shader we came up with:

'unity_ProjectorClip'

Shader "Projector/Light" {
    Properties {
        _ShadowTex ("Cookie", 2D) = "" {}
        _ProjectorDir ("_ProjectorDir", Vector) = (0,0,0,0)
    }
 
    Subshader {
        Tags {"Queue"="Transparent"}
        Pass {
            ZWrite Off
            ColorMask RGB
            Blend OneMinusDstColor One
            Offset -1, -1
 
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #include "UnityCG.cginc"
        
            struct v2f {
                float4 uvShadow : TEXCOORD0;
                float4 uvFalloff : TEXCOORD1;
                UNITY_FOG_COORDS(2)
                float4 pos : SV_POSITION;
            };

            struct VertexInput {
                float4 vertex : POSITION;
                fixed3 normal : NORMAL;
            };
        
            float4x4 unity_Projector;
            float4x4 unity_ProjectorClip;
            fixed4 _ProjectorDir;

            v2f vert (VertexInput v)
            {
                v2f o;
                o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                o.uvShadow = mul (unity_Projector, v.vertex);
                o.uvFalloff = mul (unity_ProjectorClip, v.vertex);
                UNITY_TRANSFER_FOG(o,o.pos);
                o.uvFalloff.a = clamp(-1*dot(normalize(_ProjectorDir), v.normal),0,1);

                return o;
            }
        
            fixed4 _Color;
            sampler2D _ShadowTex;
        
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 texS = tex2Dproj (_ShadowTex, UNITY_PROJ_COORD(i.uvShadow));
                fixed4 res = texS * ceil(abs(i.uvFalloff.a)) ;
            
                return res;
            }
            ENDCG
        }
    }
}

^ Thanks a lot for posting this solution. I was able to implement, and I can see that the backfacing areas of my mesh had their alphas lowered down to zero via this line: o.uvFalloff.a = clamp(-1*dot(normalize(_ProjectorDir), v.normal),0,1);

However instead of changing the alpha of those backfaces, i would like to just have the projector ignore those faces (not render my projection to them) but maintain their alpha. Does that make sense?

I don’t understand shader code enough to implement that change so I’m not sure how to achieve this. I would really appreciate any suggestions! Thanks

I don’t have enough knowledge as well but I think it must be something like this

instead of

o.uvFalloff.a = clamp(-1*dot(normalize(_ProjectorDir), v.normal),0,1);

use this

o.uvFalloff.rgb *= clamp(-1*dot(normalize(_ProjectorDir), v.normal),0,1);

this must make the projected texture black on back-facing area
but if you mean no texture on that part at all then its the same as making the alpha to zero like the solution you wrote cuz zero alpha means nothing to show.
however I havn’t tested this and I can’t guaranty if this gonna work or not.

As _ProjectorDir is in world space, so will v.normal have to be converted to world space:

float3 worldNormal = mul(unity_ObjectToWorld, float4(v.normal, 0.0)).xyz;
                o.uvFalloff.a = clamp(-dot(_ProjectorDir, worldNormal), 0, 1);

Here’s a shader i sort of poorly hacked together from this example and several others (like this Projector shader with angle limitation ) and includes a masking ability

I made it so that the cutoff angle was adjustable, but it doesn’t really make sense to me (and it might not be very efficient)

// Upgrade NOTE: replaced '_Projector' with 'unity_Projector'
// Upgrade NOTE: replaced '_ProjectorClip' with 'unity_ProjectorClip'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

// Upgrade NOTE: replaced '_Projector' with 'unity_Projector'

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

// Upgrade NOTE: replaced '_Projector' with 'unity_Projector'
// Upgrade NOTE: replaced '_ProjectorClip' with 'unity_ProjectorClip'

Shader "AdditiveMaskNoBack"
    {
        Properties
        {
            _Color("Main Color", Color) = (1,1,1,1)
            _ShadowTex("Cookie", 2D) = "" {}
        _MaskTex("Mask", 2D) = "gray" {}
            _FalloffTex("FallOff", 2D) = "" {}
            _AngleLimit("Angle Limit (rad)", Float) = .9
        }

            Subshader
            {
                Tags {"Queue" = "Transparent"}
                Pass
                {
                    ZWrite Off
                    AlphaTest Greater 0
                    ColorMask RGB
                    Blend SrcAlpha OneMinusSrcAlpha
                    Offset -1, -1

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

                    struct v2f {
                        float4 uvShadow : TEXCOORD0;
                        float4 uvMask : TEXCOORD1;
                        float4 uvFalloff : TEXCOORD2;
                        half projAngle : TEXCOORD3;
                        UNITY_FOG_COORDS(2)
                        float4 pos : SV_POSITION;
                    };

            struct vertexInput
            {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
            };

                    float4x4 unity_Projector;
                    float4x4 unity_ProjectorClip;
                    half3 projNormal;

                    inline half angleBetween(half3 vector1, half3 vector2)
                    {
                        return acos(dot(vector1, vector2) / (length(vector1) * length(vector2)));
                    }

                    v2f vert(float4 vertex : POSITION, float3 normal : NORMAL, float4 texcoord : TEXCOORD0)
                    {
                        v2f o;
                        o.pos = UnityObjectToClipPos(vertex);
                        o.uvShadow = mul(unity_Projector, vertex);
                        o.uvMask = mul(unity_Projector, vertex);
                        o.uvFalloff = mul(unity_ProjectorClip, vertex);
                        projNormal = mul(unity_Projector, normal);
                        o.projAngle = abs(angleBetween(half3(0,0,-1), projNormal));
                        UNITY_TRANSFER_FOG(o,o.pos);
                        return o;
                    }

                    fixed4 _Color;
                    sampler2D _ShadowTex;
                    float4 _ShadowTex_ST;
                    sampler2D _MaskTex;
                    sampler2D _FalloffTex;
                    half _AngleLimit;

                    fixed4 frag(v2f i) : SV_Target
                    {
                        if (i.projAngle <_AngleLimit) //||1)
                        {
                            fixed4 texS = tex2Dproj(_ShadowTex, UNITY_PROJ_COORD(i.uvShadow));
                            fixed4 mask = tex2Dproj(_MaskTex, UNITY_PROJ_COORD(i.uvMask));
                            texS.rgba *= _Color.rgba;
                            texS *= mask.a;

                            fixed4 texF = tex2Dproj(_FalloffTex, UNITY_PROJ_COORD(i.uvFalloff));
                            //fixed4 res = texS * texF.a;
                                //i.projAngle;

                            //fixed4 res = texS * texF.a * step(-_AngleLimit, i.projAngle);
                            fixed4 res = texS * texF.a* ceil(abs(i.uvFalloff.a));
                            //
                            UNITY_APPLY_FOG_COLOR(i.fogCoord, res, fixed4(0,0,0,0));
                            return res;
                        }
                        else
                        {
                            //return fixed4(.5, .5, 1, 1); positive purple alpha for debugging
                            return fixed4(.5, .5, 1, 0);

                        }
                    }
                    ENDCG
                }
            }
    }

If you want the multiply shadow and get rid of white background on texture,leave the alpha alone and clamp the uvshadow instead of falloff.a,this way white colors on otherside become invisible by multiply effect.

o.uvShadow *= clamp(-1*dot(normalize(_ProjectorDir),v.normal),0,1);

I had the same problem and found this thread looking for a fix.

I used monotoan’s solution (as well as Partel-Lang’s worldNormal) but I replaced this line:

o.uvFalloff.a = clamp(-1*dot(normalize(_ProjectorDir), v.normal),0,1);

with this one:

o.uvFalloff.a *= max(0, sign(-dot(_ProjectorDir, worldNormal)));

Since we only care about whether the surface is facing away from the light source, there is no need to normalize _ProjectorDir. We do want to avoid to get a range between 0 and 1 though because that would result in the shadow becoming lighter on angled surfaces. So I use sign to get full opacity for everything larger than 0. And max to avoid negative alpha.

Thanks for sharing your solutions.

No need for an extra variable; Unity gives you all the necessary tools already:

-normalize(mul((float3x3)unity_Projector, v.normal)).z

The 4th row/column in a matrix is only used for transforming positions, but if we are dealing with a vector, just cast the matrix to float3x3 and it will remain a vector in the same space. The rest will obtain the negated size of the vector in the projection forward direction ‒ 1 if directly opposite the projector, -1 for a parallel back face, and 0 if perpendicular.