Search Unity

Question Calculating ray intersection with two spheres (with smooth_blending between the two spheres)

Discussion in 'General Graphics' started by iron_attorney, Dec 6, 2022.

  1. iron_attorney

    iron_attorney

    Joined:
    May 12, 2015
    Posts:
    5
    I have the following function to define the distance to the interesection between a ray and the surface of a sphere:


    Code (CSharp):
    1. float intersect_sphere_distance(Ray ray, Sphere sphere)
    2. {
    3.     // Calculate distance along the ray where the sphere is intersected
    4.     float3 sphere_to_ray_start_vector = ray.origin - sphere.position;
    5.  
    6.     float hit_distance_1 = -dot(ray.direction, sphere_to_ray_start_vector);
    7.     float hit_distance_2_sqr = hit_distance_1 * hit_distance_1 - dot(sphere_to_ray_start_vector, sphere_to_ray_start_vector) + sphere.radius * sphere.radius;
    8.  
    9.     if (hit_distance_2_sqr < 0)
    10.         return 1.#INF; // Infinity
    11.  
    12.     float hit_distance_2 = sqrt(hit_distance_2_sqr);
    13.  
    14.     // Use entry point is not valid, use exit point
    15.     return hit_distance_1 - hit_distance_2 > 0 ? hit_distance_1 - hit_distance_2 : hit_distance_1 + hit_distance_2;
    16. }

    And this function to determine the position in 3D space that a hit occured (this could be one of many sphere intersection tests to determine which sphere actually hit, hence the "best_hit" detail):

    Code (CSharp):
    1. void intersect_sphere(Ray ray, inout RayHit best_hit, Sphere sphere)
    2. {
    3.     float hit_distance = intersect_sphere_distance(ray, sphere);
    4.  
    5.     if (hit_distance > 0 && hit_distance < best_hit.distance)
    6.     {
    7.         best_hit.distance = hit_distance;
    8.         best_hit.position = ray.origin + hit_distance * ray.direction;
    9.  
    10.         best_hit.normal = normalize(best_hit.position - sphere.position);
    11.     }
    12. }

    What I'm trying to do is write a function that smoothly blends between multiple spheres to make one consistant shape. The shape I wish to achieve is two spheres joined smoothly by a tube that tapers a little towards the centre.

    I've seen this achieved when doing ray marching instead of using one infinite ray, but ultimately they tutorial found the distance to an object (or smoothly combined combinations of objects) and the rest should be roughly the same.

    The video I first saw this on:


    A snippet of his code where he does smooth blending, but I can't get it to work (I always get a shape that looks like only the intersecting volume between the spheres):

    Code (CSharp):
    1. // polynomial smooth min (k = 0.1);
    2. // from [URL]https://www.iquilezles.org/www/articles/smin/smin.htm[/URL]
    3. float4 Blend( float a, float b, float3 colA, float3 colB, float k )
    4. {
    5.     float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 );
    6.     float blendDst = lerp( b, a, h ) - k*h*(1.0-h);
    7.     float3 blendCol = lerp(colB,colA,h);
    8.     return float4(blendCol, blendDst);
    9. }

    My current attempt looks like this (commented out `smooth_foo()` code is from the video, the uncommented code is from his linked github):


    Code (CSharp):
    1. float smooth_lerp_amount(float a, float b, float k)
    2. {
    3.     // return max(k - abs(a-b), 0) / k;
    4.     return saturate(0.5 + 0.5 * (b-a) / k);
    5. }
    6.  
    7. float smooth_min(float a, float b, float k)
    8. {
    9.     // float h = max(k - abs(a-b), 0) / k;
    10.     // return min(a, b) - pow(h, 3) * 1 / 6.0;
    11.  
    12.     float h = smooth_lerp_amount(a, b, k);
    13.     return lerp(b, a, h) - k*h*(1.0-h);
    14. }
    15.  
    16. void intersect_double_sphere(Ray ray, inout RayHit best_hit, Sphere sphere_a, Sphere sphere_b)
    17. {  
    18.     float hit_distance_a = intersect_sphere_distance(ray, sphere_a);
    19.     float hit_distance_b = intersect_sphere_distance(ray, sphere_b);
    20.  
    21.     float hit_distance = smooth_min(hit_distance_a, hit_distance_b, 1.0f);
    22.  
    23.     if (hit_distance > 0 && hit_distance < best_hit.distance)
    24.     {
    25.         best_hit.distance = hit_distance;
    26.         best_hit.position = ray.origin + hit_distance * ray.direction;
    27.      
    28.         float lerp_amount = smooth_lerp_amount(hit_distance_a, hit_distance_b, 1.0f);
    29.  
    30.         float3 normal_a = normalize(best_hit.position - sphere_a.position);
    31.         float3 normal_b = normalize(best_hit.position - sphere_b.position);
    32.         best_hit.normal = lerp(normal_a, normal_b, 1.0 - lerp_amount);
    33.     }
    34. }
     
    Last edited: Dec 6, 2022
  2. iron_attorney

    iron_attorney

    Joined:
    May 12, 2015
    Posts:
    5
    It has occured to me that the distance I'm calculating for my one shot ray is different to the distance Sebastian Lague is using in his ray marching algorithm, which is probably why I can't straight lift it from his work. Maybe if my ray passes between the two spheres (intersecting them or otherwise), I need to calculate the closest the ray passes to each if it were to travel right through them? In the current form, I definitely won't calculate a hit anywhere except when it intersects at least one of the spheres, which is at least one of my issues.