This is a read-only archive of the old Scratch 1.x Forums.
Try searching the current Scratch discussion forums.

#1 2007-09-25 20:48:00

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

How my Raytracer works

This post is directed toward Oldschooler2 in regards to my raytracer project http://scratch.mit.edu/projects/Canthiar/5347

I'm going to break this up into many posts since it's a little complicated and there is a small amount of background math that needs to be discussed.  I'm going to preface this entire discussion by stating that I am not a mathematics expert so I may make some mistakes along the way, but I will try my best to be as accurate as possible. 

The first few posts will introduce the mathematics required, followed by how to apply that math, and then I will describe what I had to do to convert all of that into a Scratch program.  It will probably take me several days to post all of the relevant parts.

Please feel free to ask questions along the way.

Last edited by Canthiar (2008-01-03 13:27:09)

Offline

 

#2 2007-09-25 20:51:26

toontownmiser
Scratcher
Registered: 2007-06-28
Posts: 18

Re: How my Raytracer works

Yaay!  I've been wondering how that works.


Algebra I Lesson for Today:
(cat)+(dog)=(fish)
Given: c=1, a=1, t=2  d=1,o=1, g=2 and f=1, i=1, s=2, s=2
True or False?

Offline

 

#3 2007-09-26 00:26:06

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

Scalars, Points, and Vectors

I'm going to start by talking about scalars and vectors.  A scalar is just a regular value.  It has no dimensions and can be used to express such things as size, length, speed, or scale.  If you create a variable in Scratch that variable is considered a scalar.

A vector is a single dimensional set of values.  With a vector you can express both size and direction.  If we take the scratch variables x and y and put them together then that would be a vector expressed as [x y].  If we create a vector variable it will normally be denoted with a bar over it or with an apostrophe.  So we would say that A' = [ x y ].

A point is similar to a vector in that it is a single dimensional set of values, but it represents a position, rather than a direction like a vector.  Normally when defining a point there is no apostrophe: B = [ x y ]

If you have two points then you can create a vector by subtracting one from the other.  If you have a point A defined as an [ x y ] position and you have a point B defined as an [ x y ] position then to calculate a vector between A and B you would subtract the x value in A from the x value B and the same with the y values to get the vector AB' = B - A.

Here is a Scratch program that illustrates the differences between points and vectors:  http://scratch.mit.edu/projects/Canthiar/39385

Please let me know if you see any errors or have any questions.

Offline

 

#4 2007-09-26 01:43:24

andresmh
Scratch Team at MIT
Registered: 2007-03-05
Posts: 1000+

Re: How my Raytracer works

The project illustrates it very well. I'd be following your explanation of the raytracing project works because I have not had the time to analyze your code  smile


Andres Monroy-Hernandez | Scratch Team at the MIT Media Lab
on identi.ca and  twitter

Offline

 

#5 2007-09-26 10:03:42

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

andresmh wrote:

The project illustrates it very well. I'd be following your explanation of the raytracing project works because I have not had the time to analyze your code  smile

The code is a little hard to read because I did a lot of copy paste to speed things up. Hopefully this will help clear things up. 

It's going to be a few more posts before I get into how the program works because I want to kind of give an overview of the math that is involved.  I'm going to skip over a lot of stuff that you would normally learn about in a complete course of linear algebra, but hopefully that will allow me to get through this faster.

Offline

 

#6 2007-09-26 11:47:42

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

The Scalar Component of a Vector

In my last discussion I mentioned that a vector has direction and size.  For many calculations you will want to separate out the direction and size components.  The proper way to do this is to use inner products, but this delves a little too deep into high level mathematics.  Instead I'm going to show how to do it using the Pythagorean theorem since it is easy to model using just x and y.

The Pythagorean theorem states that for any right triangle the square of the hypotenuse is equal to the sum of the squares of the other two sides.  If we apply this to the vector then we would get an equation that looks like this: length*length = x*x + y*y.  To get the length apply a square root to both sides:  sqrt( length * length ) = sqrt( x*x + y*y ), which yields length = sqrt( x*x + y*y ).

The length of a vector is called the norm and for a vector AB' it is written like this: |AB'|, with lines on both sides.  The norm of a vector is a scalar since it is only the size component of the vector.

To get the just the direction component of a vector you simply divide it by its length.  So direction x = x / length and direction y = y / length, which can be expressed as the vector direction' = [direction x, direction y].  The equation can also be expressed as direction' = AB' / |AB'|.

This direction vector will have a length of 1 and is called a unit vector since it is only a single unit in size.  This is also sometimes referred to as a normalized vector.  This unit vector can be multiplied by a different scalar to make a vector that is longer or shorter than the original vector, but still points in the same direction.

