Search Unity

  1. If you have experience with import & exporting custom (.unitypackage) packages, please help complete a survey (open until May 15, 2024).
    Dismiss Notice
  2. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice

How to make a correct projector with camera depth check?

Discussion in 'Shaders' started by KILEYI, Jun 21, 2018.

  1. KILEYI

    KILEYI

    Joined:
    Mar 7, 2015
    Posts:
    52
    What I need is a projector which can project image to multiple meshes and if the front mesh block the projector light,the back ones will not get projected texture.

    I don't know how to compare depth texture with mesh frag position.

    Here is the wrong result I got.The red curve lines I draw is what's wrong.

    Here is the Unity project I uploaded.

    https://github.com/mindoff6502/Projector_Test



    And here is the shader

    Code (CSharp):
    1. Shader "Projector/ProjectorCustom_Depth"
    2. {
    3.     Properties
    4.     {
    5.         _ShadowTex("Cookie", 2D) = "gray" {}
    6.         _FalloffTex("FallOff", 2D) = "white" {}
    7.         _ProjectorPos ("_ProjectorPos", Vector) = (0,0,0,0)
    8.         _Factor ("_Factor", float) = 1
    9.         _DepthTex("DepthTex", 2D) = "gray" {}
    10.     }
    11.         Subshader
    12.         {
    13.             Tags{ "Queue" = "Transparent" }
    14.             Pass{
    15.             ZWrite Off
    16.             ColorMask RGB
    17.             Blend SrcAlpha OneMinusSrcAlpha
    18.             Offset -1, -1
    19.  
    20.             CGPROGRAM
    21.             #include "UnityCG.cginc"
    22.  
    23.             #pragma vertex vert
    24.             #pragma fragment frag
    25.             #pragma multi_compile_fog
    26.             #include "UnityCG.cginc"
    27.  
    28.             struct v2f
    29.             {
    30.                 float4 uvShadow : TEXCOORD0;
    31.                 float4 uvFalloff : TEXCOORD1;
    32.                 float4 worldPos : TEXCOORD2;
    33.                 float4 viewPos : TEXCOORD3;
    34.                 float4 projPos : TEXCOORD4;
    35.              
    36.                 float4 worldNormal : TEXCOORD5;
    37.                 float4 pos : SV_POSITION;
    38.  
    39.             };
    40.  
    41.             struct VertexInput
    42.             {
    43.                 float4 vertex : POSITION;
    44.                 float2 uv   : TEXCOORD0;
    45.                 fixed3 normal : NORMAL;
    46.             };
    47.  
    48.             float4x4 unity_Projector;
    49.             float4x4 unity_ProjectorClip;
    50.  
    51.             float4 _ProjectorPos;
    52.  
    53.             float4x4 viewMatrix;
    54.             float4x4 projMatrix;
    55.  
    56.             float _Factor;
    57.  
    58.             v2f vert(VertexInput v)
    59.             {
    60.                 v2f o;
    61.                 o.pos = UnityObjectToClipPos(v.vertex);
    62.                 o.uvShadow = mul(unity_Projector, v.vertex);
    63.                 o.uvFalloff = mul(unity_ProjectorClip, v.vertex);
    64.              
    65.                 o.worldNormal = normalize(mul(UNITY_MATRIX_M,v.normal));
    66.  
    67.                 o.worldPos = mul(UNITY_MATRIX_M,v.vertex);
    68.                 o.viewPos = mul(viewMatrix, o.worldPos);
    69.                 o.projPos = mul(projMatrix, o.viewPos);              
    70.  
    71.                 return o;
    72.             }
    73.  
    74.             sampler2D _ShadowTex;
    75.             sampler2D _DepthTex;
    76.             sampler2D _FalloffTex;
    77.  
    78.             fixed4 frag(v2f i) : SV_Target
    79.             {
    80.                 float4 texS = tex2Dproj(_ShadowTex, UNITY_PROJ_COORD(i.uvShadow));
    81.  
    82.                 float4 dir2Projector = _ProjectorPos - i.worldPos;
    83.                 float dir = dot(normalize(dir2Projector), i.worldNormal);
    84.                 i.uvFalloff.a = dir > 0 ? 1 : 0;
    85.  
    86.                 float4 finalColor = texS * i.uvFalloff.a;
    87.  
    88.                 float4 projectTexCoord = float4(0,0,0,1);
    89.                 projectTexCoord.x = i.projPos.x / i.projPos.w / 2.0f + 0.5f;
    90.                 projectTexCoord.y = i.projPos.y / i.projPos.w / 2.0f + 0.5f;
    91.                 projectTexCoord.z = i.projPos.z / i.projPos.w / 2.0f + 0.5f;
    92.  
    93.                 float4 depthColor = tex2Dproj(_DepthTex, (i.uvShadow));
    94.  
    95.                 if(0 <= projectTexCoord.x && projectTexCoord.x <= 1 &&
    96.                     0 <= projectTexCoord.y && projectTexCoord.y <= 1 &&
    97.                     0 <= i.projPos.z
    98.  
    99.                     )
    100.                 {
    101.  
    102.                 }
    103.                 else
    104.                 {
    105.                     finalColor.a = 0;
    106.                 }
    107.  
    108.                 return finalColor;
    109.             }
    110.             ENDCG
    111.         }
    112.     }
    113. }
     
    Last edited: Jun 21, 2018
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,366
    Short version: You can't.

    Long version:
    Projectors alone lack the necessary information to do what you want. You need to generate a depth texture or shadow map (which is just a form of depth texture) from the point of view of the projector and pass that along to the projector's material. There aren't any working examples of this that I know of on the forums or even on the asset store. There are example projects showing how to generate custom shadow maps in various ways, but there aren't good examples of how to match the projector's matrix.
     
  3. KILEYI

    KILEYI

    Joined:
    Mar 7, 2015
    Posts:
    52
    So,how is other engine or DX or OpenGL do this?
    I'm sure I have seen some games have correct projector like I said,Can't remember name though.
    And strange thing is I check some shadowmap example how to check depth texture with frag position.
    Only works for non projector shader,if I use it in projector shader,I still got wrong result.
    Really make me mad.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,366
    This has nothing to do with the graphics API, it's simply a feature that Unity lacks built in support for.

    A projector and a spotlight in Unity share a lot in common, at least when using the forward rendering path. I'm going to go over what a spot light does real quick and then the projector.

    Spotlights in Unity these are treated as point lights with a frustum projected texture. Unity spotlights are actually pyramid shaped, a frustum, and the roundness is from the default light cookie, which is a projected grey scale texture. For legacy reasons, a light cookie can only be greyscale (actually the texture's alpha) when used with Unity's shaders. The way Unity's lighting system works is it re-renders every mesh a light's bounds overlaps with an additional time with the information for that light passed to the appropriate shader pass in the shader that mesh's material uses. These are the Forward Add passes that some shaders have. For shadows, Unity renders out a depth texture from the point of view of the spot light, then passes that depth texture to those shader passes, along with setting some keywords to let the shader know to use them.

    Projectors work almost exactly the same as spotlights. There are a few big differences though. Just like with spotlights, it does an overlap test with its bounds and meshes in the scene and re-renders those meshes. But instead of using the shader assigned to the mesh, it uses the projector's shader instead. Because it's not using the mesh's shader the projected texture can be used however you want, and isn't limited to being greyscale. Additionally projectors pass in a second projection matrix so that the falloff can be controlled easily by another texture. Also unlike spotlights though, Unity has no option to generate a depth texture for use with shadowing for the projector. As I said before, you have to generate and provide that yourself. This is more than just adding a few additional lines to the projector's shader code.


    The high level overview version of how to go about this is this:
    1. Create a camera component on the projector with matching projection settings (fov, near / far clip, orthographic).
    2. Render a depth texture or shadow map texture from the camera using a render texture and a replacement shader.
    3. Pass that render texture to the projector's material.
    4. Write a projector shader that looks at that render texture and exclude shadowed pixels.
    There are lots of fun gotchas like projector and camera projection matrices not quite lining up which might require you manually calculate your own projection matrix for the camera, or doing a bit more magic in the shader to get the two to work together, or pass the camera's matrix to the projector's shader instead of using the one automatically supplied. In short you have to do pretty much all of the work Unity does internally for spotlights yourself, while also reverse engineering some of the oddities with Unity.


    Other game engines I've used have support for full color "light cookies", so as long as an additive projection is okay you could just use a spotlight. And indeed if you write your own custom shaders for Unity you can use full color light cookies as well. The Alloy shader framework adds this feature for example, but if you write your own vertex fragment shader you could modify the spotlight ForwardAdd pass to behave however you want. Some other engines have support for generating a shadow map for their projectors. I've even used engines which have had no formal spotlight type at all and you had to use projectors to fake it.
     
    simonpasi_XR likes this.
  5. KILEYI

    KILEYI

    Joined:
    Mar 7, 2015
    Posts:
    52
    Thank you for the explaination,after a few days,still can't solve the problem.
    I decide to give it up.