0
$\begingroup$

I am trying to get a segment closest point on a surface and display a circle (or sphere in this case) at that position.

I actually get the correct position but I can't get a uniform sphere based on the computed data.

enter image description here

Here's the associated code I use to find the intersection point and draw the sphere.

float3 a = from;
float3 b = to;
float3 c = worldPosition;

float3  ab = b - a;
float t = dot(c - a, ab) / dot(ab, ab);
float3 point = a + saturate(t) * ab;

return length(point - worldPosition) - radius;
$\endgroup$
  • $\begingroup$ I don't fully understand your question. You have a line segment and you are trying to use a fragment shader to display kind-of a clipped distance field on the mesh surface? $\endgroup$ – Isolin Jun 11 at 10:29
  • $\begingroup$ Thanks for your answer. I have a line segment and I compute the surface intersection closest point using the cylinder fragment shader. Using this closest segment point I compute a sphere distance field. Everything is done in world space. $\endgroup$ – MaT Jun 11 at 11:02
  • $\begingroup$ Should there be a black circle displayed also when there is no true intersection but only strong proximity? $\endgroup$ – Isolin Jun 11 at 11:04
  • $\begingroup$ I'm not sure to understand, the main issue is the deformation of the computed length which should be uniform. I guess that there should be a black circle when there's no true intersection because it illustrates the distance but it should be uniform. $\endgroup$ – MaT Jun 11 at 11:09
  • $\begingroup$ Got it, I will try to answer the question accordingly. $\endgroup$ – Isolin Jun 11 at 11:13
2
$\begingroup$

Your actual result is correct in terms of what you can achieve in a single-pass with a fragment shader. It calculates the distance of the fragment's world position to the line segment. That distance is different for each fragment so the stretched black ellipsoid is the correct output.

A fragment shader has only local information about the current fragment being processed. You can't do more with that. Your expected result requires some global knowledge about the scene, since you are searching for the minimum distance across the whole surface.

Without any thoughts on optimization, the naive solution path should involve contextual computation of local minima of the fragment-to-linesegment distance. In order to get such context, you need to compute the distance field first and then access it to find the local minima in a separate pass.

  1. Compute the raw distance field in the first pass (same as your example but no radius subtraction at the end.
  2. Mark the pixels with local minima in the second pass (e.g. by setting the alpha channel to 1). This will be screen-space pass. The best is to have a rgba float texture at output with rgb being fragment's world position and a being 1 for local minima and 0 else.
  3. Paint the black circles/spheres in a third pass (also screen-space).

Having the information about the local minimum of your distance function enables you to output the right shape for around the minima in the last pass. In pseudocode:

vec4 worldPos = tex2D(distance_texture, frag.x, frag.y);
if (worldPos.a > 0.5) //is minimum
    return vec3(0,0,0);
else
{
    //Sample the screen-space neighborhood in larger and larger circles
    // until a fragment with distance.a == 1 is found
    // or until all fragments are more than radius away (in world coords).

    //Minimum world space distance of any sampled fragment.
    float minDist = 0.0;
    int iter = 1;
    //Search for a local minimum until all fragments are more than radius away,
    // since then even if a local minimum gets found it will no longer produce
    // the black shading.
    while (minDist < radius && minDist >= 0.0 && iter < Math.Max(screenWidth, screenHeight))
        minDist = sampleNeighborhood(iter++, worldPos);

    if (minDist < 0.0) //found a local minimum
        return vec3(0,0,0);
    else
        return vec3(1,1,1); //or any other computed shading

    //I am aware that the function call above checks central fragments several times.
    //That should be of course optimized in production code.
}

float sampleNeighborhood(int neigh, vec4 worldPos)
{
    float minDist = float.MaxValue;
    for(int x = Math.max(0, frag.x - neigh); x <= Math.Min(screenWidth, frag.x + neigh; ++x)
        for(int y = Math.max(0, frag.y - neigh); x <= Math.Min(screenHeight, frag.y + neigh; ++y)
        {
            float p = tex2D(distance_texture, x, y);
            float d = distance(p.xyz, worldPos.xyz);
            if (p.a > 0.5 && d <= radius)
                //Return negative distance to indicate a local minimum was found.
                return -1.0; 
            else
                minDist = Math.min(minDist, d);
        }
    return minDist;
}

Finding the local minima in the second pass is much easier, since you only need to sample the local 3x3 neighborhood.

I hope that helps you in reaching the desired rendering!

| improve this answer | |
$\endgroup$
  • 1
    $\begingroup$ Notice, that the same question is also on the game development site: link. I wasn't sure how to handle this, so I found this Meta question - Long story short: Maybe you could provide your answer there too by copying or linking it (Don't want to farm reputation that you earned ;) ). $\endgroup$ – wychmaster Jun 11 at 12:15
  • 1
    $\begingroup$ Thank you :) Haha it automatically converted it to a comment stating that it is trivial :) $\endgroup$ – Isolin Jun 11 at 12:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.