--------

I've written a lot of text so I'm going to take a short break to put together a working example and to answer any questions.

edit: Here is a link to the Scratch program that illustrates how to get the length of a vector: http://scratch.mit.edu/projects/Canthiar/39547

Last edited by Canthiar (2007-09-28 15:23:37)

Offline

 

#7 2007-09-28 12:11:23

bigB
Scratcher
Registered: 2007-06-09
Posts: 100+

Re: How my Raytracer works

thankyou for taking the time to do this.  i am looking forward to seeing how you incorporated this.


http://scratch.mit.edu/projects/bigB/260981 Draw to Text
http://scratch.mit.edu/projects/bigB/181829 3D Stunt Flyer

Offline

 

#8 2007-09-29 13:03:05

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

Dot Product

The dot product, also called the inner product, is an operation where all of the elements in the same position in a vector are multiplied together and then all of those values added into a single scalar value.  If we have a vector A' and a vector B' then the dot product would be A'.x * B'.x + A'.y * B'.y.

The dot product returns a scalar value that is basically how much the two vectors point in the same direction multiplied together.  Using this property of the dot product useful functionality can be derived from it.

The most useful one is getting the angle between two vectors.  The dot product of two unit length vectors is the cos of the angle between them.  cos(angle) = A' dot B'.  If the vectors are not unit vectors you can divide the dot product by the product of the norm of each vector.  cos(angle) = (A' dot B') / ( |A'| * |B'| ).

Another useful thing that a dot product allows for is to project a vector onto another vector.  You do this by taking a vector by calculating the dot product with a unit length vector.  Then you multiply the scalar by the unit length vector.  projected = ( A' dot B' ) * A'.  If the vector that you want to project onto isn't a unit vector then you can divide by the norm of the vector that you are projecting onto.  project = ( A' dot B' ) / |A'| * A'.

The dot product can also be used to calculate the distance that point is from the surface of a plane.  I will go over this when I talk about collisions with planes.

edit: Here is a Scratch program to illustrate some of the properties of the dot product http://scratch.mit.edu/projects/Canthiar/40821

Last edited by Canthiar (2007-09-30 13:12:02)

Offline

 

#9 2007-09-30 13:18:07

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

I'm making assumptions about how much math people know in these posts.  If there is anything that you don't know and want clarification on then please let me know.

The next few posts will talk about rays and how they intersect with objects using the methods mentioned above.

edit: Also please let me know if the timing in the presentations is too fast.  Since I'm familiar with the topics it's hard for me to know if I'm skipping anything or if it takes extra time to understand the material.

Last edited by Canthiar (2007-09-30 13:31:24)

Offline

 

#10 2007-09-30 19:29:34

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

The Ray

The ray is what fundamentally makes a ray tracer work.  A ray is basically a line that has a beginning, but no end.  The ray is made of two components; a beginning point and a unit length vector to denote direction. 

Since the vector is a unit length any scalar that you multiply the vector by will set the vector to that length.  This allows any position along the ray to specified by a scalar.  This scalar is normally called the time along the ray.  The equation for finding a point at time along the ray is: position = ray_start + ray_direction * time.

When casting a ray into a scene it may or may not point at an object in the scene.  If it does point at an object then the time along the ray at which it intersects has to be calculated.  If the ray points at multiple objects then the object that creates the lowest time is the one that is closest to the start of the ray.

Offline

 

#11 2007-09-30 21:02:22

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

The Ray in the Raytracer

In the real world photons are generated from a light source, such as the sun or a light bulb, and they are reflected off objects and back to your eye.  The problem is that a light source generates too many photons to calculate quickly.  Instead a raytracer will start a ray at the eye and point it toward a set of objects.

When the ray intersects an object rays will be generated to check for light and reflected objects.  A ray will be generated at the point of intersection pointing toward each light in the world, if the ray doesn't intersect any objects before it gets to the light then no shadow is cast on the original object.

Offline

 

#12 2007-10-02 12:50:21

DrJim
Scratcher
Registered: 2007-05-26
Posts: 100+

Re: How my Raytracer works

You are really doing a good job making the basic principles behind your program clear.

Thanks for taking the time to make the effort.

Offline

 

#13 2007-10-03 14:02:04

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

Intersection

In a pure mathematical way intersections are done by taking the equation for a shape and the equation for the ray and finding the intersection.  This is normally done using a quadratic, cubic, or quartic equation to solve the roots in both equations.

