Calculating ray intersection with two spheres (with smooth_blending between the two spheres to make them appear as one continuous geometry)

I have the following function to define the distance to the interesection between a ray and the surface of a sphere:

float intersect_sphere_distance(Ray ray, Sphere sphere)
{
// Calculate distance along the ray where the sphere is intersected
float3 sphere_to_ray_start_vector = ray.origin - sphere.position;

float hit_distance_1 = -dot(ray.direction, sphere_to_ray_start_vector);
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;

if (hit_distance_2_sqr < 0)
return 1.#INF; // Infinity

float hit_distance_2 = sqrt(hit_distance_2_sqr);

// Use entry point is not valid, use exit point
return hit_distance_1 - hit_distance_2 > 0 ? hit_distance_1 - hit_distance_2 : hit_distance_1 + hit_distance_2;
}


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):

void intersect_sphere(Ray ray, inout RayHit best_hit, Sphere sphere)
{
float hit_distance = intersect_sphere_distance(ray, sphere);

if (hit_distance > 0 && hit_distance < best_hit.distance)
{
best_hit.distance = hit_distance;
best_hit.position = ray.origin + hit_distance * ray.direction;

best_hit.normal = normalize(best_hit.position - sphere.position);
}
}


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: https://www.youtube.com/watch?v=Cp5WWtMoeKg&list=PLFt_AvWsXl0ehjAfLFsp1PGaatzAwo0uK&index=14

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):

// polynomial smooth min (k = 0.1);
// from https://www.iquilezles.org/www/articles/smin/smin.htm
float4 Blend( float a, float b, float3 colA, float3 colB, float k )
{
float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 );
float blendDst = lerp( b, a, h ) - k*h*(1.0-h);
float3 blendCol = lerp(colB,colA,h);
return float4(blendCol, blendDst);
}


This is what I've tried to do so far, with the commented out parts of the "smooth_..." functions being taken from the tutorial video, and the uncommented parts being from the linked github repo. The results are not good whichever I use. I either get both spheres intersecting normally with weird surface normals on one of them, or I get just the intersecting section with surface normals that look correct for the resulting shape at least, haha.

float smooth_lerp_amount(float a, float b, float k)
{
// return max(k - abs(a-b), 0) / k;
return saturate(0.5 + 0.5 * (b-a) / k);
}

float smooth_min(float a, float b, float k)
{
// float h = max(k - abs(a-b), 0) / k;
// return min(a, b) - pow(h, 3) * 1 / 6.0;

float h = smooth_lerp_amount(a, b, k);
return lerp(b, a, h) - k*h*(1.0-h);
}

void intersect_double_sphere(Ray ray, inout RayHit best_hit, Sphere sphere_a, Sphere sphere_b)
{
float hit_distance_a = intersect_sphere_distance(ray, sphere_a);
float hit_distance_b = intersect_sphere_distance(ray, sphere_b);

float hit_distance = smooth_min(hit_distance_a, hit_distance_b, 1.0f);

if (hit_distance > 0 && hit_distance < best_hit.distance)
{
best_hit.distance = hit_distance;
best_hit.position = ray.origin + hit_distance * ray.direction;

float lerp_amount = smooth_lerp_amount(hit_distance_a, hit_distance_b, 1.0f);

float3 normal_a = normalize(best_hit.position - sphere_a.position);
float3 normal_b = normalize(best_hit.position - sphere_b.position);
best_hit.normal = lerp(normal_a, normal_b, 1.0 - lerp_amount);
}
}
$$$$
`
• I don't think you can do that, atleast that simply, through raytracing because it's different from raymarching. Ray marching relies on calculating the distance which can be blended but raytracing is actually intersecting with the shape itself and not using the distance at all. Try waiting for more experienced people though, It'd be interesting if a solution does exist. Dec 8, 2022 at 5:00
• Thanks. It has since occured to me that I am using the full ray distance in the calculation that is probably the individual marching step's distance to scene object, which is why it's not working for me. Also in my current code, my smooth_min result will be infinity if neither regular sphere involved was intersected in the first place. At a minimum, I need to calculate my intersection distance while considering both objects at once to allow me to even hit some parts of the smoothly interpolated section Dec 8, 2022 at 10:37
• Again, the thing is in raytracing there is no simple way to blend 2 given shapes to intersect test with an interpolated section. You can't consider both objects at once by doing some sort of blending since a pixel color is set based on intersecting the ray with a particular shape. On the contrary, in raymarching you set color based on when the distance reaches a realy small value. This distance can be modified easily by blending to make the ray stop earlier or more deeper to produce blended shapes without actually using that blended shape's equation. Dec 9, 2022 at 0:30
• Yeah that's fair enough, so what I need is either an interesection equation for a shape of the kind I'm after, or I need to do some fancy algorithmic work to achieve it. Either way, anyone know anything that could help me achieve what I'm after without using ray marching like Sebastien Lague does? Dec 9, 2022 at 15:52