Search Unity

Question Having trouble converting a world space position to screen space in a shader

Discussion in 'Shaders' started by Roggi_, Sep 30, 2022.

  1. Roggi_

    Roggi_

    Joined:
    May 14, 2014
    Posts:
    84
    Hi, I am having trouble converting a 3d world position to a 2d screen position.
    I"ve been at it for a while, trying every code snipped I could find but the results are always wrong.
    Iam trying to do a simple example where a point in world space is rendered as a red circle on screen from any view angle.

    Code (CSharp):
    1.  
    2.             HLSLPROGRAM
    3.             half4 frag(Varyings IN) : SV_TARGET
    4.             {
    5.  
    6.                
    7.                float3 wpos = float3(0,0,4);    
    8.  
    9.                float4 cpos = mul(UNITY_MATRIX_VP, float4(wpos.xyz, 1.0));
    10.                float4 spos = ComputeScreenPos(cpos);
    11.                spos.xy /= spos.w;
    12.  
    13.  
    14.                if (distance(IN.uv, spos.xy) < 0.05)
    15.                    return float4(1,0,0,0);
    16.              
    17.                float4 tex = SAMPLE_TEXTURE2D(_MainTex, sampler_point_clamp, IN.uv);
    18.              
    19.                return tex;
    20.             }
    21.             ENDHLSL
    I've tried this:
    https://forum.unity.com/threads/sha...npos-like-camera-worldtoviewportpoint.562078/
    and this:
    https://forum.unity.com/threads/player-position-in-world-space-to-screen-position.595861/
    and this:
    https://forum.unity.com/threads/shader-to-map-camera-space-to-world-space.526838/
    and this:
    https://forum.unity.com/threads/how...en-space-in-framgment-shader-function.219843/


    but the results are always wrong. Either the sphere is not moving at all, or it's not on the target.
    What am I doing wrong?

    Iam using URP btw, if it makes a difference.
     
  2. DevDunk

    DevDunk

    Joined:
    Feb 13, 2020
    Posts:
    5,042
    What are you trying to make?
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,338
    So, you need to understand what this matrix is.

    This transforms from the current world position to the clip space position … for the geometry that is currently being rendered. The fact you’re comparing the calculated screen position to the uv makes me believe this is being done for a post process effect. When rendering a post process, the geometry that’s being rendered is a single quad or triangle that’s covering the entire screen. The easiest way to do that is by having the “world” be the camera plane, and thus the “world to clip space” matrix is essentially empty. It is not the world space of the scene anymore. So you can’t use it to get a clip space position of something in the game scene.

    For that you need to use the
    unity_WorldToCamera
    and
    unity_CameraProjection
    matrices. Check the above links you posted for examples that use those specifically.
     
    Roggi_, lilacsky824 and DevDunk like this.
  4. Roggi_

    Roggi_

    Joined:
    May 14, 2014
    Posts:
    84
    Thanks, yes I forgot to mention that I am indeed trying to work with post processing.
    I've tried the snipped in the first link before and this is the result I am getting:

    https://imgur.com/yLnVDqL

    Code (CSharp):
    1.             HLSLPROGRAM
    2.             half4 frag(Varyings IN) : SV_TARGET
    3.             {
    4.  
    5.                float3 wpos = float3(0,0,4);    
    6.                wpos = normalize(wpos - _WorldSpaceCameraPos) * (_ProjectionParams.y + (_ProjectionParams.z - _ProjectionParams.y)) + _WorldSpaceCameraPos;
    7.  
    8.                float2 res = float2(0,0);
    9.                float3 cpos = mul(unity_WorldToCamera, wpos);
    10.                float camPosZ = cpos.z;
    11.                float height = 2 * camPosZ / unity_CameraProjection._m11;
    12.                float width = _ScreenParams.x / _ScreenParams.y * height;
    13.                res.x = (cpos.x + width / 2) / width;
    14.                res.y = (cpos.y + height / 2) / height;
    15.  
    16.  
    17.                if (distance(IN.uv, res.xy) < 0.05 )
    18.                    return float4(1,0,0,0);
    19.              
    20.                float4 tex = SAMPLE_TEXTURE2D(_MainTex, sampler_point_clamp, IN.uv);
    21.              
    22.                return tex;
    23.             }
    24.             ENDHLSL
    It works, but there is an issue with it where it also draws an inverted positioned circle when I turn around and look away from the stated world position.

    The other issue with it is that I have no idea why it works or what it does.
    Why does the wpos need to be processed first?
    Why does it need that unity_CameraProjection._m11 single matrix value?
    What are those height, width, divide by 2 transformations?
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,338
    To answer some of these questions, I'm going to start by correcting / simplifying a few things in the above code:

    Code (csharp):
    1. float4 tex = SAMPLE_TEXTURE2D(_MainTex, sampler_point_clamp, IN.uv);
    2.  
    3. float3 circle_worldPos = float3(0,0,4);
    4. // this avoids the extra line of code to subtract the world space camera position
    5. float4 circle_cameraPos = mul(unity_WorldToCamera, float4(circle_worldPos, 1.0));
    6.  
    7. // if behind the camera, ignore
    8. if (circle_cameraPos.z <= 0.0)
    9.   return tex;
    10.  
    11. // the WorldToCamera matrix is +z forwad, but the projection matrix expects a -z forward view space
    12. circle_cameraPos.z = -circle_cameraPos.z;
    13.  
    14. // transform view space to clip space position
    15. float4 circle_clipPos = mul(unity_CameraProjection, circle_cameraPos);
    16.  
    17. // clip space has a -w to +w range for on screen elements, so divide x and y by w to get a -1 to +1 range
    18. // then multiply by 0.5 and 0.5 to bring from a -1 to +1 range to 0.0 to 1.0 screen position UV
    19. float2 circle_screenPos = (circle_clipPos.xy / circle_clipPos.w) * 0.5 + 0.5;
    20.  
    21. // offset between the circle center and the screen position
    22. float2 circle_screenOffset = IN.uv - circle_screenPos;
    23.  
    24. // get the aspect ratio of the screen and adjust the x axis of the offset so you get a circle instead of an oval
    25. float aspect = _ScreenParams.x/_ScreenParams.y;
    26. circle_screenOffset.x *= aspect;
    27.  
    28. float dist_to_circle = length(circle_screenOffset);
    29.  
    30. if (dist_to_circle < 0.05)
    31.   return float4(1,0,0,0);
    32.  
    33. return tex;
     
    Last edited: Oct 7, 2022
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,338
    Most of the code you used was trying to account for a few mistakes they made. Like they used a
    float3
    in the
    mul
    with
    unity_WorldToCamera
    , which only applies the rotation from that matrix. You need to use a
    float4
    with a w of 1.0 for it to apply the transform too. And then they are taking a camera space position (which is a position relative to the camera, like if you move an object to be a child of the Camera game object) and then are using the camera space depth (
    cpos.z
    ), the projection's FOV (
    unity_CameraProjection._m11
    ), and the screen's aspect ratio to try and rescale that camera space position back into screen space. And it all kind of works, but is just way more complicated than it needed to be.
     
  7. Roggi_

    Roggi_

    Joined:
    May 14, 2014
    Posts:
    84
    Thank you very much for the explanation.

    btw, at line 28. it should be
    length(circle_screenOffset)
     
    bgolus likes this.
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,338
    yarp

    I just typed that out, didn’t test it, so there absolutely could be bugs.:D
     
    Roggi_ likes this.