If you define a sphere as any position that is radius distance from a center point then you can use a norm to to write a sphere equation as: radius = | position - center |.  We would then use the ray equation that was used in a previous discussion(position = ray_start + ray_direction*time) to find the intersection between the two equations.

Substitute the ray equation into the sphere equation:

radius = | ray_start + ray_direction*time - center |

Now solve for time:

radius = | (ray_start-center) + ray_direction*time |

raidus = sqrt( (ray_start-center) + ray_direction*time ) dot ( (ray_start-center) + ray_direction*time)

radius^2 = ( (ray_start-center) + ray_direction*time ) dot ( (ray_start-center) + ray_direction*time)

radius^2 = (ray_start-center) dot (ray_start-center) + (ray_start-center) dot ray_direction*time + (ray_start-center) dot ray_direction*time + ray_direction*time dot ray_direction*time

radius^2 = (ray_start-center) dot (ray_start-center) + 2(ray_start-center) dot ray_direction*time + (ray_direction dot ray_direction)*time^2

0 = (ray_start-center) dot (ray_start-center) - radius ^2 + 2(ray_start-center) dot ray_direction*time + (ray_direction dot ray_direction)*time^2

Pulling out time for use in a quadratic equation you get

A = (ray_direction dot ray_direction)
B = 2(ray_start-center) dot ray_direction
C = (ray_start-center) dot (ray_start-center) - radius ^2

let dot_product = (ray_start-center) dot ray_direction
let to_center = |ray_start-center|
ray_direction dot ray_direction is 1 since ray_direction is a unit length vector.  With these substitutions the values become:

A = 1
B = 2 * dot_product
C = to_center^2 - radius^2

The quadratic equation is defined as: time = -B +- sqrt( B^2 - 4AC) / 2A

Plugging in the values:

time = 2*dot_product +- sqrt( (2 * dot_product)^2 - 4 * (to_center^2 - radius^2) ) / 2

time = 2*dot_product +- sqrt( 4 * (dot_product^2) - 4 * (to_center^2 - radius^2) ) / 2

time = 2*dot_product +- 2 * sqrt( dot_product^2 - to_center^2 - radius^2 ) / 2

time = dot_product +- sqrt( dot_product^2 - to_center^2 - radius^2 )

For our purposes we only need the closest time, so the equation becomes:

time = dot_product - sqrt( dot_product^2 - to_center^2 - radius^2 )

For any value less than 0 or for any square root of a negative number there is no intersection.

Here is a Scratch program to demonstrate this: http://scratch.mit.edu/projects/Canthiar/40988

Offline

 

#14 2007-10-05 01:07:49

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

Normal Vector and Reflection Vector

At the point where the ray intersects an object in order to do reflection and lighting the direction that the surface is facing needs to be known.  The direction is represented by a unit length vector called a surface normal.  Depending on the object that is being collided with the normal will be calculated differently.  For a sphere the calculation is just the unit vector pointing from the center of the sphere to the point of intersection.

When a ray hits the surface it is reflected off the surface.  This is needed for certain lighting effects and to find any reflections for shiny surfaces.  This is done by reflecting the vector portion of the ray with the surface normal.  If you have a ray pointing way from the surface, then we can project the ray  onto the normal using a dot product and multiplying the result by the normal.  Then subtracting the original ray creates a vector vector that can be added back to the original vector to reflect it around the normal

The equation would look like this:

reflection = ray + 2 * ( ( ray dot normal ) * normal- ray )

This can then be reduced:

reflection = ray - 2 * ray + 2 * ( ray dot normal ) * normal

reflection = 2 * ( ray dot normal ) * normal - ray

The reflection equation can be used to calculate the intensity of reflected light or to create a ray that can be recast into the scene from the intersection position.

Link to scratch project that illustrates how the reflection calculation works: http://scratch.mit.edu/projects/Canthiar/41976

Fixed bad equation, added link to project

Last edited by Canthiar (2007-10-05 09:47:40)

Offline

 

#15 2007-10-05 15:04:35

bigB
Scratcher
Registered: 2007-06-09
Posts: 100+

Re: How my Raytracer works

so how much quicker would you raytracer run in scratch had direct functions such as cos, sin,tan and sqrt?


http://scratch.mit.edu/projects/bigB/260981 Draw to Text
http://scratch.mit.edu/projects/bigB/181829 3D Stunt Flyer

Offline

 

#16 2007-10-05 15:55:26

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

Having a sqrt function would certainly make the code cleaner, but I don't think it would speed things up by much.  The virtual machine in Scratch is kind of slow, plus loops and broadcasts seem to cause task switching and the screen to refresh which can slow things down considerably. 

