# Raymarching Refraction

I'm sorry if this question is imprecise, or violates any guidelines. This is my first post on Stack Exchange, so any feedback is welcome.

I am working on a ray-marcher in my spare time, which you can see for reference at https://replit.com/@GriffinSchreibe/My-First-Raymarcher (Please press view code to see what's happening, as it does not output anything to the console you can see just from viewing the project). Yes, I am working on an online IDE, purely because I do not have sufficient compute power on my laptop. My code is heavily based on Ray-tracing in one weekend. I have gradually worked through the first few sections, but one section has been stumping me, namely the refraction section.

In my code I have this function that returns the ray color, given a ray shot from the camera:

vec3 ray_color(const ray &r, int depth, float current_medium_ior = 1) {
// A medium ior of 1 means air.
if (depth <= 0) {
return vec3(0, 0, 0);
}
// setup parameters and variables
float total_distance_traveled = 0.0;
const int NUMBER_OF_STEPS = 64; // 32;
const float MAXIMUM_HIT_DISTANCE = 0.001;
const float MAXIMUM_TRACE_DISTANCE = 1000.0;

vec3 color;

// iterate through the max number of steps
for (int i = 0; i < NUMBER_OF_STEPS; ++i) {
// update the position
vec3 current_position =
r.origin() + total_distance_traveled * r.direction();
// find the distance
Surface closest_surface = map_the_world(current_position);
float distance_to_closest = step_size * closest_surface.sd;

// if we hit something
float abs_dist = abs(distance_to_closest);
if (abs_dist < MAXIMUM_HIT_DISTANCE) {
vec3 normal = calculate_normal(current_position);
vec3 direction;

float new_medium_ior = closest_surface.ior;
if (closest_surface.surface_type == "REFRACTIVE") {

// Check if the medium has changed
if (new_medium_ior != current_medium_ior) {
std::clog<<"Medium change!\n";
float ior_ratio = new_medium_ior / current_medium_ior;

vec3 direction = refract(r.direction(), normal, ior_ratio);

// Recursion
color = ray_color(ray(current_position + 0.004 * direction, direction), depth - 1, current_medium_ior);
return color;
} else {
// Continue with loop for further intersections within the medium or other behavior
}
}
else {
direction = closest_surface.scatter_direction(r.direction(), normal);
// Catch degenerate scatter direction
if (direction.near_zero())
direction = normal;
// Recursion.
color = 0.5 * ray_color(ray(current_position + 0.004 * normal, direction), depth - 1, current_medium_ior);
return color;
}
}

// if we have been going for too long
if (total_distance_traveled > MAXIMUM_TRACE_DISTANCE) {
break;
}

// update the distance
total_distance_traveled += distance_to_closest;
}

// if we didn't find anything
// then we do a blue-white gradient.
vec3 unit_direction = unit_vector(r.direction());
auto a = 0.5 * (unit_direction.y() + 1.0);
return (1.0 - a) * vec3(1.0, 1.0, 1.0) + a * vec3(0.5, 0.7, 1.0);
}


As you can see, I attempt to implement refraction, but only when the medium changes. After weeks of toil, I am still perplexed as to how to properly implement ray-marched refraction in the spirit of Ray-tracing in one Weekend (By that I mean no "hacky" solutions).

I would be deeply grateful to anyone who could help me solve this problem. Oh, and you can play around with the code in the link above.

EDIT: After fixing the medium change check to see if the origin medium was different from the current medium, I am now getting the refracting sphere as blue. I think that what is happening is that the step size is getting tiny inside the material, as I had the Distance Estimator return 0 inside the material. It then ran out of steps, and returned sky color. I am still curious about the best way to stop the ray-marcher running out of steps, though.