1

This is my first question, so I hope I do it right. I am currently writing a simple raytracer in C for a school project. The program seems to be functioning as intended as it displays the basic shapes (planes, spheres and cylinders) using a library provided by the school to display the pixels on a window.

I do have an odd issue, which after several days I still cannot figure out.

Here is a simple scene which displays properly with one light (always only one light), one sphere and one plane: img1

But then things get a little funky.

When I add a cylinder to the scene, I have the projected shadow of the cylinder, but not the one of the sphere anymore:

img2

Here again, we have the cylinders' shadows, but not the spheres:

img1

And finally here, the light is past the plane on the right and therefore all the shapes SHOULD be in the shades:

img1

I have a hard time wrapping my head around this one. I am still a beginner with C and especially 3D programming.

Once I've created a ray from the camera, I check if I find an intersection with any shape in my scene:

bool    shapes_intersect(t_shape *shapes, t_inter *inter)
{
    bool    hit;
    int     i;

    hit = false;
    i = 0;
    while (i < shapes->plane_nb)
    {
        if (hit_pl(inter, &shapes->planes[i]))
            hit = true;
        i++;
    }
    i = 0;
    while (i < shapes->sphere_nb)
    {
        if (hit_sp(inter, &shapes->spheres[i]))
            hit = true;
        i++;
    }
    i = 0;
    while (i < shapes->cyl_nb)
    {
            if (hit_cy(inter, &shapes->cylindres[i]))
                hit = true;
            i++;
    }
    return (hit);
}

And once an intersection is found, we trace another ray from that intersection to the point of light and go through the same function (shapes_intersect) in order to determine if that intersection is lit or not:

static bool     in_shadow(t_data *d, t_inter inter, t_vec light)
{
    t_inter shadow;

    shadow.ray.pos = vecs_multf(inter.normal, 0.0001);
    shadow.ray.pos = vecs_add(inter.pos, shadow.ray.pos);
    shadow.ray.axe = normalized(vecs_sus(light, shadow.ray.pos));
    shadow.ray.tMAX = RAY_T_MAX;
    return (shapes_intersect(&d->shapes, &shadow));
}

void    ray_trace(t_data *d)
{
    int         x;
    int         y;
    t_ray       ray;
    t_inter     inter;
    uint32_t    curr_pixel;
    bool        visible;

    x = 0;
    y = 0;
    
    while ((uint32_t)x < d->img->width)
    {
        while ((uint32_t)y < d->img->height)
        {
                ray = make_ray(&d->cam, vec2_init(((2.0f * x) / d->img->width) - 1.0f, ((-2.0f * y) / d->img->height) + 1.0f));
            inter = inter_cpy_ray(&ray);
            if (shapes_intersect(&d->shapes, &inter))
            {
                curr_pixel = color_prod(inter.rgb, color_scale(d->amb.rgb, d->amb.ratio));
                visible = !in_shadow(d, inter, d->lum.pos);
                curr_pixel = color_add(curr_pixel, visible * color_comp(&d->lum, inter));
                mlx_put_pixel(d->img, x, y, curr_pixel);
            }
            else
            {
                curr_pixel = 255;
                mlx_put_pixel(d->img, x, y, curr_pixel);
            }
            y++;
        }
        y = 0;
        x++;
}

Which is the main reason I cannot wrap my head around this problem. The intersections are properly found, we can see the shapes and even some of the shadows, but for some reason the planes and the spheres shadows are not detected when there's a cylinder in the scene.

Would anybody have an idea of what is going on here?

Thank you for your time.

Spektre
  • 49,595
  • 11
  • 110
  • 380
Johann
  • 11
  • 2

2 Answers2

3

did no in depth analysis nor test but from a quick look:

I do not see distance ordering of the hits in your shapes_intersect function. You should chose the closest object hit instead of the hit with object with biggest index ...

As cylinders are tested last they have biggest priority so if your ray hits any cylinder then hits with any other object are ignored ...

If you hit sphere (they are tested secondly) then planes are ignored ...

also if ray hits more objects of the same type only the one with highest index is registered so again rendering is wrong ...

To remedy all this just add condition in case your hit == true you should update your inter only if intersection point is closer (along the ray direction) than already chosen intersection point ...

btw see this for some inspiration:

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Thank your for your reply! In the functions hit_pl, hit_sp and hit_cyl I always check if t is smallest than the the last t and only return true and assign if that's case. I figured that would be enough to check if I found the closest intersection. For example, in hit_pl, I have the lines: if (inter->t > time[0] && time[0] > 0) { inter->t = time[0]; inter->pos = ray_calculate(&inter->ray, inter->t); inter->normal = normalized(vecs_sus(inter->pos, elem->pos)); inter->rgb = elem->rgb; return (true); } Is that what you meant? – Johann Sep 01 '23 at 08:28
  • @Johann sounds about right if `time[0]` is the ray distance from ray origin to intersection point. Having it in array is weird which might suggest bug is in there but without code I only guess Also some compilers might need brackets to evaluate properly `if ((inter->t > time[0])&&(time[0] > 0))` had some problems with it in past since then I am paranoid about brackets. Hope that the `time[0]` is computed from original ray origin and not from updated one after any hit. In case this is GC/GCC with use of threads (or sometimes even without) some stuff should be volatile in order to work properly – Spektre Sep 01 '23 at 10:43
  • Shouldn't the time[0] be computed from the new ray origin (from intersection point to light point) since we're trying to find if our intersection is lit or not? I actually also thought the array was a bit weird, so I ended up changing it to 3 chained lists (one for each type of shape), and the shapes_intersect function now iterates the hit_pl, hit_sp and hit cyl functions on the respective lists. The issue unfortunately remains the same. – Johann Sep 01 '23 at 11:48
  • @Johann you should compute `time[0]` in respect to original ray position because if you compute it for updated one then if you find hit for more distant object first it will ignore the closer hits because after ray origin update they will become negative and ignored by the `if` sorting your hits by distance. You update the ray origin only after you test all the objects ... – Spektre Sep 01 '23 at 16:40
0

The main issue was indeed how I initialized t in the ray going from intersection point to light point. Now, t is initialized as the length between intersection and light point and it works like a charm.

Thank you A LOT for your help !

Johann
  • 11
  • 2