As an experiment I inlined all of the Broadcast and Wait messages to increase the speed, but it just made it really difficult to work with the code.

I got a little bit of a speed up by inlining a Broadcast and Wait that was used for the ray cast intersection section.

Offline

 

#17 2007-10-09 00:20:32

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

Illumination

In order to see objects in a ray traced scene they need to interact with light sources.  There are many different types of light sources such as point, directional, spot, and area.  Each object also has a material that reflects light differently and with different colors.

Normally there are several components to how a surface reflects light.  The most common are ambient, diffuse, and specular. 

Ambient simulates all of the light reflecting off of everything in the scene.  It is the darkest something can get if there is no light directly shining on it.  Ambient light is a constant value applied regardless of light level.  It is very easy to calculate color = surface_color * ambient_color

Diffuse simulates light that indirectly reflects off of a surface.  When light hits a surface it will scatter into many directions.  The most common calculation for diffuse lighting is Lambert lighting.  It is the dot product between the surface normal and the direction to the light source.  The calculation looks like this color = (normal' dot to_light') * surface_color * light_color.  The vector to_light' can be the direction of a directional light or a vector from the point on the surface pointing toward the light source. 

If it's a point source then the light can optionally attenuate with distance.  This means that the further away you get from a light source the dimmer it will be.  This is done by dividing the distance from the surface to the light source by some maximum distance that the light can travel.  To prevent an unnatural linear falloff this attenuation value is squared or cubed.  You can simulate different atmospheric effects by combining some value of squared and cubic attenuations with different attenuation colors.

Specular simulates light that directly reflects off of the surface.  When light hits a surface it will reflect based on the direction that the surface is facing.  The most intense will be reflected at an angle between the viewer and the light source.  There are two popular specular models; Blinn and Phong.  Phong uses the reflection vector that had been mentioned earlier.  Blinn uses what is referred to as a half-way vector that is meant to be fast to compute.  Since I was already calculating a reflection vector I went with Phong.  Phong is basically color = ( reflected' dot to_light') ^ phong_power * phong_color * light_color.  Phong technically has the ambient and diffuse component in the same equation.

Reflected light can also come from other objects in the scene.  Depending on how shiny a surface is this can make an object look a mirror.  This is simply done by casting another ray into the scene for the point of intersection in the reflected direction.

Once all of the lighting values have been calculated they are just added up along with contributions and capped at 100%.  A value at or greater than 100% is considered fully saturated.  final_color = ambient_color * ambient_intensity + diffuse_color * diffuse_intensity + specular_color * specular_intensity + reflection_color * reflection_intensity.

Shadows are created by casting a ray to a light source, if there is an object between the start of the ray and the light source then only the ambient and reflective components contribute to the surface color.

Last edited by Canthiar (2007-10-09 17:05:43)

Offline

 

#18 2007-10-09 09:34:15

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

Getting it all into Scratch Part 1

I will take several posts to talk about how all of the above math was used to get a ray-tracer written in Scratch.  Scratch has pretty much all of the math required, the only thing missing was the ability to make unit length vectors because it requires a square root.

There are many ways to get a square root, easiest way is through a form of numerical integration called the Newton-Raphson method.  The method requires that you start with an initial guess and iterate a number of times with both the original value and the estimated result.  The Newton-Raphson square root looks like this:

estimate = 1/2 * ( estimate + square / estimate )

Each time it gets called the estimate gets closer to the real value.  The initial estimate can be the square value.  So to get a unit vector in scratch you just do the following

square = (vector.x*vector.x + vector.y*vector.y + vector.z*vector.z)
estimate = 1/2 * ( estimate + square / estimate )
estimate = 1/2 * ( estimate + square / estimate )
estimate = 1/2 * ( estimate + square / estimate )
estimate = 1/2 * ( estimate + square / estimate )
estimate = 1/2 * ( estimate + square / estimate )
estimate = 1/2 * ( estimate + square / estimate )
estimate = 1/2 * ( estimate + square / estimate )
estimate = 1/2 * ( estimate + square / estimate )
unit_vector.x = vector.x / estimate
unit_vector.y = vector.y / estimate
unit_vector.z = vector.z / estimate


The resulting unit vector may not have a length of 1, but it will be really close.

Offline

 

#19 2007-10-11 11:14:14

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

Getting it all into Scratch Part 2

Creating the initial ray that goes into the scene is fairly simple.  Define some point in space and designate that point to by your camera position, that will be the start of the ray.  Then update x and y over the screen.  The direction of your ray will simply be [x y 1] / |[x y 1]|.

