I am writing a small raytracer in python , everything was working fine but when i implemented plane geometry then the reflective spheres are getting weird patches on lower bottom. image below:
I guess this is happening because of some error in calculating lighting with wrong normals? but i am unable to figure out exactly what.
code to find palne-ray intersection:
class Plane(Shape):
def __init__(self, color, y=0, specular=-1, reflective=0):
self.specular = specular
self.reflective = reflective
self.center = Vec3(0,y,0)
self.normal = Vec3(0,-1,0)
self.color = color
def intersect_at_point(self, origin, ray):
denom = ray.dot(self.normal)
if abs(denom) > 0.0001:
diff = self.center - ray
t = diff.dot(self.normal) / denom
if t > 0.0001:
return t
return None
trace ray function.
def trace_ray(self, origin, direction, t_min, t_max, depth):
closest_object, closest_t = self.closest_intersection(origin, direction, t_min, t_max)
if closest_object == None:
return Vec3(173/255, 216/255, 230/255)
# Compute local color
P = origin + closest_t * direction # Compute intersection
N = P - closest_object.center # Compute normal at intersection
N = N / N.mag()
local_color = closest_object.color * self.compute_light(P, N)
# If we hit the recursion limit or the object is not reflective, we're done
r = closest_object.reflective
if depth <= 0 or r <= 0:
return local_color
# Compute the reflected color
R = self.reflect_ray(-1*direction, N)
reflected_color = self.trace_ray(P, R, 0.001, math.inf, depth - 1)
return local_color * (1 - r) + reflected_color * r
closest intersection method.
def closest_intersection(self, O, D, t_min, t_max):
closest_t = math.inf
closest_object = None
for object in self.world.objects:
t1, t2 = object.intersect_at_point(O, D)
if (t_min < t1 < t_max) and t1 < closest_t:
closest_t = t1
closest_object = object
if (t_min < t2 < t_max) and t2 < closest_t:
closest_t = t2
closest_object = object
for plane in self.world.planes:
t = plane.intersect_at_point(O,D)
if t and (t_min < t < t_max) and t < closest_t:
closest_t = t
closest_object = plane
return closest_object, closest_t
Calculate light method.
def compute_light(self, Point, Normal):
i = 0.0
P = Point
N = Normal
for light in self.world.lights:
if light.type == 'ambient':
i += light.intensity
else:
if light.type == 'point':
L = light.position - P
t_max = 1
if light.type == 'directional':
L = light.direction
t_max = math.inf
# Shadow check
shadow_sphere, shadow_t = self.closest_intersection(P, L, 0.001, t_max)
if shadow_sphere != None:
continue
n_dot_l = N.dot(L)
if n_dot_l > 0:
# print(f'light intensity {light.intensity}')
i+= light.intensity * n_dot_l / (N.mag() * L.mag())
return i