Skip to content

This project is an introduction to the beautiful world of Ray tracing. It has the basics, and there for is a 'Mini Ray tracer'.

Notifications You must be signed in to change notification settings

JoviMetzger/42Project-MiniRT

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🔸🔻 MiniRT 🔻🔸

Welcome to MiniRT

This project was made in collaboration with 🌈 Sarah Mclacke 🌈
MiniRT is about a ray-tracing engine that renders realistic
3D computer-generated images through ray-tracing techniques.

Table of Contents


eVerYTinG bEloW tHiS liNe wAs ME gOiNg "hhhUUUUUhhhhhh?????????????" (aka. cat meme)

Screencast from 30-09-24 15-57-13



🧩Building a Ray Tracer

A ray tracer works a bit differently than your eye.
Ray tracing simulates how rays of light interact with objects to produce realistic images.
Unlike how our eyes work, which detect light coming from objects,
a ray-tracing algorithm traces rays from the camera (the "eye") into the scene,
determining which objects they hit and rendering the scene accordingly. "hUh??" moment

This-figure-demonstrates-the-concept-of-ray-tracing-A-ray-is-cast-from-the-camera

You get 3 main steps to building a ray tracer:

  • Step 1)    Calculate the ray from the “eye” through the pixel,
                     ⇾ Set up your camera and calculate a ray per pixel (width x height).
  • Step 2)    Determine which objects the ray intersects,
                     ⇾ Calculate your intersection points with each object (save that information in a struct)
  • Step 3)    Compute a color for the closest intersection point.
                     ⇾ Calculate, with the Phong reflection model, your light per light source and shadow.
Step 1;

🖍️ Step 1

🎲 Understanding the 3D grid:

You get 2D grid, which only have x and y coordinates. Coordinates(x, y)
And you get a 3D grid, which has x, y and z coordinates. Coordinates(x, y, z)

  • x: left/right
                  ⇾ x = right (positive numbers) | -x = left (negative numbers)
  • y: up/down
                  ⇾ y = right (positive numbers) | -y = left (negative numbers)
  • z: forward/back forward
                  ⇾ z = right (positive numbers) | -z = left (negative numbers)

3D_Grid_Example

Online 3D grid for visualizing: 3D-Graph

🎲 How do I set up the ray?:

    1.    Set up the width, height and image ratio of the window/image.
         image_width and image_height you can choose yourself. (a nice size: WIDTH = 1800; and HEIGHT = 1900;)
         image_ratio = image_height / image_width;
    1.    Calculate the viewport, so what your imaginary camera sees.
         pos_x and pos_y is at what pixel you are currently.
         viewport_w = 2 * ((pos_x + 0.5) / image_width) - 1;
         viewport_h = (1 - 2 * ((pos_y + 0.5) / image_height)) * image_ratio;
    1.    Calculate each delta pixel.
         M_PI ⇾ This is a constant representing the value of pi. (M_PI = 3.14159265358979323846)
         pixel_x = viewport_w * (tan((fov / 2) * (M_PI / 180)));
         pixel_y = viewport_h * (tan((fov / 2) * (M_PI / 180)));
    1.    Initialize the ray vector and the ray place.
         Make sure your camera vector works, in other words, make sure your camera can rotate.
         We did it like this; it's not the most correct way, but it works (if it works, don't tough it)
forward = normalize(camera.vector);
-------------------------------------

/* Camera vector(x,y,z)
* - If camera vector(0,1,0) -> (fabs(forward.y) == 1.0)
* 	- We give it init_ray_pos(1, 0, 0).
* - In anyother case it will stay init_ray_pos(0, 1, 0).
*/
if (fabs(forward.y) == 1.0)
	right = normalize(cross_product(forward, init_ray_pos(1, 0, 0)));
else
	right = normalize(cross_product(forward, init_ray_pos(0, 1, 0)));