There is a problem with generating your rays like this.  The ray will point in a very wide range of directions and will change based on the size of the image.  To fix this we will have to divide the x and y value in the range by some value to make it more reasonable.

I'm going to take you though some steps that I didn't do in my original ray tracers since I wasn't as familiar with Scratch as I am now.

With a camera or normal vision you can only see a certain distance to the left or right without moving the camera or your eye.  Everything that you can see is in your field of view.  If you draw a line out from your eye on the left and right into the distance and measure the angle between them then this is the horizontal angle of the field of view. 

Try this exercise.  Open your hands flat and place them on each side of your head next to your eyes with the palms facing inward and slowly rotate them until you can't see them any more.  For most people your hands will be at almost 180 degrees apart.  For a single eye they will be a over 90 degrees apart.  If you do the same thing with a camera then the value will be a lot lower.

To get a ray traced image  to have a consistent angle we must scale the ray by some value calculated from the FOV.  The equation is scale = tan( angle / 2 ) / image_width.

Since Scratch doesn't have any trig functions we can use some of the behaviors in Scratch that do.  When you set a sprite to point in a particular direction then it uses trig functions to calculate movement.  If you move a distance of 1 from the center at an angle then the following trig values can be derived:  sin = x, cos = y, tan = x/y.  Putting this into scratch code it would look like this:

<go to x sad  0 )y sad  0 )>
<point in direction((( <{ FOV  }> </> 2 )))>
<move( 1 )steps>
<set{ FOVScale  }to(((  <x position></>  (( <y position> <*> <{ Width  }> )))))>


Here is a Scratch example that uses these concepts to create a ray traced scene: http://scratch.mit.edu/projects/Canthiar/43278

The example will draw the distance to an object as different colors.

Offline

 

#20 2007-10-12 23:54:42

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

Getting it all into Scratch Part 3

In a normal ray tracer there is a color component to both a light and material.  The color is represented as the four color components that the eye can see; red, green, and blue.  The intensity value for each should range from 0 to 1.  Then to get the reflected color of a surface you just multiply each component in the material color by each component in the light color.

Since scratch doesn't handle colors using red, green, and blue I used the pen color to represent the material color and the pen shade to represent the light brightness.  There are some tricks that you can do to alter the surface color based on the x,y, and z of intersection.


Example code: http://scratch.mit.edu/projects/Canthiar/43911

This sample code has two sprites.  One that uses a "broadcast and wait" for square roots and one that does them inlined.  The inlined version runs faster, but may be more difficult to follow.

Offline

 

#21 2007-10-15 06:00:13

Gigabyte123
Scratcher
Registered: 2007-10-15
Posts: 100

Re: How my Raytracer works

A RAY TRACER!!!??, I am thirty minutes new and I see this! This makes me feel there is a long way to go!


gather your confidence- reach for the skies... and always use deoderant!

Offline

 

#22 2007-10-15 06:02:58

Gigabyte123
Scratcher
Registered: 2007-10-15
Posts: 100

Re: How my Raytracer works

are you some scratch einstien or something... no seriosly, I gotta see this.


gather your confidence- reach for the skies... and always use deoderant!

Offline

 

#23 2007-10-15 10:31:28

Canthiar
Scratcher
Registered: 2007-05-16
Posts: 100+

Re: How my Raytracer works

I'm not really a Scratch Einstein since I didn't really know Scratch all that well when I started this project.  I've written ray tracers before and this felt like a fun challenge.  There's a bunch of stuff that I messed up on or cheated on to get working since I was trying to do everything from memory.  Once you know the math and how everything works it's really not that hard.

Offline

 

#24 2007-11-03 05:36:58

Gigabyte123
Scratcher
Registered: 2007-10-15
Posts: 100

Re: How my Raytracer works

only bieng grade 7, and matrices and that stuff is grade 10, errrr, I know a little about matrices anyway. But I am not a university graduate for maths or something crazy like that.


gather your confidence- reach for the skies... and always use deoderant!

Offline

 

#25 2007-11-03 08:39:43

kevin_karplus
Scratcher
Registered: 2007-04-27
Posts: 1000+

Re: How my Raytracer works

It is not crazy to be a university graduate in mathematics.  I got both a BS and an MS in math before switching to computer science, where I got a PhD.  I now work in bioinformatics (a field also known as computational biology), where the math and computer science both come in handy. 

I have had to keep taking courses, though, as I did not take enough science or statistics when I was a student.  In any case, biology has changed a *lot* since I was a student, as even beginning biology courses contain a lot of molecular biology that simply wasn't known when I was in high school.

Offline

 

Board footer