Search Unity

Projector makes it look like has black borders and there aren't

Discussion in 'General Graphics' started by rubens268, May 29, 2019.

  1. rubens268

    rubens268

    Joined:
    Jan 11, 2015
    Posts:
    5
    Hi!

    Im trying to build my own Fog of War, I'm doing it with a projector, I alredy have it, with a custom shader that acts as a mask and with a projector.

    It works ok, but I have a problem, it looks like the mesh has a black border and looks odd



    upload_2019-5-29_20-4-7.png

    upload_2019-5-29_20-4-27.png



    upload_2019-5-29_20-7-47.png

    upload_2019-5-29_20-8-1.png

    I can attach more examples if u want.

    My projector shader is :
    Code (CSharp):
    1. // Upgrade NOTE: replaced '_Projector' with 'unity_Projector'
    2. // Upgrade NOTE: replaced '_ProjectorClip' with 'unity_ProjectorClip'
    3. // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
    4.  
    5. Shader "Projector/Multiply" {
    6.     Properties{
    7.         _ShadowTex("Cookie", 2D) = "gray" {}
    8.         _FalloffTex("FallOff", 2D) = "white" {}
    9.         _Alpha("Alpha", Range(0,1)) = 0.5
    10.  
    11.     }
    12.         Subshader{
    13.         Tags{ "RenderType" = "Transparent"  "Queue" = "Transparent" "IgnoreProjector" = "True" }
    14.         Pass{
    15.         ZWrite Off
    16.         Fog{ Color(1, 1, 1) }
    17.         AlphaTest Greater 0
    18.         ColorMask RGB
    19.         Blend DstColor  OneMinusSrcAlpha
    20.         Offset -1, -1
    21.  
    22.         CGPROGRAM
    23. #pragma vertex vert
    24. #pragma fragment frag
    25. #include "UnityCG.cginc"
    26.         struct v2f {
    27.         float4 uvShadow : TEXCOORD0;
    28.         float4 uvFalloff : TEXCOORD1;
    29.         float4 pos : SV_POSITION;
    30.     };
    31.     float4x4 unity_Projector;
    32.     float4x4 unity_ProjectorClip;
    33.     half  _Alpha;
    34.     v2f vert(float4 vertex : POSITION)
    35.     {
    36.         v2f o;
    37.         o.pos = UnityObjectToClipPos(vertex);
    38.         o.uvShadow = mul(unity_Projector, vertex);
    39.         o.uvFalloff = mul(unity_ProjectorClip, vertex);
    40.         return o;
    41.     }
    42.     sampler2D _ShadowTex;
    43.     sampler2D _FalloffTex;
    44.     fixed4 frag(v2f i) : SV_Target
    45.     {
    46.         fixed4 texS = tex2Dproj(_ShadowTex, UNITY_PROJ_COORD(i.uvShadow));
    47.         texS.a = 1.0 - texS.a;
    48.  
    49.         fixed4 texF = tex2Dproj(_FalloffTex, UNITY_PROJ_COORD(i.uvFalloff));
    50.         fixed4 res = lerp(fixed4(1,1,1,0), texS, texF.a);
    51.         res.a = _Alpha;
    52.         return res;
    53.     }
    54.         ENDCG
    55.     }
    56.     }
    57. }
     

    Attached Files:

  2. rubens268

    rubens268

    Joined:
    Jan 11, 2015
    Posts:
    5
    It happens too with textured mesh

    upload_2019-5-29_20-12-45.png
    upload_2019-5-29_20-12-57.png
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I was wondering if you were going to come back with this problem after your previous post.


    The short answer is "don't use projectors".


    The error comes from that "Offset -1, -1" line. Removing that line will remove the lines and black splotches, but it'll also result in some meshes having problems with z fighting where the entire surface will kind of flicker in streaks, which is why that lines exists in the first place.

    The issue is there's no guarantee that the projector's vertex shader output will exactly match the output of the vertex shader output of the shaders used on your objects. Very subtle differences in the data used will incur small floating point precision errors between the two, even if the results should be identical algebraically. This results in two polygon surfaces which, while may appear identical in the rendered image, are ever so slightly different leading to z fighting where the floating point math ends up with your projector's shader output being "behind" the surface it's supposed to be on top of. The "Offset" property tells the GPU to push the output depth slightly closer to the camera, with the two values denoting by how far and how much the surface facing should add to that. (How exactly that's calculated and in what units those values are in are dependent on the hardware and there's no enforced standard, so it may look different on different hardware.) This is a good work around most of the time, but it does cause the issue you see above when you have close or intersecting geometry. The reason is you're seeing through the offset projector geometry twice:

    Here's an example image to show you what is happening. Imagine the camera is looking from above, The two green surfaces are the polygon surfaces of some bushes or something. The dotted line is the offset projector surface. Notice how at the point the two shapes intersect, there's a small area where you're seeing through the projector from both shapes.
    upload_2019-5-29_12-26-9.png

    So, what are the solutions?

    The "best" option is, as I said at the top, don't use projectors. Instead you could use a post process effect based on the camera depth texture. You would extrapolate the world position from the depth texture, and project your fog of war onto that. Enabling the depth texture on the camera does add the extra cost of re-rendering the entire scene again to produce that texture, but you may already be doing this as Unity's main directional light shadows use the depth texture already; it projects the shadow map on to the world position extrapolated from the depth texture, which is almost exactly what you'd need to do. It also gets enabled by several other post processing effects and the "soft particles" setting under the project's quality settings. It'll also be generated already if you're using deferred rendering as all dynamic lighting on opaque objects uses the world position extrapolated from the depth texture.

    The next best option is to use custom shaders on all of your objects rather than a post process. Gives you way more control, and works on transparencies properly, which neither the projector nor post process method can do. You'd use global shader values and the world position in the fragment shader / surface shader surf function to calculate the UVs for your fog of war texture, and do the wanted darkening there.


    The last option, which is "ef it, I want to keep using projectors until they rip them from my cold, dead hands" solution, is to use stencils. Add this just inside your Pass.
    Code (csharp):
    1. Stencil {
    2.     Ref 1
    3.     Pass Replace
    4.     Comp NotEqual
    5. }
    That'll check if the stencil buffer has "1" written to it at that pixel, and either render and write "1" itself if it has not been, or skip rendering entirely if it has.
     
  4. rubens268

    rubens268

    Joined:
    Jan 11, 2015
    Posts:
    5
    U're rly nice, why aren't u covered of gold? Realy thanks another time :-D