Search Unity

vert shader changing depth not working on iOS

Discussion in 'Shaders' started by Antony-Blackett, Mar 24, 2017.

  1. Antony-Blackett

    Antony-Blackett

    Joined:
    Feb 15, 2011
    Posts:
    1,778
    Hi, my shader (attached below) worked fine on all platforms until a recent update of unity (5.1.1). Now it no longer alters the depth as I intended it to, on iOS. It still works fine in the editor with Metal emulation.

    Note that I can't use Offset for this because Offset doesn't work consistently on Android and because it works in depth buffer space it's hard to know how much to offset things by.

    the intended result is for object that use this shader to appear ~5-10 meters in front of other objects without changing their apparent size.

    Code (csharp):
    1.  
    2.  
    3. Shader "Custom/VertextColourUnlitOnTop" {
    4.     Properties {
    5.         _Color ("Color", Color) = (1,1,1,1)
    6.         _MainTex ("Main Texture", 2D) = "white" {}
    7.         _DepthScale ("Depth Scale", Range(0,1)) = 1.0
    8.     }
    9.     SubShader {
    10.         Tags { "RenderType"="Opaque" }
    11.  
    12.      
    13.        Pass
    14.        {
    15.         CGPROGRAM
    16.         #pragma fragmentoption ARB_precision_hint_fastest
    17.         #pragma vertex vert
    18.         #pragma fragment frag
    19.  
    20.         #include "UnityCG.cginc"
    21.  
    22.         sampler2D _MainTex;
    23.         uniform half4 _MainTex_ST;
    24.         half _DepthScale;
    25.         half4 _Color;
    26.  
    27.         struct VertexInput
    28.         {
    29.             half4 color : COLOR;
    30.             half4 vertex : POSITION;
    31.             half4 texcoord : TEXCOORD0;
    32.         };
    33.      
    34.         struct FragmentInput
    35.         {
    36.             half4 position : SV_POSITION;
    37.             half4 color : COLOR;
    38.             half2 uv : TEXCOORD0;
    39.         };
    40.  
    41.  
    42.         FragmentInput vert ( VertexInput i )
    43.         {
    44.             FragmentInput o;
    45.             half4 worldPosition = mul ( unity_ObjectToWorld, i.vertex );
    46.             half4 cameraPos = half4(_WorldSpaceCameraPos, 1);
    47.             worldPosition = cameraPos + (worldPosition - cameraPos) * _DepthScale;
    48.             o.position = mul ( UNITY_MATRIX_MVP, mul( unity_WorldToObject, worldPosition ) );
    49.  
    50.             o.uv = TRANSFORM_TEX( i.texcoord, _MainTex );
    51.             o.color = i.color;
    52.             return o;
    53.         }
    54.      
    55.         half4 frag (FragmentInput i) : COLOR
    56.         {
    57.             return _Color * tex2D( _MainTex, i.uv ) * i.color;
    58.         }
    59.      
    60.         ENDCG
    61.         }
    62.     }
    63.     FallBack "Diffuse"
    64. }
    65.  
    66.  
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    There have actually been a few posts about this recently... not why your code doesn't work, but ways to do an offset like this. I'm actually a little surprised this works at all as there's a hand full of things that could cause little problems, though nothing specific that jumps out as the real issue.

    For example, you're using half and not float for the position. I guess it's fine if you keep your scenes relatively small and close to the scene's origin. It also wouldn't cause any problems with your code.

    Another issue is you're multiplying the un-normalized distance offset between the camera and the vertex, so the amount the object gets pushed towards the camera will depend on how far away it is. I've seen several people use that setup, but be aware a value of "0.5" means it'll get placed half way between it's actual location and the camera no matter how far away it is. Again, that's fine and is a perfectly valid way to do the depth offset, but not exactly how you described wanting it to work.

    The last issue is line 46 and 47 are doing math on all 4 components of the vector, and you should limit it to only the first 3. Looking over the code you're actually getting a little lucky and it should still work, but it's still best to avoid it. If I was to keep the code working roughly as is I would remove line 46 entirely and replace lines 45, 47 & 48with:

    float4 worldPosition = mul(unity_ObjectToWorld, v.vertex);
    worldPosition.xyz = lerp(worldPosition.xyz, _WorldSpaceCameraPos.xyz, _DepthScale);
    o.position = mul(UNITY_MATRIX_VP, worldPosition);


    In general unless you really know what you're doing you shouldn't ever mess with the w component of a position, or always set it to one before applying a transform matrix (or zero if you only want a rotation). The way line 47 was written you were avoiding it being a problem because you were effectively doing 1+(1-1)*_DepthScale, which is always 1.

    That last line also avoids doing two matrix multiplications and just transforms world space to clip space with out needing to transform back to object space.

    So, as for why your original code is not working for you on iOS, I have no idea, but you can try that modification and see if it fixes it for you. If you want to be able to adjust the distance by a specific amount rather than just some percentage distance between the camera and the vertex you can try searching for the forums for other solutions, but I'll admit they're hard to find since they're usually responses to more general "how do I make this work" questions. If you want you can try this bit of code though:
    https://forum.unity3d.com/threads/move-eyes-with-shaders.387895/#post-2974534
     
    Antony-Blackett likes this.
  3. Antony-Blackett

    Antony-Blackett

    Joined:
    Feb 15, 2011
    Posts:
    1,778
    Thanks bgolus,

    Extremely helpful. I'll give the lerp a try and let you know if this solves the issue on iOS.
     
  4. Antony-Blackett

    Antony-Blackett

    Joined:
    Feb 15, 2011
    Posts:
    1,778
    So, your solution works, however this has reminded me as to why my solution was so roundabout in the first place. It seems Unity does not set the _WorldSpaceCameraPos variable when the editor camera is rendering the scene view so you end up with this strange result in the scene window.

    Screen Shot 2017-03-27 at 3.15.50 PM.png

    As you can see the selected mesh outline is no where near the shader result (red pointy circle). Needless to say this makes selecting objects in the scene view a real pain in the backside...

    I'll make some more attempts (hacks) at it and see if I can get the best of both worlds..

    Cheers for the help.

    Edit: Actually my assumption that the editor camera does not set _WorldSpaceCameraPos is just a guess. But I can't think why else I would get this result.
     
  5. Antony-Blackett

    Antony-Blackett

    Joined:
    Feb 15, 2011
    Posts:
    1,778
    For now, this is my solution that seems to work for all platforms i'm interested dealing with at this time

    Code (csharp):
    1.  
    2.  
    3.         FragmentInput vert ( VertexInput i )
    4.         {
    5.             FragmentInput o;
    6.  
    7.             float4 worldPosition = mul ( unity_ObjectToWorld, i.vertex );
    8.             worldPosition.xyz = lerp(_WorldSpaceCameraPos, worldPosition.xyz, _DepthScale);
    9.             o.position = mul(UNITY_MATRIX_VP, worldPosition);
    10.  
    11.             #if !SHADER_API_MOBILE // In the editor
    12.             float4 mulPos = mul ( UNITY_MATRIX_MVP, i.vertex );
    13.             o.position.xy = mulPos.xy;
    14.             o.position.w = mulPos.w;
    15.             #endif
    16.  
    17.             o.uv = TRANSFORM_TEX( i.texcoord, _MainTex );
    18.             o.color = i.color;
    19.             return o;
    20.         }
    21.  
    22.  
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    It's a fair guess. You can try using this instead:

    float3 cameraPos = -UNITY_MATRIX_V._m03_m13_m23;

    That should be the same as _WorldSpaceCameraPos, but guaranteed to be for the camera currently rendering, as long as you're not using it in an image effect shader. That's extracting the offset in the view matrix used for transforming from world space to camera space.
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    This will certainly make it render something, but if your scene camera and game camera are at different distances, and your assumption is correct, you might have the effect render behind or on top of stuff it shouldn't in the scene view.
     
  8. Antony-Blackett

    Antony-Blackett

    Joined:
    Feb 15, 2011
    Posts:
    1,778
    Yes and it does. But this is only a slight annoyance compared to not being able to select things in the scene window by clicking on where they appear to be. I only need it to look correct in the game view and be good enough for workflow in the scene view.

    Code (csharp):
    1.  
    2. float3 cameraPos = -UNITY_MATRIX_V._m03_m13_m23;
    3.  
    This doesn't seem to work at all and I can't figure out why it wouldn't. Oh well, it's working sufficiently for my needs. If I need to revisit it later I'll come back to this thread and pull some more knowledge.
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    You might try unity_CameraToWorld._m03_m13_m23 instead, though I'm not having the issue you're having with the scene view so this might end up actually being identical to _WorldSpaceCameraPos. unity_CameraToWorld and UNITY_MATRIX_V aren't quite the same thing, and I always forget which one is the best one to use for stuff like this.

    Another alternative is this:
    float4 viewPosition = mul(UNITY_MATRIX_MV, v.vertex);
    viewPosition.xyz = viewPosition.xyz * (1.0 - _DepthScale);
    o.position = mul(UNITY_MATRIX_P, viewPosition);


    It doesn't need the camera position at all, or even a lerp.
     
  10. Antony-Blackett

    Antony-Blackett

    Joined:
    Feb 15, 2011
    Posts:
    1,778
    This code also works in the game view but not in the scene view.
     
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    That ... shouldn't be possible. Honestly that sounds like you're running across a bug of some kind causing the scene view camera's rendering to get those matrix values corrupted.
     
  12. DigitalDesignDude

    DigitalDesignDude

    Joined:
    Mar 25, 2020
    Posts:
    3
    I know this is an older post but I wanted to ask what exactly is the alternative if you cannot use Offset in your shader?

    I'm new to shaders and am trying to figure out what code you're using to control the amount of offset and have it consistent on all devices.

    This is something I'm trying to achieve in my game where I have billboarding spites that require an offset to prevent them from clipping into the game's 3D enviroment geometry. Please see my post for more details.
     
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Here's a more complete solution that lets you adjust the depth offset in world space units.
    upload_2020-5-7_1-0-3.png
    Code (csharp):
    1. Shader "Unlit/CameraPush"
    2. {
    3.     Properties
    4.     {
    5.         _Color ("Color", Color) = (1,1,1,1)
    6.         _MainTex ("Texture", 2D) = "white" {}
    7.         _DepthOffset ("Depth Offset (Negative Towards Camera)", Float) = -1
    8.     }
    9.     SubShader
    10.     {
    11.         Tags { "Queue"="Transparent" "RenderType"="Transparent" }
    12.         LOD 100
    13.  
    14.         Pass
    15.         {
    16.             ZWrite Off
    17.             Blend SrcAlpha OneMinusSrcAlpha
    18.  
    19.             CGPROGRAM
    20.             #pragma vertex vert
    21.             #pragma fragment frag
    22.             // make fog work
    23.             #pragma multi_compile_fog
    24.  
    25.             #include "UnityCG.cginc"
    26.  
    27.             struct v2f
    28.             {
    29.                 float4 pos : SV_POSITION;
    30.                 float2 uv : TEXCOORD0;
    31.                 UNITY_FOG_COORDS(1)
    32.             };
    33.  
    34.             fixed4 _Color;
    35.             sampler2D _MainTex;
    36.             float4 _MainTex_ST;
    37.             float _DepthOffset;
    38.  
    39.             v2f vert (appdata_full v)
    40.             {
    41.                 v2f o;
    42.  
    43.                 // world position
    44.                 float3 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)).xyz;
    45.  
    46.                 // camera relative world position
    47.                 // often called "world space view direction", but is a full offset vector, not just a normalized direction
    48.                 float3 camRelativeWorldPos = worldPos - _WorldSpaceCameraPos;
    49.  
    50.                 //  world space camera forward vector
    51.                 // no normalize needed since the view matrix is assumed to be unscaled
    52.                 float3 camForward = mul((float3x3)UNITY_MATRIX_I_V, float3(0.0,0.0,-1.0));
    53.  
    54.                 // view depth of the camera relative position
    55.                 float viewDepth = dot(camForward, camRelativeWorldPos);
    56.  
    57.                 // scale the camera relative position to be at a view space depth of 1
    58.                 float3 depthNormalizedPos = camRelativeWorldPos / viewDepth;
    59.  
    60.                 // get the new target view depth, clamped just outside the near clip plane
    61.                 float newDepth = max(_ProjectionParams.y + 0.00001, viewDepth + _DepthOffset);
    62.  
    63.                 // depth offset world position
    64.                 float3 newWorldPos = depthNormalizedPos * newDepth + _WorldSpaceCameraPos;
    65.  
    66.                 // new depth offset position's clip space position
    67.                 float4 newClipPos = mul(UNITY_MATRIX_VP, float4(newWorldPos, 1.0));
    68.  
    69.                 // unmodified clip space position
    70.                 o.pos = UnityObjectToClipPos(v.vertex);
    71.  
    72.                 // do fog here so it's based off of the original position
    73.                 UNITY_TRANSFER_FOG(o,o.pos);
    74.  
    75.                 // override only the clip space z
    76.                 // this ensures no weird stretching or distortion in the textures since
    77.                 // the uv perspective correction is based purely on the xy&w clip space
    78.                 o.pos.z = newClipPos.z / newClipPos.w * o.pos.w;
    79.  
    80.                 // regular shader stuff
    81.                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    82.                 return o;
    83.             }
    84.  
    85.             fixed4 frag (v2f i) : SV_Target
    86.             {
    87.                 fixed4 col = tex2D(_MainTex, i.uv) * _Color;
    88.                 UNITY_APPLY_FOG(i.fogCoord, col);
    89.                 return col;
    90.             }
    91.             ENDCG
    92.         }
    93.     }
    94. }
     
  14. DigitalDesignDude

    DigitalDesignDude

    Joined:
    Mar 25, 2020
    Posts:
    3
    @bgolus Thank you for taking the time to put that together!

    I attempted to add some billboard code to it from this tutorial, but my sprites seem to flash in and out of sight with it.

    Animated Gif: https://i.imgur.com/f0G6HDq.gifv


    Combing features from different custom shaders is something I want to get better at but I guess it's not as simple as just copying over some lines of code.

    I figure modifying
    o.pos
    is the way to get the billboard effect but something must be conflicting to cause the sprites to disappear. All the sprites have the default 0,0,0 rotation when instantiated and no other code assigned to them other than in their shader.

    Is there anything more you can suggest?


    Code (CSharp):
    1. Shader "Unlit/PushToCamera"
    2. {
    3.     Properties
    4.     {
    5.         _Color ("Color", Color) = (1,1,1,1)
    6.         _MainTex ("Texture", 2D) = "white" {}
    7.         _DepthOffset ("Depth Offset (Negative Towards Camera)", Float) = -1
    8.     }
    9.     SubShader
    10.     {
    11.         Tags { "Queue"="Transparent" "RenderType"="Transparent" }
    12.         LOD 100
    13.         Pass
    14.         {
    15.             ZWrite Off
    16.             Blend SrcAlpha OneMinusSrcAlpha
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.             // make fog work
    21.             #pragma multi_compile_fog
    22.             #include "UnityCG.cginc"
    23.             struct v2f
    24.             {
    25.                 float4 pos : SV_POSITION;
    26.                 float2 uv : TEXCOORD0;
    27.                 UNITY_FOG_COORDS(1)
    28.             };
    29.             fixed4 _Color;
    30.             sampler2D _MainTex;
    31.             float4 _MainTex_ST;
    32.             float _DepthOffset;
    33.  
    34.             v2f vert (appdata_full v)
    35.             {
    36.                 v2f o;
    37.                 // world position
    38.                 float3 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)).xyz;
    39.                 // camera relative world position
    40.                 // often called "world space view direction", but is a full offset vector, not just a normalized direction
    41.                 float3 camRelativeWorldPos = worldPos - _WorldSpaceCameraPos;
    42.                 //  world space camera forward vector
    43.                 // no normalize needed since the view matrix is assumed to be unscaled
    44.                 float3 camForward = mul((float3x3)UNITY_MATRIX_I_V, float3(0.0,0.0,-1.0));
    45.                 // view depth of the camera relative position
    46.                 float viewDepth = dot(camForward, camRelativeWorldPos);
    47.                 // scale the camera relative position to be at a view space depth of 1
    48.                 float3 depthNormalizedPos = camRelativeWorldPos / viewDepth;
    49.                 // get the new target view depth, clamped just outside the near clip plane
    50.                 float newDepth = max(_ProjectionParams.y + 0.00001, viewDepth + _DepthOffset);
    51.                 // depth offset world position
    52.                 float3 newWorldPos = depthNormalizedPos * newDepth + _WorldSpaceCameraPos;
    53.                 // new depth offset position's clip space position
    54.                 float4 newClipPos = mul(UNITY_MATRIX_VP, float4(newWorldPos, 1.0));
    55.                 // unmodified clip space position
    56.                 //o.pos = UnityObjectToClipPos(v.vertex);
    57.  
    58.                 //Billboard Code =======================================================
    59.            
    60.                 float4 world_origin = mul(UNITY_MATRIX_M, float4(0,0,0, 1));
    61.                 float4 view_origin = float4(UnityObjectToViewPos(float3(0,0,0)), 1);
    62.  
    63.                 float4 world_pos = mul(UNITY_MATRIX_M, v.vertex);
    64.            
    65.                 float4 view_pos =  world_pos - world_origin + view_origin;
    66.  
    67.                 float4 clip_pos = mul(UNITY_MATRIX_P, view_pos);
    68.  
    69.                 o.pos = clip_pos;
    70.            
    71.                 //End of Billboard Code ==============================================
    72.  
    73.  
    74.                 // do fog here so it's based off of the original position
    75.                 UNITY_TRANSFER_FOG(o,o.pos);
    76.                 // override only the clip space z
    77.                 // this ensures no weird stretching or distortion in the textures since
    78.                 // the uv perspective correction is based purely on the xy&w clip space
    79.                 o.pos.z = newClipPos.z / newClipPos.w * o.pos.w;
    80.                 // regular shader stuff
    81.                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    82.                 return o;
    83.             }
    84.             fixed4 frag (v2f i) : SV_Target
    85.             {
    86.                 fixed4 col = tex2D(_MainTex, i.uv) * _Color;
    87.                 UNITY_APPLY_FOG(i.fogCoord, col);
    88.                 return col;
    89.             }
    90.             ENDCG
    91.         }
    92.     }
    93. }
     
  15. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    You need to do all of my code after the bill boarding code, using the calculated billboard position. My code also does a lot of stuff in world space rather than view space just to avoid that extra full matrix multiply, but that view facing billboard shader already does all its work in view space so there’s no need. Just use the view position instead. View depth is just the
    -view_pos.z
    , and you don’t need to worry about
    _WorldSpaceCameraPos
    anymore. All the code simplifies down a bunch once it’s in view space.