-------------------------------------
up = cross_product(right, forward);
pixel_direction = plus(plus(scale_vector(right, screen.pixel_delta_x),
	scale_vector(up, screen.pixel_delta_y)), forward);
return (normalize(pixel_direction));

Step 2;

🖍️ Step 2

For intersecting objects, you get different options and examples online,
take the one that makes most sense for you.
Sphere, start with the sphere, it is the easiest. (Plenty examples online)

  • Do camera, screen testing with the sphere:
               ○ Check for distortion at the edges of the screen.
               ○ Make sure you understand how your grid/position of objects works.
               ○ Make sure, if you have two spheres next to each other,
                   that they intersect correctly with each other.

Plane is easy as well.

  • Make sure you understand that a plane is infinite.
               ○ Make sure if you change the vector that all vector directions work.
               ○ It is very important to make sure that the surface normal is correct;
                   otherwise later, the light might give you problems.

Cylinder is a b**ch!

  • Cylinders consist out of two or three objects.
               ○ The body and the two caps.
               ○ The body ⇾ it will be in the beginning infinite,
                   so you need to cut/trim it at the correct height.
               ○ The caps are two planes, that are trimmed/cut as well.
                              ○ Make sure the caps are perfectly on the body,
                                 sometimes there is space in between cap and body,
                                 you will see the space once you added light.
                              ○ Make sure the surface normal of the caps is correct.
               ○ Make sure all vector directions work correctly (look correct)

Make sure you have all the objects, before adding light.
Make sure you take the object closest to the camera. (Or do this in step 3)

Correct_intersection_and_Space_between_cap_and_body


Step 3;

🖍️ Step 3

