Search Unity

Drawing lines with vertex shader. Calculating with space matrices

Discussion in 'Shaders' started by look001, Oct 2, 2019.

  1. look001

    look001

    Joined:
    Mar 23, 2017
    Posts:
    111
    Hi there,
    i'm want to do a shader that draws a line from a set of points. The idea is to make a quad of two triangles for each line segment. C# will create two vertices at the same position foreach point and build the triangles for the quad.
    The Vertices then need to be pushed apart from each other in a way that the line is facing to the camera. This sounds like a task for the shader. To calculate the push out normal the tangent(for each line segment) is passed for each vertex as a normal. The tangent is fliped for each vertex of a pair (to push them out in opposite directions). The idea is inspired by this post.

    Now to the shader...
    Code (CSharp):
    1. v2f vert (appdata v)
    2. {
    3.     v2f o;
    4.    
    5.     float3 tangent = v.normal;
    6.     float3 worldPos = mul((float3x3)unity_ObjectToWorld, v.vertex.xyz);
    7.    
    8.     float2 viewPos = mul(UNITY_MATRIX_V, worldPos);
    9.     float2 viewTangent = mul(UNITY_MATRIX_V, worldPos + tangent) - viewPos;
    10.     float2 viewNormal = normalize(float2(-viewTangent.y, viewTangent.x));
    11.    
    12.     float2 viewPosOut = viewPos + viewNormal * 0.5;
    13.    
    14.     o.vertex = mul(UNITY_MATRIX_P, viewPosOut);
    15.     o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    16.    
    17.     UNITY_TRANSFER_FOG(o,o.vertex);
    18.     return o;
    19. }
    i read the tangent then the worldPosition of the current vertex is calculated. With
    mul(UNITY_MATRIX_V, worldPos + tangent) - viewPos
    i calculate the tangent in viewspace and determine the normal (orthogonal to viewTangent) from the cameras view. Finally the pushed out viewPosition is calculated in viewPosOut and converted to projection space ready for the fragment shader.

    AFAIK this should work. However in game there is nothing on the sceen. The positions are all set right and the c# code also works (I checked it by calculating everything inside c# without the shader and the result were as expected)

    Here i'm stuck. What is the problem. Did i misunderstood some of the matrix multiplication and spaces?
     
    Last edited: Oct 24, 2019
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    The UNITY_MATRIX_V is a float4x4. Your worldPos is a float3. Using a float3 (or float4 with a w of zero) means it's acting as a rotation and scale transform, but not using the translation. That won't give you the view position. The worldPos needs to be a float4 with a w of 1. Similarly the view position is also a float4. Note that the view space isn't viewport or screen space, it's world scale space relative to the camera. It's basically the same position you would see if you took a game object and made it a child of the camera game object (assuming the camera isn't scaled).

    Lastly when transforming from view space to projection space (aka clip space, what the SV_Position output of the vertex shader should be in) both the input and output should still float4 values, with the view position needing a w of 1.
     
    look001 likes this.
  3. look001

    look001

    Joined:
    Mar 23, 2017
    Posts:
    111
    Thank you that improved my undestanding. I modified the shader:
    Code (CSharp):
    1. v2f vert (appdata v)
    2. {
    3.     v2f o;
    4.  
    5.     float3 tangent = v.normal;
    6.     float3 worldPos = mul((float3x3)unity_ObjectToWorld, v.vertex.xyz);
    7.  
    8.     float4 screenPos = UnityObjectToClipPos(worldPos);
    9.     float4 screenTangent = UnityObjectToClipPos(worldPos + tangent) - screenPos;
    10.     float4 screenNormal = normalize(float4(-screenTangent.y, screenTangent.x, 0, 1));
    11.  
    12.     float4 screenPosOut = screenPos + screenNormal * 0.5;
    13.  
    14.     o.vertex = screenPosOut;
    15.     o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    16.  
    17.     UNITY_TRANSFER_FOG(o,o.vertex);
    18.     return o;
    19. }
    now all the caculations happen in clip space and UnityObjectToClipPos sets w to 1, right?
    The line is drawn to screen. I noticed that depending on the distance of the line its width changes perspectively. However the screenNormal always is the same length so why is it doing this? Also i didn't quiet understand what the z and w in clip space, since i thought clip coordinates are two dimensional (-1 to 1 for the corresponding screen position)
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Again, 3D positions all need to be float4 values, not float3! This line is not transforming the vertex position into world space, it’s just rotating and scaling it as if it was placed at the world center. Do not convert the matrix to a float3x3! Keep it a float4x4 and use float4(v.vertex.xyz, 1.0) or the default v.vertex, which is already a float4 with a w of 1.

    Clip space position is not screen space position! Clip position is what the GPU uses to calculate the screen position, but it should not be used to compare positions, especially those that vary in depth. Clip space is in -w to w range for xyz, and w is the view depth. Dividing the clip space xyz by w gets you the normalized device coordinates, which is the -1 to 1 range for xyz you’re expecting.

    Once you are in screen space, you want to be doing the tangent and normal in 2D screen space as the other two components no longer make sense for doing orientation comparisons like you’re doing. Also you may need to correct for aspect ratio before doing anything in screen space. You’ll then need to convert back from screen space to clip space.
     
    Last edited: Oct 3, 2019
    look001 likes this.
  5. look001

    look001

    Joined:
    Mar 23, 2017
    Posts:
    111
    Thank you! I modified the code again to make it work:
    Code (CSharp):
    1. float3 tangent = v.normal;
    2.  
    3. float4 clipPos = UnityObjectToClipPos(v.vertex);
    4. float4 tangentClipPos = UnityObjectToClipPos(v.vertex + tangent);
    5.  
    6. float4 screenPos = clipPos / clipPos.w * _ScreenParams;
    7. float4 tangentScreenPos = tangentClipPos / clipPos.w * _ScreenParams;
    8.  
    9. float4 screenTangent = tangentScreenPos - screenPos;
    10. float4 screenNormal = normalize(float4(-screenTangent.y, screenTangent.x, 0, 1));
    11.  
    12. float4 screenPosOut = screenPos + screenNormal * 20;
    13. float4 clipPosOut = screenPosOut / _ScreenParams * clipPos.w;
    14.  
    15. o.vertex = clipPosOut;
    The calculation should be in screen space, its almost working. If the object is not in the center of the camera the lines are not at the right place. I read your comment about warping caused by the devision by w. What can i do to prevent it or is there a different approch without perspective devision? In general is it a good idea to render lines like this?
     
    Last edited: Oct 24, 2019