The basic principles:
Ray Generation: The process begins by generating rays from the viewer’s eye through each pixel on the screen. These rays act as virtual camera rays and serve as the starting point for the ray tracing algorithm.
Ray Intersection: Once the rays are generated, they are traced through the virtual scene. As the rays propagate, they intersect with objects within the scene, such as geometry or surfaces. These intersections are computed by solving mathematical equations or algorithms, determining the point at which the ray interacts with an object.
Reflection and Refraction: When a ray intersects with a surface, it can either bounce off (reflection) or pass through (refraction) the object, based on the material properties defined for the surface. This behavior allows for the simulation of realistic effects like shiny reflections or transparent objects.
Lighting: As rays bounce or pass through objects, they interact with light sources and other objects in the scene. This interaction determines the intensity and color of the rays, taking into account factors such as the material’s properties, the angle of incidence, and the presence of shadows.
Shadow Calculation: Shadows are an essential aspect of rendering realistic scenes. Ray tracing calculates shadows by determining if a ray from a surface point to a light source is blocked by any other object in the scene. If an obstruction is detected, the point is in shadow and receives less light.
Here is a diagram that demonstrates ray tracing:
And here is the Cornell Box, a popular scene that showcases what ray tracing can do by implementing light sources, shadows, global illumination, reflections, ect:
Of course, this is only a surface level explanation covering the basics. Ray tracing can also be extended with more advanced techniques like global illumination and path tracing. For a more complete description, I recommend checking out the book Ray Tracing in One Weekend by Peter Shirley (which I’m currently using to build a more advanced path tracer in C++).
Some picture results of the python ray tracer we will create (in addition to the picture in the thumbnail) can be seen here:
from math import sqrt
class Vector3: # Vector3 Class
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __add__(self, other_Vector3): # Adding vector with another vector
return Vector3(self.x + other_Vector3.x, self.y + other_Vector3.y, self.z + other_Vector3.z)
def __sub__(self, other_Vector3): # Subtracting vector with another vector
return Vector3(self.x - other_Vector3.x, self.y - other_Vector3.y, self.z - other_Vector3.z)
def __mul__(self, n): # Multiplying vector with a number
return Vector3(self.x * n, self.y * n, self.z * n)
def __rmul__(self, n): # Multiplying vector with a number (on the other side)
return Vector3(self.x * n, self.y * n, self.z * n)
def __truediv__(self, n): # Dividing vector with a number
return Vector3(self.x / n, self.y / n, self.z / n)
def __str__(self): # Representation method
return "({}, {}, {})".format(self.x, self.y, self.z)
def dot_product(self, other_Vector3): # dot product
return self.x * other_Vector3.x + self.y * other_Vector3.y + self.z * other_Vector3.z
def normalize(self): # Normalize vector
magnitude = sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
return Vector3(self.x / magnitude, self.y / magnitude, self.z / magnitude)
def negative(self): # Multiply x, y, and z by -1
return Vector3(self.x * -1, self.y * -1, self.z * -1)
def to_color(self): # Formatting to an rgb color
r = int(self.x)
b = int(self.y)
g = int(self.z)
if (r > 255): r = 255
if (r < 0): r = 0
if (b > 255): b = 255
if (b < 0): b = 0
if (g > 255): g = 255
if (g < 0): g = 0
return Vector3(r, b, g)
def to_tuple(self): # Converting to a tuple
return (self.x, self.y, self.z)