Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

How to get 3d position of fragment?

Discussion in 'Shaders' started by drhenry, Apr 11, 2010.

  1. drhenry

    drhenry

    Joined:
    Jan 13, 2010
    Posts:
    9
    Hi all,

    I'd like to code an effect, where a transparent wall glows locally where a ball hits it (it's my first shader).
    I thought it might look good, if the glow (opacity) gets lower the farther away the fragment is from the current intersection point of ball and wall.
    So I transform the vertex position in the vertex shader into eye coordinates (at least I hope that I do so). In the fragment shader I have to get the fragment in eye coordinates, too, to get the distance to the intersection point, don't I? How can I compute it?

    So far I wrote this template:

    Code (csharp):
    1. Shader "PinballShaders/BoundaryGlowShader"
    2. {
    3.     Properties
    4.     {
    5.         _Color ("Main Color", Color) = (0.1, 0.3, 0.7, 0.25)
    6.         _BallPos("Pinball position", Vector) = (0,0,0,0)
    7.         //Min and max dist indicate the size of the effect when
    8.         //pinball comes near the wall
    9.         _MinDist("Minimum distance", Float) = 0.1
    10.         _MaxDist("Maximum distance", Float) = 0.5
    11.         _MainTex ("Base (RGBA)", 2D) = "white" {}
    12.     }
    13.     SubShader
    14.     {
    15.         Blend SrcAlpha OneMinusSrcAlpha
    16.         Cull Off
    17.  
    18.         Pass
    19.         {
    20. CGPROGRAM
    21. #pragma vertex vert
    22. #pragma fragment frag
    23.  
    24. #include "UnityCG.cginc"
    25.  
    26. float4 _Color;
    27. float4 _BallPos;
    28. float _MinDist;
    29. float _MaxDist;
    30. sampler2D _MainTex;
    31.  
    32. struct v2f {
    33.     float4 pos : POSITION;
    34.     float4 color : COLOR0;
    35. };
    36.  
    37. v2f vert (appdata_base v)
    38. {
    39.     v2f o;
    40.     o.pos = mul (glstate.matrix.mvp, v.vertex);
    41.     o.color = _Color;
    42.     return o;
    43. }
    44.  
    45. half4 frag (v2f i) : COLOR
    46. {
    47.     return i.color;
    48. }
    49. ENDCG
    50.         }
    51.     }
    52.     FallBack "VertexLit"
    53. }
     
  2. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    You can do your distance and colour calculation in the vertex shader. You'll get the same result because distance is linear with respect to position. You already know the object space position of the vertex. Just move it into world space by multiplying it by the model matrix, and subtract from the glow source position.
     
  3. horsman

    horsman

    Joined:
    Jul 19, 2008
    Posts:
    156
  4. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    Good point. Horsman's solution is better, because it doesn't do the matrix multiplication inside the shader.
     
  5. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    Aaaand I'm wrong again. Neither mine nor Horsman's approach is correct, because distance does not vary linearly with position. Position varies linearly, though (obviously), so you can pass the object space position of the effect, along with the object space position of the vertex (the untransformed vertex) to the fragment shader, and do the distance calculation there.
     
  6. drhenry

    drhenry

    Joined:
    Jan 13, 2010
    Posts:
    9
    Thank you Daniel for your suggestions. The reason why I wanted to compute the distance in the fragment shader was that the wall is quite big (relative to the ball) and consists of only two triangles. If the ball hits the wall in the middle its distance to each vertex is too high so that the wall doesn't get opaque at the intersection point...
    I posted my "final" code to http://answers.unity3d.com/question...n-a-shader-if-two-objects-are-close/7645#7645.
     
  7. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    That's all very well, but it looks like you're computing the view space position of the ball from its object space position, which is probably not what you want. My guess is that your ball's position is being fed into the shader as a world position, in which case you only want to multiply it by the view matrix, and you only need to do that once, in a script before putting it into the material.
     
  8. drhenry

    drhenry

    Joined:
    Jan 13, 2010
    Posts:
    9
    Sorry, I didn't get the point...
    Currently I transform the global ball coordinates into the local coordinate system of the wall in a script. These are passed to the shader.
    The shader then transforms the ball position and the vertex coordinates into the eye coordinate system. The fragment shader now computes the distance and the effect looks fine.
    Is there still anything wrong with my code?

    I admit that it would be more efficient if I could transform the ball into eye coordinates somehow in a script so that I don't do this matrix multiplication for each vertex.

    I tried the following script:

    Code (csharp):
    1. var camera : Transform = Camera.main.transform;
    2.     var cameraRelative : Vector3 = camera.InverseTransformPoint(pinball.transform.position);
    3.     cameraRelative.Scale(camera.transform.localScale);
    4.     var localPoint : Vector4 = Vector4(cameraRelative.x, cameraRelative.y, cameraRelative.z, 1);
    5.     renderer.material.SetVector("_BallPos", localPoint);
    and adjusted the vertex shader:

    Code (csharp):
    1. v2f vert (appdata_base v)
    2. {
    3.     v2f o;
    4.     o.color = _Color;
    5.     o.pos = mul (glstate.matrix.mvp, v.vertex);
    6.    
    7.     o.fragPos = mul (glstate.matrix.modelview[0], v.vertex);
    8.     o.ballPos = _BallPos;
    9.     o.texcoord = v.texcoord;
    10.    
    11.     return o;
    12. }
    but it didn't give me the results I expected.
     
  9. drhenry

    drhenry

    Joined:
    Jan 13, 2010
    Posts:
    9
    Ah, wait:
    if the ball position is in the local coordinate system of the wall, then I can simply pass the coordinates to the fragment shader without any matrix multiplication. That's even better! I've tried it and it works!

    Code (csharp):
    1. v2f vert (appdata_base v)
    2. {
    3.     v2f o;
    4.     o.color = _Color;
    5.     o.pos = mul (glstate.matrix.mvp, v.vertex);
    6.    
    7.     o.fragPos = v.vertex;
    8.     o.ballPos = _BallPos;
    9.     o.texcoord = v.texcoord;
    10.    
    11.     return o;
    12. }
    Now I can even omit ballPos in the v2f structure and access _BallPos directly in the fragment shader.

    Is this what you wanted me to do all along? :)
     
  10. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
  11. nm8shun

    nm8shun

    Joined:
    Jul 14, 2007
    Posts:
    476
    Sorry to resurrect this old thread, but it's exactly what I'm hoping to do, and I don't quite understand the implementation.

    So from what I understand, the shader should look like this:
    Code (csharp):
    1.  
    2. Shader "PinballShaders/BoundaryGlowShader"
    3. {
    4.    Properties
    5.    {
    6.       _Color ("Main Color", Color) = (0.1, 0.3, 0.7, 0.25)
    7.       _BallPos("Pinball position", Vector) = (0,0,0,0)
    8.       //Min and max dist indicate the size of the effect when
    9.       //pinball comes near the wall
    10.       _MinDist("Minimum distance", Float) = 0.1
    11.       _MaxDist("Maximum distance", Float) = 0.5
    12.       _MainTex ("Base (RGBA)", 2D) = "white" {}
    13.    }
    14.    SubShader
    15.    {
    16.       Blend SrcAlpha OneMinusSrcAlpha
    17.       Cull Off
    18.  
    19.       Pass
    20.       {
    21. CGPROGRAM
    22. #pragma vertex vert
    23. #pragma fragment frag
    24.  
    25. #include "UnityCG.cginc"
    26.  
    27. float4 _Color;
    28. float4 _BallPos;
    29. float _MinDist;
    30. float _MaxDist;
    31. sampler2D _MainTex;
    32.  
    33.  
    34. struct v2f {
    35.     float4 pos : POSITION;
    36.     float4 color : COLOR0;
    37.     //position of the wall fragment in eye coordinates
    38.     float4 fragPos : TEXCOORD1;
    39.     //position of the pinball in eye coordinates
    40.     float4 ballPos : TEXCOORD2;
    41. };
    42.  
    43. v2f vert (appdata_base v)
    44. {
    45.     v2f o;
    46.     o.color = _Color;
    47.     o.pos = mul (glstate.matrix.mvp, v.vertex);
    48.    
    49.     o.fragPos = v.vertex;
    50.     o.ballPos = _BallPos;
    51.     o.texcoord = v.texcoord;
    52.    
    53.     return o;
    54. }
    55.  
    56. half4 frag (v2f i) : COLOR
    57. {
    58.     float4 outColor = i.color;
    59.  
    60.     // Check distance here.
    61.     float dist = distance(i.ballPos, i.fragPos);
    62.     float safety = step(0.001 , _MaxDist);
    63.     outColor.a = safety *  (1 - clamp(dist, 0, _MaxDist)/_MaxDist);
    64.  
    65.     return outColor;
    66. }
    67.  
    68. ENDCG
    69.       }
    70.    }
    71.    FallBack "VertexLit"
    72. }
    This shader should be applied to the wall object.

    Then, the wall should also include a script that has the following:
    Code (csharp):
    1.  
    2. var pinball : GameObject;
    3.  
    4. function Update(){
    5. var camera : Transform = Camera.main.transform;
    6.    var cameraRelative : Vector3 = camera.InverseTransformPoint(pinball.transform.position);
    7.    cameraRelative.Scale(camera.transform.localScale);
    8.    var localPoint : Vector4 = Vector4(cameraRelative.x, cameraRelative.y, cameraRelative.z, 1);
    9.    renderer.material.SetVector("_BallPos", localPoint);
    10.  }
    11.  
    12.  
    Then in the inspector, the pinball is defined with a drag and drop.

    I'm unable to get this to work. Where have I gone astray?

    Thanks
     
  12. drhenry

    drhenry

    Joined:
    Jan 13, 2010
    Posts:
    9
    My Script is a bit different: I compute the ball coordinates with respect to the WALL coordinate system, you compute it wrt the camera coordinate system.
    The trick is, that in the fragment shader you only need to know the distance between the ball position and the wall's fragment position in 3d, no matter which coordinate system both are in. It only matters, that both are in the SAME coordinate system. In my case, both are in the local coordinate system of the wall.

    What you do at the moment is pass the pinball's position with respect to the camera's coordinate system, while i.fragPos in your fragment shader is in the wall's coordinate system.
    Now there are two solutions for you:
    Either
    1) compute the ball position in the script wrt the local wall's coordinate system (see my code).
    or
    2) in the vertex shader you should replace
    Code (csharp):
    1. o.fragPos = v.vertex;
    by
    Code (csharp):
    1. o.fragPos = mul (glstate.matrix.modelview[0], v.vertex);
    EDIT: solution 1 is more efficient because it uses only one matrix multiplication, while solution 2 means one matrix mutliplication for each vertex!
     
  13. nm8shun

    nm8shun

    Joined:
    Jul 14, 2007
    Posts:
    476
    Thanks much drHenry. I had to abandon this for the moment - but hope to get back to it. Your solution seems really robust and I sure appreciate you taking the time to help coach me through it.
     
  14. Vade-Mecum

    Vade-Mecum

    Joined:
    Dec 10, 2012
    Posts:
    17
    Hello everyone! I got no options left but to ressurect this old thread to ask for some help..
    So.. i've needed a shader to make the schematic building cutoff, that would take the center of an object, make it's front-end transparent and the backwards part - opaque. So i took this shader and modify it for it is to do what i wanted to. So here it is:
    Code (csharp):
    1. Shader "PinballShaders/BoundaryGlowShader"
    2. {
    3.    Properties
    4.    {
    5.     _Color ("Main Color", Color) = (1,1,1,1)
    6.     _BallPos("Pinball position", Vector) = (0,0,0,0)
    7.     _FadePower ("FadePower", Float) = 1
    8.     _MainTex ("Texture", 2D) = "white" { }
    9.    }
    10.    SubShader
    11.    {
    12.    Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
    13.    Lighting Off
    14.    Blend SrcAlpha OneMinusSrcAlpha
    15.    Cull Off
    16.       Pass
    17.       {
    18. CGPROGRAM
    19. #pragma vertex vert
    20. #pragma fragment frag
    21. #include "UnityCG.cginc"
    22. float4 _Color;
    23. sampler2D _MainTex;
    24. float4 _BallPos;
    25. float _FadePower;
    26.  
    27. struct v2f {
    28.     float4 pos : POSITION;
    29.     float4 color : COLOR0;
    30.     float2  uv : TEXCOORD0;
    31.     float4 fragPos : TEXCOORD1;
    32.     float4 ballPos : TEXCOORD2;
    33. };
    34.  
    35. float4 _MainTex_ST;
    36.  
    37. v2f vert (appdata_base v)
    38. {
    39.     v2f o;
    40.     o.color = _Color;
    41.     o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    42.     o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
    43.     o.fragPos = mul (UNITY_MATRIX_MV, v.vertex);
    44.     o.ballPos = mul (UNITY_MATRIX_MV, _BallPos);
    45.     return o;
    46. }
    47.  
    48. half4 frag (v2f i) : COLOR
    49.  
    50. {
    51.     float4 outColor = i.color;
    52.     half4 texcol = tex2D (_MainTex, i.uv);
    53.     float dist = distance(i.ballPos, i.fragPos);
    54.     texcol.w = dist <= _FadePower ? 0 : 1;
    55.         return texcol;
    56. }
    57. ENDCG
    58.       }
    59.    }
    60.    FallBack "VertexLit"
    61. }
    but now the thing happens, that i just don't understand. The original shader comes with a script with this:
    Code (csharp):
    1.    renderer.material.SetVector("_BallPos", localPoint);
    I've added this:
    Code (csharp):
    1.    renderer.material.SetFloat("_FadePower", dist);
    but it seems like it's just ignores the script - the shader works without it. And naturally it doesn't work the way it supposed to - set all the fragments' alpha between camera and the object center to transparent.
    So would you please help me to make this work right?