In [1]:
# raytracing tutorial
# 03- basic sphere but now lit by a source

In [2]:
import numpy
import matplotlib.pyplot as plt
import math

# plot images in this notebook
%matplotlib inline

In [3]:
# axes x to the right, y upwards. z into the screen (left hand rule)

In [4]:
# sphere object

class Sphere():
    
    def __init__(self, x, y, z, r):
        self.centre = numpy.array([x,y,z])
        self.radius = r
        pass
    
    def status(self):
        print("centre = ", self.centre)
        print("radius = ", self.radius)
        pass

    def intersection(self, camera_location, ray_direction_vector):
        # calculate quadratic determinant "b^2 - 4ac" for ray intersecting circle
        b = numpy.dot(2 * ray_direction_vector,(camera_location - self.centre))
        b2 = b*b
        a = numpy.dot(ray_direction_vector, ray_direction_vector)
        c = numpy.dot((self.centre - camera_location), (self.centre - camera_location)) - (self.radius * self.radius)
        delta = b2 - (4 * a * c)
        #print(delta)
        
        if (delta >= 0):
            # calculate nearest point (lowest t)
            t = (-b - math.sqrt(delta)) / (2 * a)
            intersection_point = camera_location + (t * ray_direction_vector)
            
            # calculate normal at surface
            normal = (intersection_point - self.centre) / numpy.linalg.norm(intersection_point - self.centre)
            
            # return tuple (intersection yes/no, nearest point, normal)
            return (delta>0, intersection_point, normal)
            pass
        
        # return tuple (intersection yes/no, nearest point, norm)
        return (delta >= 0, 0, 0)

In [5]:
# camera location
camera_location = numpy.array([0,0,-100])

# view port
view_port_location = numpy.array([-10, 0, 0])
view_port_width = 20
view_port_height = 20

# resolution (pixels per unit distance)
resolution = 4

In [6]:
# light source

# light is at above right, and a bit forward
light_location = numpy.array([100, 100, -50])

In [7]:
# create sphere
sphere = Sphere(0,10,10,5)
sphere.status()


centre =  [ 0 10 10]
radius =  5

In [8]:
# create image

image = numpy.zeros([view_port_width * resolution, view_port_height * resolution, 3], dtype='uint8')
print("image shape = ", image.shape)


image shape =  (80, 80, 3)

In [35]:
# main loop is to consider every pixel of the viewport

for pixel_ix in range(image.shape[0]):
    for pixel_iy in range(image.shape[1]):
        
        current_position = view_port_location + numpy.array([pixel_ix/resolution, pixel_iy/resolution, 0])
        #print("current_position", current_position)
        ray_direction_vector = current_position - camera_location
        ray_direction_vector /= numpy.linalg.norm(ray_direction_vector)
        #print(ray_direction_vector)
        
        # calculate  background sky pixel colour from vertical direction of ray
        colour = 100 + int(ray_direction_vector[1] *  3 * 255)
        image[pixel_ix, pixel_iy] = [50, 50, colour]
        
        # check intersection with sphere, then light using norm and light source
        intersection = sphere.intersection(camera_location, ray_direction_vector)
        if intersection[0]:
            # colour red if true
            norm = intersection[2]
            to_light_source = light_location - intersection[1]
            cos_with_lightsource = numpy.dot(norm, to_light_source) / (numpy.linalg.norm(to_light_source))
            # rescale from [-1,1] to [-127, 127]
            lighting = (cos_with_lightsource * 127)
            image[pixel_ix, pixel_iy] = [255, 127 + lighting, 127 + lighting]
            pass
        
        pass
    pass

In [36]:
# transpose array so origin is bottom left, by swapping dimensions 0 and 1, but leave dimension 3

image2 = numpy.transpose(image, (1, 0, 2))
plt.imshow(image2, origin='lower')


Out[36]:
<matplotlib.image.AxesImage at 0x10e372d68>

In [ ]:


In [ ]: