Order from us for quality, customized work in due time of your choice.
Assignment – Dynamic Allocation, File I/O, Low Level Programming, Strings, qSort
Ray Tracing
Having established the theoretical foundations for the gameplay of your new game, your studio informs you they have failed to hire any graphics programmers. Only a minor setback, as you have now been relocated to lead the graphics division. You want to make this a truly next-gen game, so it will be entirely ray traced. Unsatisfied with every game engine and rendering tool on the market, you decide to make your own in standard C to fully customize it for your needs.
Table of Contents
Assignment Overview (READ THIS FIRST!!)
Milestone Task Breakdown
General Details Regarding the Milestone Format
Milestone 1
Milestone 2
Final Submission
Implementation and Theory
What you’ve given from input
Multi-File Structure
Makefile
Vectors in 3 Dimensions
Rays
The Camera and Viewport
Representing RGB Colors Using 3D Vectors
Light and Background Color
Spheres and the World
Checking for Sphere Intersection
Conversion Between HEX and RGB Color Formats
Assigning Colors
Lighting and Calculating Color
Using PPM Files to Render Images
The Rendering Pipeline
Anti-Aliasing
Input and Output
Input
Output
Milestone 1 Output
Milestone 1 Example I/O
Milestone 2 Output
Milestone 2 Example I/O
Final Submission Output
Final Submission Example I/O
Checking
Marking Scheme
Overview
In this assignment, you will be creating a 3D renderer from scratch. The final version of your program will output an image of spheres in 3D space, rendered using semi-realistic lighting and ray tracing techniques. This may seem daunting at first, but this is very possible to do using what you’ve learned, what you will learn, and a little extra math.
This description consists of three sections: Milestone task breakdown, implementation and theory, and input/output specifications and examples. The purpose of each section is described below:
Milestone Task Breakdown: For submission timeline purposes, this assignment is broken down into two milestones (and a final submission). You should have a working part of the assignment done for each milestone. This is for you to properly test your program at different stages. The milestone section in this description is to provide you with the specifics regarding which tasks should be completed by each milestone’s deadline.
Implementation and Theory: Here we describe the way the program functions conceptually, as well as implementation requirements. This section does not necessarily describe each task in the order that they should be implemented in (that is what the milestones section is for). We recommend to make sure you understand how the overall program functions before starting with any implementation.
Input and Output Specifications and Examples: As always, we will provide you with the specifics regarding the input format and what’s expected from you in terms of output for each milestone/final submission. Finally, for each milestone/final submission, example I/O is provided as well.
The suggested workflow is:
Find the milestone you plan to work on in the milestone breakdown section.
Find the task you plan to work on.
Complete the task by referring to the implementation/theory section corresponding to the task.
Do this for each task in the milestone, testing your code as you go along.
Once you complete every task, you can check your progress by looking at the I/O examples, and by running the appropriate check script.
Fair warning: this assignment is long and challenging. There is a reason you get half the semester to do it. The milestones are there to encourage people to get started early. If you are struggling, make sure to attend lab sections and office hours to ask for help. Good luck!
Milestone Task Breakdown
This section outlines what you should be working on and should complete for each milestone. For implementation details and explanations, refer to the implementation section.
This section is designed as follows: each milestone section below will contain what needs to be completed by the due date of that milestone. Once you know what you have to work on next, refer to the implementation section of that respective task/concept, there you will find the explanations for the concepts, as well as the implementation details.
General Details Regarding the Milestone Format
You will be provided with a check script for each milestone and the final submission for you to verify your progress (more details on check).
Milestone 1 and 2 are each graded out of 10 points, and weigh 2% of your final grade. The final submission is worth 13% of your final grade (more details on the marking scheme).
While there are 3 sections for this assignment, it should be one overall program (not one file, not three programs, one multi-file program).
Your program should give different output based on the milestone/final submission, this is determined using a preprocessor macro that your program will be compiled with. Your program will be tested using different input and output according to each Milestone/final submission (more details on output).
Each student will have to demo at least one milestone.
Both Milestones have a three week period available to be completed. There is one week between the due date of Milestone 2 and the due date of the final submission.
Before You Start
Familiarize yourself with what you’re given from input which is a brief introduction of the input (this section is different than the input specifications section which is in thorough detail).
Familiarize yourself with the multi-file structure of this assignment.
Consider creating a Makefile for a smooth workflow.
Milestone 1 (Due Sunday Nov. 10 at 23:59)
Milestone 1 will always be compiled using the preprocessor macro MS1
Create assg.c (and optionally assg.h). This is where the main function should be.
vector.h provides many function prototypes, implement these functions in a new file vector.c (vector library details).
Create representations for viewport and camera (viewport and camera details).
Create representations for light and background color (light and background color details).
spheres.h provides function prototypes used by the world data type, implement them in a new file spheres.c (no need to implement doesIntersect for Milestone 1) (world data type details).
Read input and store the provided information in the previously created representations (input details, color representation details).
Give output according to the Milestone 1 output specifications.
Mandatory files for Milestone 1: src/assg.c, src/vector.c, src/spheres.c.
Important: When reading input and storing values, exclude any color for Milestone 1. In the input file, colors will be given in HEX (0x###### where each # is a hexadecimal digit). Converting HEX to RGB will be implemented after Milestone 2. Instead, you must use the default colors given here:
The default color for spheres is white (rgb(255, 255, 255)).
The default background color is black (rgb(0, 0, 0)).
We strongly recommend to start writing your Makefile by this point because of the program’s size.
See: Milestone 1 Example I/O
Milestone 2 (Due Sunday Dec. 1 at 23:59)
Milestone 2 will always be compiled using the preprocessor macro MS2
Implement the function that checks for sphere intersection (sphere intersection details).
Implement the rendering pipeline using everything implemented thus far (rendering pipeline details).
color.h provides a function prototype for a function used to write a color to a PPM file, implement this function in color.c (PPM details).
Write output to the PPM file given in the input (input details) according the Milestone 2 output specification.
Mandatory files for Milestone 2: src/assg.c, src/vector.c, src/spheres.c, src/color.c.
See: Milestone 2 Example I/O
Final submission (Due Sunday Dec. 8 at 23:59)
The Final Submission will always be compiled using the preprocessor macro FS
Complete Makefile, which should compile all three parts of the assignment with the appropriate preprocessor macros. (Makefile details).
color.h provides a function prototype for a function that converts from HEX to RGB, implement this function in color.h (HEX and RGB details).
Implement color sorting to assign each sphere its correct color (details about assigning colors).
Update the code that reads input and builds the world so that it uses the new HEX to RGB function to store the proper colors for the background and each sphere (input details, HEX and RGB details).
Implement anti-aliasing into the rendering pipeline (anti-aliasing details).
Mandatory files for the Final Submission: src/assg.c, src/vector.c, src/spheres.c, src/color.c, Makefile.
See: Final Submission Example I/O
Implementation and Theory
This section explains both the math, and the implementation concepts needed to complete the assignment. We will of course not give you all the specifics since we want you to implement the grand majority of the program yourself.
Use this as a guide for the concepts needed for you to complete the assignment. Do not use this section for order of implementation, for that please refer to the Milestone breakdown section before this one. Once familiar with the concepts and process, your task is then to translate that into C code as specified.
What you’re given from input
First, we briefly introduce what is given as input, this will make the implementation easier to explain since you will know what will be given in advance. Both milestones and the final submission use the same input format. For thorough details on input, please refer to the input specification section.
You are given:
Image width and height.
Viewport height.
Camera focal length.
Light position and brightness.
The number of colors in the world.
A list of colors in HEX.
Background color index.
The number of spheres in the world.
Position, radius, and color index of multiple spheres.
Don’t worry if you don’t understand all of this, it will be explained in a moment.
Multi-File Structure
This assignment is large, and so multiple files will be used for organization. This is what the source directory src will look like:
src/|– [assg.h]|–
Files enclosed in <> are not provided and are mandatory to include in the final submission. These are files that you must create and implement.
All other files are provided and are mandatory to include in the final submission. These files contain function prototypes that are given to you and must not be changed. You may however, implement your own functions for which you may declare the prototypes in these files.
Important: Refer to the milestone task breakdown section for details about which files are expected from you for each milestone/final submission.
Important: The main function goes in assg.c.
Note: Make sure to include the corresponding header file when implementing the functions. For example in vector.c you should include vector.h using #include “vector.h”.
Makefile
For the final submission of the assignment, you are required to submit a Makefile (named Makefile in the root directory). This Makefile should be able to compile all three parts of the assignment with each of their appropriate preprocessor macros defined (i.e. Milestone 1 executable should be compiled with the macro MS1 defined, Milestone 2 with MS2, etc).
When running make, your Makefile should do the following:
Create three executable files, one for each section of the assignment. These executable files
Should be named MS1_assg, MS2_assg, and FS_assg corresponding to the assignment sections Milestone 1, Milestone 2, and Final Submission respectively.
Should all be located in the root directory.
Should be created by compiling your code defining the MS1, MS2, or FS preprocessor macro depending on to the assignment section.
When ran with the appropriate input, should produce the output corresponding to its assignment section.
Delete all intermediate object files.
Important: FS_check.sh and our making scripts will use your Makefile to compile your final submission, so make sure it functions as intended.
We recommend you to begin writing your Makefile as soon as you learn how to. It will make your workflow much smoother since otherwise you will have to link and compile many object files every time you want to test your program.
Vectors in 3 Dimensions
A 3D vector is a way to represent direction and magnitude in 3D space. A vector can denote a point in space, a direction, or many other things. In this assignment, they will be used to describe direction and position of objects in our world. A 3D vector can be represented using 3 real numbers, and they are represented using an arrow and written as
v→=(a,b,c)
Where a,b,c are real numbers. Since we’re using vectors to represent positions and directions, we can think of a corresponding to the x axis, b to the y axis, and c to the z axis.
In C, structs make it very easy for us to implement a 3D vector type:
typedef struct { float x; float y; float z;} Vec3;
For this assignment, you are tasked with implementing functions that will perform operations on the Vec3 type defined above. The prototypes for these functions, as well as the type definition above, are given in a file vector.h in the source directory src. Create a vector.c file to implement these functions. Make sure to include vector.h at the top of vector.c as #include “vector.h”.
The following function prototypes are given to you in vector.h and are for you to implement in vector.c. Do not change anything in the header file vector.h.
Vec3 add(Vec3 v1, Vec3 v2);Vec3 subtract(Vec3 v1, Vec3 v2);Vec3 scalarMultiply(float s, Vec3 v);Vec3 scalarDivide(Vec3 v, float d);Vec3 normalize(Vec3 v);float dot(Vec3 v1, Vec3 v2);float length2(Vec3 v);float length(Vec3 v);float distance2(Vec3 v1, Vec3 v2);float distance(Vec3 v1, Vec3 v2);
The operations to be implemented are defined (mathematically) below:
Let v→=(v1,v2,v3) and u→=(u1,u2,u3). Then the operations you will implement are defined as follows:
Vector addition:
v→+u→=(v1+u1,v2+u2,v3+u3)
Vector subtraction:
v→−u→=(v1−u1,v2−u2,v3−u3)
Scalar Multiplication:
sv→=(sv1,sv2,sv3)
Scalar Division:
v→s=(v1s,v2s,v3s)
The Dot Product:
v→⋅u→=v1u1+v2u2+v3u3
This represents the cosine of the angle between the two vectors.
The Length of a Vector:
|v→|=v12+v22+v32=v→⋅v→
You are also tasked with implementing length2 which should calculate length squared (this is sometimes useful since it skips the square root calculation, which can be very slow).
Distance Between 2 Vectors:
dist(v→,u→)=|v→−u→|
You are also tasked with implementing distance2 which should calculate distance squared.
Normalization of a Vector:
normv→=v→|v→|
This represents a new vector that is in the same direction as v→, but has length equal to 1.
NOTE: As you can see, you will need to use a square root function. You must include math.h to make use of the sqrt function. Make sure to link your program using -lm. More details can be found in the compiling and linking section below.
Rays
You may have heard of the term “ray tracing” when talking about computer graphics. Well, you will be implementing it in this assignment (to some extent). You will be casting rays from the camera into the world, and if the ray hits anything, an additional ray is casted to check for shadows. However, there will be no additional reflection or refraction rays.
A ray is simply a line in 3D space that we represent using two 3D vectors, a position vector p→, and a direction vector d→. The position vector describes any point on the line. The direction vector describes the direction in which the line (ray) extends into. We can write any line as a function using these two vectors:
R(t)=p→+td→
where t is any scalar. One can see that inputting different values t into the R yields different points on the line. In fact, any point on the line can be given by R(t) for some real number t.
Using our implementation of vectors, we can easily represent a ray in C using either structs, or just two vectors.
Implementation: To represent a ray, you must use the Vec3 type defined in vector.h for the direction and position vector (each ray will be two Vec3 variables).
With the help of a camera and a viewport, we will create rays, shooting them from the camera’s position, into each pixel of the image. From each ray, a color is calculated, and a final image is produced.
The Camera and Viewport
The purpose of rays is to check if they intersect with anything in our world. Each ray corresponds to exactly one pixel (for now). After a ray intersects with any geometry, the color is calculated and the pixel is colored accordingly. In order to cast rays through each pixel of our image, we need to map the dimensions of our 2D image, into a 2D viewport in the 3D world.
The viewport is simply a rectangle in front of the camera. All rays are shot from the camera (a single point) in the direction of, and through the viewport. Its purpose is to determine the image’s field of view, scale, viewing direction, etc.
Implementation of the camera and viewport:
The position of the camera should always be (0,0,0).
The viewport height is given through input.
The viewport width is calculated to match the image aspect ratio.
The distance from the camera to the middle of the viewport is the focal length, which is given through input.
The camera should always point towards the negative z axis. Meaning the viewport’s z value should be -focalLength. This means the viewport is always parallel to the x and y axes.
Notice how the dimensions of the viewport are not the same as the image’s. We call these two coordinate systems the world space, vs the image space. To calculate the point in the viewport each pixel is shot through, each pixel’s image coordinates need to be scaled to match their respective world coordinates.
How can I shoot rays using the camera and viewport?
The viewport is the rectangle where the world is projected onto. It is divided into many pixels where one ray is shot through each. The ray then tells its pixel if there is any geometry in that direction and what color it should be. The final result is an image of the 3D world.
To produce a ray, we must calculate a position vector and a direction vector.
The position vector will always be the camera’s position (which is (0,0,0)).
The direction vector will go from the camera to the center of one pixel (HINT: Subtracting two vectors v→−u→ results in a new vector that goes from u→ to v→).
Make sure that the ray goes through the center of the pixel (as seen in the graphic above). The pixel position coordinates correspond to the corner of the pixel, not the center.
Important: Make sure your representation of the camera and viewport can be accessed throughout assg.c. They should be global variables.
Representing RGB Colors Using 3D Vectors
Programmers and artists like to work with the RGB color format. The format is represented as three numbers, respectively being the amount of Red, Green, and Blue.
You may be familiar with representing RGB values on a range from [0,255], however since our Vec3 implementation uses float, you will be representing RGB on the range [0,1].
So, red will be (1.0, 0.0, 0.0), white (1.0, 1.0, 1.0), black (0.0, 0.0, 0.0) etc.
Light and Background Color
In this assignment, there will only be one light source. This light source is a single point in space where all light comes from. The light’s position will determine the shading applied on each surface. Furthermore, the background color is not affected by light and is always the same: this should be represented the same way all color is represented: Vec3 RGB in the range [0,1].
The light source has two attributes:
Position
Brightness
The background color, the light’s position and brightness are all provided through input.
Refer to Assigning Colors for determining the background color in HEX, and Conversion Between HEX and RGB Color Formats to decode the RGB color.
Important: Make sure your representation of the light and background color can be accessed throughout assg.c. They should be global variables.
Spheres and the World
As you know, the image you will be generating will consist of spheres alone. Each sphere in this assignment will have 3 attributes: the position for its center, its radius, and its color. Given to you in the header file spheres.h is the following type definition
typedef struct { float r; Vec3 pos; Vec3 color;} Sphere;
The vector data type (different from Vec3):
The vector data type is a dynamic array that keeps track of the amount of memory allocated, and the size. It allows for easy and efficient insertion. Initially, the vector begins with a capacity of one and size zero. It also doubles its capacity once the size surpasses the old capacity.
In spheres.h, you will be implementing a dynamic array for these spheres. This way you can easily manage all spheres dynamically. This will be implemented using the vector data type (again, this is different that Vec3). The following type definition is given:
typedef struct { Sphere **spheres; int size; int capacity;} World;
spheres is the array of sphere pointers, size is the number of spheres in the array, and capacity is the number of spheres able to be stored by the array determined by the allocation size of it.
The following four function prototypes are given in spheres.h for you to implement in spheres.c:
void worldInit(World *world);void freeWorld(World *world);void addSphere(World *world, Sphere *sphere);Sphere *createSphere(float radius, Vec3 position, Vec3 color);
Implement these functions appropriately in order to manage memory and all the spheres according to the vector data type.
Additionally, addSphere should append the given sphere pointer to the end of World.spheres.
Important: Make sure that you are managing memory correctly when implementing dynamic arrays. Details on how leaking memory will impact your grade are found in the marking scheme section.
Checking for Sphere Intersection
This is a crucial step in the assignment. Knowing if a ray intersects is how you determine the color that will be outputted. Any errors here may result in: spheres behind the camera being rendered, nothing being rendered, light direction being wrong, etc.
This is a long section as it is math intensive and we want to explain things thoroughly and provide implementation details.
You will be implementing the following function in spheres.c, where the prototype is provided to you in spheres.h:
int doesIntersect(const Sphere *sphere, Vec3 rayPos, Vec3 rayDir, float *t);
This function should check if sphere intersects with the ray rayPos + t*rayDir. If there is an intersection then it should provide the value of t through the pointer float *t and return 1. This can then be used to calculate the intersection point and normal vector. If there is no intersection then it should return 0.
The Math
As we know, a ray (line) starting at a point p→, going in direction d→ can be written as
R(t)=p→+td→,t∈R
A sphere with center position c→ and radius r can be written as
|x→−c→|2=r2
where x→ is a point on the surface of the sphere. This can be rewritten using the dot product
(x→−c→)⋅(x→−c→)=r2
To see if a ray R(t) intersects the sphere, we can replace x→ with R(t)
(R(t)−c→)⋅(R(t)−c→)=r2
The ray function can be replaced with its parametric equation defined above
(p→+td→−c→)⋅(p→+td→−c→)=r2
this can be manipulated into a quadratic equation with variable t
t2(d→⋅d→)+2td→⋅(p→−c→)+(p→−c→)⋅(p→−c→)−r2=0
Obtaining the discriminant of this quadratic equation tells us how many intersections there are with the sphere
discriminant=b2−4ac⟹discriminant{<0No intersections (2 complex roots)=0One intersection>0Two intersections (2 real roots)
If there are any intersections, the quadratic can be solved using the quadratic formula
t=−b±b2−4ac2a
Then if there are any intersections, the intersection point will be at
p→+td→
Pseudocode Overview
Here we provide pseudocode outlining the process of implementing this function.
This is not code you should copy and paste directly as it is missing parts and uses overloaded operators for vectors which you do not have access to. Use this to guide you through implementing the calculation of sphere intersection in your assignment, filling in any missing sections.
Vec3 rayPos; // Ray positionVec3 rayDir; // Line directionVec3 spherePos; // Sphere centrefloat sphereRadius; // Sphere radius
// Get a, b, c for the quadratic formulaVec3 V = rayPos – spherePos;float a = dot(rayDir, rayDir);float b = 2*dot(rayDir, V);float c = dot(V, V) – sphereRadius*sphereRadius;float discriminant = calculateDiscriminant(a, b, c);
// Check the discriminant.// This will tell us if there are any intersections.if (discriminant < 0); // Does not intersect (complex roots)if (discriminant >= 0); // Does intersect (real root(s))
// Solve for t using the quadratic formulafloat t1, t2;solveQuadratic(a, b, c, &t1, &t2);// t1 and t2 are the factors for which rayDir is scaled// to yield an intersection point.
if (t1 < 0); // First intersection is behindif (t2 < 0); // Second intersection is behind
if (t1 > 0); // First intersection is in frontif (t2 > 0); // Second intersection is in front
// The solution must be positive, we dont want to render// anything that is behind the camera.// If ray intersects the spheres in two// positions (entering and exiting the sphere)// then we only care about the one entering// the sphere (the lower one).float t = /* Choose t1 or t2 if there is an intersection */;
// We can finally find our intersection and normalVec3 intersectionPoint = rayPos + t*rayDir;Vec3 normal = intersectionPoint – spherePos;// NOTE: the normal vector is called normal but it// does not need to be normalized.
// Calculate a colour based on these two vectors
Just like vector.c, you may need to use sqrt in spheres.c in order to implement doesIntersect. Again, make sure you include math.h and link your program using -lm. More details in the compiling and linking section below.
Conversion Between HEX and RGB Color Formats
Provided to you is the header file color.h. Since the input will provide color not in RGB in the range [0,1], but in HEX, you will have to convert from HEX to RGB.
The HEX format is simply the RGB format on the range [0,255], where each of the three values is in hexadecimal, and represented as a single string. For example, all the following represent the same color in different formats:
When reading any color from the input file, it will be in HEX (will always be in the format 0x###### where # is a hexadecimal digit). You have to store it in unsigned int; this is because you will have to unpack this value and convert it into RGB [0,1] using Vec3.
Using the exmple above, 0x00FF33, which looks like 00000000 11111111 00110011 in binary, should be converted to a Vec3 in the form [0.0, 1.0, 0.2].
Given to you in color.h, is the following function prototype that should be implemented by you in color.c. This function should convert from HEX to RGB [0,1] and return it. Implement it using the process described above.
Vec3 unpackRGB(unsigned int packedRGB);
Assigning Colors
There is one additional complication. You are provided with the color index each sphere and the background should be, and a list of colors in random order.
You need to:
Read the list of colors in HEX (you are provided with the number of colors, and a list of colors, see input specifications).
Sort the list of colors using qsort according to the following comparison function. You may simply copy and paste this function into your color.c file.
int compareColor(const void *a, const void *b){ int a1 = 0, b1 = 0; for (int i = 0; i < sizeof(int); i++) { a1 |= (*((unsigned char*)a + i) & 0x0F) << (i * 8); b1 |= (*((unsigned char*)b + i) & 0x0F) << (i * 8); } return (a1 < b1) ? -1 : (b1 < a1) ? 1 : (*((int*)a) < *((int*)b)) ? -1 : (*((int*)a) > *((int*)b)) ? 1 : 0;}
Each sphere has a color index. This index corresponds to the sorted list, so a sphere color of index 0 will be the HEX color at index 0 of the list, and so on.
The background color also has an index, as it is simply one of the available colors.
Note that several spheres may have the same color index, and one or more spheres may be the same color as the background. However, you will not encounter duplicate colors in the list.
Lighting and Calculating Color
After calculating intersection, the program has everything it needs to know to calculate a final pixel color and where to write it to. All the things the program needs to know are:
(x,y): Pixel to write to.
S→: Color of the sphere.
l→: Light position.
b: Light brightness.
p→: Intersection point.
n→: Surface normal.
The final color in RGB, given by P(x,y) should be calculated as follows:
P(x,y)=IS→
Where I is the intensity of the light. It makes sense for this intensity to decrease gradually as it’s distance to the intersection point increases. This is why the intensity is calculated as
I=min(1,I0) where I0=b×max(d→⋅n→,0)dist(l→,p→)2
Where d→ is the normalized light direction from the intersection point (going out of the intersection point, not into) (HINT: subtract and normalize functions should prove useful when calculating d→).
We clamp the dot product between d→ and n→ to be ≥0 in order to prevent a negative intensity value.
NOTE: It is very important to cap the intensity at 1.
Casting Shadows onto Surfaces
Once an intersection is found by a ray, there is a possibility that there is another sphere is blocking the light. This would result in a shadow.
Simulating this can be done using what has been implemented so far.
Implementation:
Once a ray intersects with a sphere and the lighting is calculated at that point (details in the lighting section), call this color value surfaceLightingColor, checking for a shadow should be done in this way:
Cast a ray from the intersection point in the direction of the light.
Check if that ray intersects with anything.
If it does, then scale surfaceLightingColor down by a factor shadowFactor, this will make the surface appear darker.
If not then there is no shadow here and surfaceLightingColor is the final pixel color.
shadowFactor is always equal to 0.1
Important: When casting a ray to check for shadows, make sure that the ray’s position vector is not equal to the intersection point exactly. It should be slightly moved in front (this is to avoid floating point precision causing the ray to begin inside the sphere).
This can easily be fixed by moving the intersection point slightly in the surface normal vector’s direction.
If i→ is the intersection point and n→ is the surface normal at that point, then the ray that checks for shadows’ position vector p→ should be calculated as
p→=i→+0.001n→
Not doing this will result in very small visual errors such as small black spots on spheres.
Below is the difference between casting shadows, and not casting shadows:
Using PPM Files to Render Images
After calculating a color for a pixel, you will have to write it to a PPM file (just like lab 3). You can learn more about this format here, or read the below section for the relevant information.
Here is an example Portable Pixel Map (PPM) image:
P33 2255255 0 0 0 255 0 0 0 255255 255 255 0 0 0 120 120 120
The first 3 lines is the header and should always be the following:
P3 specifies that this PPM file is in ASCII.
3 2 are the image width and image height. Change these accordin
Order from us for quality, customized work in due time of your choice.