Look at the light and shadow sections.
Make sure you take the object closest to the camera. (If you haven't in step 2)
Check all surface normals, sometimes they cause problems with the light.

For example;
a Plane below a light source has the correct lighting, but a plane above a light source has no light at all.
If that happens: flip the surface normal of the plane above (make the surface normal positive), so it shows the light.

cyl->normal = mult_vecdub(cyl->vector, -1);	// Surface normal points downwards -> (mult_vecdub() = multiplying a vector and a double)
cyl->normal = cyl->vector; 			// Surface normal points upwards



🏁Checkerboard

It is not possible to create a sphere with a perfect checkerboard!

If the checked sphere looks good from the center, check the sides; there will be distortion.
So rather calculate a checkerboard sphere that touches at the poles.
Or make a texture that rotates towards the camera, so it always looks good.

perfect_and_real_checkerboard

💡Light

(A lot of "hUh??"moments)

Calculating lighting on an object involves several steps:

  • calculating the direction of light rays,
  • determining the visibility of the light source from the surface point of the object. (Shadow part)
  • and then applying illumination models such as Phong or Blinn-Phong to compute
    the final colour of the object.

🔦 Calculate Light Direction:

Determine the direction of light rays from the light source(s) to the surface point of the object.
If the light source is directional (like the sun) , you only need the direction vector.
If the light source is a point light, you'll need to calculate the direction vector from
the surface point to the light source position.

🔦 Check Visibility:

Determine if the surface point of the object is in shadow or not.

🔦 Compute Illumination:

Apply an illumination model (such as Phong or Blinn-Phong) to compute
the final color of the object at the surface point.
This involves calculating ambient, diffuse, and specular components based on the surface properties,
light properties, and view direction.

You go through each object and each object goes through each light.

For example you have;

2 objects, 3 lights.

object1 will be caclulated with light1, light2, light3
then, object2 will be calculated with light1, light2, light3

while (++i < Number_of_objects)
{
	while (++k < Number_of_lights)
	{
		- For each light we get:
			- intersection_point,
			- light_direction,
			- current light information (You need the colour, position and ratio of the current light)
		if (Shadow)
		{
			- Shadow -> visibility of the light source from the surface point of the object.
		}
		else
		{
			- Phong reflection model
				- Ambient Light (General illumination in the scene)
				- Diffuse Light (Direct light from the source)
				- Specular Light (Highlights on shiny surfaces)
		}
	}
	- Clamp Light result, so the rgb colours don't overflow, if it overflows it prints black.
}

Phong reflection model

⭕    Make sure for your Phong reflection model that your base colour is calculated
         before you do diffuse and spectular light.
         Else your texture (Checkerboard) won't have light and shadow.



⚫Shadow

Check Visibility:

Determine if the surface point of the object is in shadow or not.
You can achieve this by casting a shadow ray from the surface point towards the light source
and checking if it intersects with any other object in the scene.
If it intersects, the surface point is in shadow; otherwise, it's light.

So, instead of calculating what makes logical sence, to calculate a ray from the light to the object,
you need to calculate a ray from the object to the light.More "hUh??"moments
If the ray (on the way to the light) hits another object, shadow will be drawn.
If the ray makes it susscesfully to the light, light/Illumination will be calculated.

Shadow ray



🧲Important Things to Know

Filed of view (FOV)

Filed of view (FOV)

  • between 70° and 90° is the sweet spot. Less or more will distort the image.
  • Distortion will always happen near the edges of the image.

Normalizing a vector ⇾ Decimal numbers (0.0)

Normalizing a vector -> Decimal numbers (0.0)

  • Your number input (excluding rgb) need to take decimal numbers.
  • Thats why we normalize our vectors.
  • The normalization process ensures that the vectors have a consistent scale or magnitude,
    which can be important in certain operations such as distance calculations, clustering, or classification.
  • So normalize every vector, either in the beginning (while parsing)
    or every vector you entcounter durring your calculations.

Selecting objects

Selecting objects

Selecting objects is not part of the project.
We just thought it's a nice and easy way for the user/evaluater.
to click on a object and do something with it.

selected_and_deselected_object

WE only have the option:

  • You can select all objects, but you can only do something with the sphere.
  • If a sphere is selected you can press arrow key up    Arrow_key_up
  • It will then add the Checkerboard on the sphere.

but YOU also could:

  • Add more textures to the sphere or give other objects textures aswell.
  • Move and rotate objects.
  • Make objects bigger or smaller.

How does it work

you need mlx functions:

- mlx_key_hook();	-> is for key movement (ESC, Arrow up).
- mlx_mouse_hook();	-> is for mouse movement (selcet objects).

🐭 Mouse movement (selcet objects):
Quick explanation how the mouse map works.

  • There is a 2D mouse_map[x][y].
  • While we loop through every pixel, we fill in this map.
  • If no object is at that position fill in -1.
  • If there is an object, give it a number (intersection point found).
Example:
   Sphere1 = 0; (red)
   Sphere2 = 1; (blue)
   Plane   = 2; (green)
mouse_map:
   -1 -1 -1 -1 -1        ⬛⬛⬛⬛⬛
   -1 -1  0 -1 -1        ⬛⬛🟥⬛⬛
   -1  0  0  0 -1        ⬛🟥🟥🟥⬛
    0  0  0  0  0   ->   🟥🟥🟥🟥🟥
   -1  0  0  0 -1        ⬛🟥🟥🟥⬛
   -1 -1  0 -1  2        ⬛⬛🟥⬛🟩
   -1 -1 -1  2  2        ⬛⬛⬛🟩🟩
  • Find the position of the mouse, with mlx_get_mouse_pos();.
  • If the mouse map is -1 at that position, so no object has been selected.
  • Else if the mouse map is NOT -1 at that position. Highlight the object.
                 ○ Just set/draw a white pixel with a bit of offset inwards next to the position.
  • For removeing the highlight, remove the colour of the pixel (set it to 0).
  • How is that possible ☝️ ?
  • Use mlx_image_to_window() to put a layer on top of the 'original'
                 ○ It loads faster because it doesn't go through each pixel and recalculates everything.
  • That's why to remove the highlight, you can set that colour to 0, so it will just unset those pixels.

🗝️ Key movement (ESC, Arrow up):

🔴    -    If the pressed key is 'ESC key', close and free window.
🟡    -    If an object is selected and the key 'Arrow up' is pressed,
               we change the pattern of that object.
🟢    -    You need to count how many arrow-ups you have.
               Because arrowUp-1 should change pattern, arrowUp-2 should change
               to a different pattern or original form.
               After that, you need to reset your arrow-up count
🔵    -    Change the pattern of that object.

void	ft_key_action(mlx_key_data_t keydata, t_data *data)
{
	(void)keydata;
	if (mlx_is_key_down(data->mlx, MLX_KEY_ESCAPE)) 🔴
		mlx_close_window(data->mlx);
	if (data->mouse.selected == true && mlx_is_key_down(data->mlx, MLX_KEY_UP)) 🟡
	{
		data->objs[data->i_am]->i++;
		if (data->objs[data->i_am]->i > 1) 🟢
			data->objs[data->i_am]->i = 0;
		change_pattern(data, data->objs[data->i_am]); 🔵
	}
}

-----------------------------------------------------------------------------------------------

void	change_pattern()
{
	- Check what type the object is : if (obj->type == E_SPHERE)
	{
		- if (obj->i == 1) -> is for Checkerboard. (obj->what_pattern = 1)
		- else -> is for Normal. (obj->what_pattern = 0)
		- go through each pixel again and redo calculations (🚩 I Don't recommand 🚩)
	}
}

🚩    :    BECAUSE:    you recalculate all pixels, it will take a while.
                                    It would be wiser to use mlx_image_to_window()
                                    to put a layer on top of the 'original'.
                                    In the very beginning, initialize all pixels and store
                                    that information for each pixel in a pixel struct (object, distance, colour, light, etc.)
                                    Reuse that pixel struct, so you don't need to
                                    recalculate everything.
                                    It's also easier for if you want object rotation,
                                    so the original information is saved, and you can access it fast.

video


About Colour

About Colour

Use this webside you find you rgb colours 👉 RGB Color Picker 👈
You can pick your colour and it will give you the rgb number.

Check that your colour overlap is correct.

Colour_example

Just an example of `.rt` file

Minirt.rt file


#Ambient lighting (A): 
#Identifier         #Ratio           #R,G,B
A                   0.2              255,255,255

# --------------------------------------------------------------------------------------------
#Camera (C): 
#Identifier         #Coordinates     #3D vector     #FOV
C                   0,0,-1           0,0,1          70

# --------------------------------------------------------------------------------------------
#Light (L):
#Identifier         #Coordinates     #Ratio        #R,G,B
L                   2,2,0            0.6           255,255,255

# --------------------------------------------------------------------------------------------
#Sphere (sp):
#Identifier         #Coordinates     #Diameter      #R,G,B
sp                  0,0,-10          12.6           10,0,255

# --------------------------------------------------------------------------------------------
#Plane (pl): 
#Identifier         #Coordinates     #3D vector     #R,G,B
pl                  10,3,-10         0,1,0          0,0,225

# --------------------------------------------------------------------------------------------
#Cylinder (cy):
#Identifier         #Coordinates     #3D vector     #Diameter        #Height      #R,G,B
cy                  -5,0,-5          0,0,1          14.2             21.42        10,0,255

# --------------------------------------------------------------------------------------------
#Triangle (tr) -> optional:
#Identifier         #Coordinates1    #Coordinates2   #Coordinates3   #R,G,B
tr                  -7,6,-11         -7,-6,-11       10,0,-11        255,102,102



🔮Installation

To execute the program, follow the steps below:

  1. Compile the RayTracer by running the following command:
$ make
  1. Finally, execute the program using the following command:
$ ./miniRT scenes/sphere/sphere_circle_back.rt

OR

$ make open


📋Resources

About

This project is an introduction to the beautiful world of Ray tracing. It has the basics, and there for is a 'Mini Ray tracer'.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •