|
Notes for September 16 class -- Ray tracing to spheres and Phong shading
RAY TRACING TO SPHERES
(1) Forming a ray for each pixel
Ray tracing is a very powerful rendering technique.
The fundamental idea is to shoot a "ray"
into the scene at every pixel of the image.
Effectively, we are tracing light backwards
from a pinhole camera into the scene.
Whatever object in the scene this ray hits first,
that is the object visible at that pixel.
We can define a ray using an origin point V
and a unit-length direction vector W.
Any point on the ray can be described by the parametric equation:
V + t W for t > 0
Note that points where t <= 0 are not part of the ray,
because those points are not in front of the ray origin V.
We can describe the image plane itself
as a square floating in space.
We can position the image plane in our virtual world so that
x==-1 along its left edge, x==+1
along its right edge, y==-1 along ite bottom edge,
and y==+1 along its top edge.
We set the z value of the image plane to zero.
As it happens, that is exactly the range of values
for vPos in the shader programs you have been implementing. :-)
Since the observer is in front of the square,
and our coordinate system follows the right hand rule,
the observer will be located on a point along the positive z axis.
Adapting the convention of photography, we use
the variable fl -- for focal length -- for the
distance of the observer from the image plane.
We say that observer is in location:
V = [0,0,fl]
To form a ray through any pixel [x,y] of the image,
we first take the difference vector from
the observer's location to that pixel:
[x,y,-fl] = [x,y,0] - [0,0,fl]
Then we normalize that vector to get the ray direction:
W = normalize([x, y, -fl])
If we were to write the same thing as shader language code it would look like this:
vec3 W = normalize(vec3(vPos.x, vPos.y, -fl));
Any point along this ray can be described by:
[Vx + t Wx, Vy + t Wy, Vz + t Wz], where t > 0
(2) Finding the distance along the ray to a sphere
We can use our ray to "ray trace" to various objects in the scene. Suppose, for example,
we want to ray trace to a sphere.
Recall that the surface of a sphere with center at C and radius r consists of all points P such that:
(Px - Cx)2 + (Py-Cy)2 + (Pz-Cz)2 = r2
Since a sphere has a center location C and
a radius r, we can define a sphere using the following four values:
Cx, Cy, Cz, r
In a shader, we can store that information in a single vec4.
For example, to describe a sphere centered at [0,0,0]
with radius 0.5:
vec4 S = vec4(0., 0., 0., .5);
To ray trace to a sphere, we need to find what point
(if any) along the ray is on the surface of the sphere.
Points on the surface of the sphere are going to be those
points that are a distance r from the center of the sphere.
The magnitude squared of any vector v can be obtained
by just taking a dot product of that vector with itself:
vx2 + vy2 + vz2
Using this fact, we can compute the distance squared from any point [x,y,z] to the sphere center by:
(x-Cx)2 + (y-Cy)2 + (z-Cz)2
So if a point [x,y,z] is on the sphere, it must be true that:
(x-Cx)2 + (y-Cy)2 + (z-Cz)2 = r2
Recall that any point along our ray can be described by:
[Vx + t Wx, Vy + t Wy, Vz + t Wz], where t >= 0
To make our math easier, let's shift coordinates,
so that the sphere is at the origin [0,0,0].
To do this, we just need to replace V by
V' = V - C
This substitution moves the sphere to the origin.
Now our ray equation becomes:
[V'x + t Wx,
V'y + t Wy,
V'z + t Wz], where t >= 0
We can substitute this point into our equation for the
sphere (which is now at the origin) to get:
(V'x + t Wx)2 +
(V'y + t Wy)2 +
(V'z + t Wz)2 = r2
Multiplying out the terms, we get:
t2 (Wx * Wx + Wy * Wy + Wz * Wz) +
t (2 * (Wx * V'x + Wy * V'y + Wz * V'z) ) +
(V'x * V'x +
V'y * V'y +
V'z * V'z - r2) = 0
Using our definition of dot product, we can rewrite this as:
(W ● W) t2 + 2 (W ● V') t + (V' ● V' - r2) = 0
But since W is unit length, (W ● W) is just 1. This further simplifies our equation to:
t2 + 2 (W ● V') t + (V' ● V' - r2) = 0
We can now solve the standard quadratic equation,
t = (-B +- sqrt(B2 - 4AC)) / 2A
where in our case:
A = 1
B = 2 (W ● V')
C = V' ● V' - r2
to get:
t = (-2(W ● V') ± sqrt(4(W ● V')2 - 4(V' ● V'-r2))) / 2
or:
t = -(W●V') ± sqrt((W●V')2 - V'●V' + r2)
(3) Finding the surface point and surface normal
The above equation can have zero, one or two real solutions,
depending on whether the discriminant (the expression inside the square root)
is negative, zero or positive, respectively.
Zero real solutions means that the ray has missed the sphere.
If there is a real solution but t is negative, that means
the sphere is behind the ray.
Otherwise,
one solution means the ray is just barely grazing the sphere, and
two solutions means the ray is going into the sphere at one point
and then exiting out at another point.
If you do find two positive roots, then you
want the smaller of the two roots, because
that is the one where the ray enters the sphere:
t = -(W●V') - sqrt((W●V')2 - V'●V' + r2)
Equation 1
If your ray hits more than one sphere, then you need to
render the one that is nearest. This will be the one with
the smallest positive value of t.
Once you find t, you can find the intersection point P on the sphere surface
by plugging t into the ray equation:
P = V + t W
Then in order to do lighting on the sphere, you can
find the normalized vector from the center of the
sphere to this surface point:
N = normalize(P - C)
To summarize the algorithm:
-
vec3 N, P;
-
vec3 V = vec3(0, 0, fl);
-
vec3 W = normalize(vec3(vPos.x, vPos.y, -fl));
-
float tMin = 1000.;
Solve the quadratic equation above
(Equation 1)
to compute t
If t > 0 and t < tMin:
-
P = V + t * W;
-
N = normalize(P - C);
-
tMin = t;
As I mentioned in class, you might find it convenient
to implement a function specifically to find the
distance along a ray to a sphere.
If the ray misses the sphere, your function
can just return -1. The function declaration
might look something like this:
float raySphere(vec3 V, vec3 W, vec4 S) {
...
}
where V and W specify the ray, and S
specifies the center and radius of the sphere.
PHONG SHADING
The first really interesting model for surface reflection was developed by Bui-Tong Phong in 1973.
Before that, computer graphics surfaces were rendered using only diffuse lambert reflection.
Phong's was the first model that accounted for specular highlights.
The Phong model begins by defining a reflection vector R, which is a reflection of the
direction to the light source L about the surface normal N.
As we showed in class, and as you can see from the diagram on the right,
it is given by:
R = 2 (N • L) N - L
|
|
|
|
Once R has been defined, then the Phong model
approximates the specular component of surface reflectance
as:
Srgb max(0, E • R)p )
where Srgb is the color of specular reflection, p is a specular power,
and E is the direction to the eye (in our case, E = -W, the
reverse of the ray direction).
The larger the specular power p, the "shinier" the surface will appear.
We can have more than one light.
To get the complete Phong reflectance, we sum over the lights in the scene:
Argb +
∑i
lightColori
(
Drgb max(0, N • Li) +
Srgb max(0, E • R) p
)
where
Argb,
Drgb and
Srgb
are the ambient, diffuse and specular color,
respectively, and p is the specular power.
|
|
|
We can add shadows by modifying the Phong shading step.
As we iterate through each light source, we first check
whether the point is in shadow from any other
sphere in the scene:
function isInShadow(P, L): // is point P in shadow from light L?
for all shapes S[i]
if raySphere(P, L, S[i]) > 0.001
return true;
return false;
If you discover that your surface point P is in shadow
from any given light source L[j], then
don't add Diffuse or Specular component for that light source.
Homework
Due Monday September 23 before the start of class
Implement a ray tracer to spheres.
Put at least two spheres into your scene
and at least two light sources,
and implement shadows.
Position the spheres so that you can
show that shadows are working.
We've made some improvements to the interactive shading editor
system, so you will find it easier to start with the newer
version, which you can download here:
hw2.zip
As we mentioned in class,
we have not yet covered how to get information about your scene
(shapes, lights, material colors, etc) from the Javascript
program running on your CPU to the GLSL shader running on your GPU.
For now you can just
set the values of your spheres and lights right inside your main() function.
For example, the following code will produce
something similar to -- but not exactly the same as -- the image to the right.
...
const int NS = 2; // Number of spheres in the scene
const int NL = 2; // Number of light sources in the scene
// Declarations of arrays for spheres, lights and phong shading:
vec3 Ldir[NL], Lcol[NL], Ambient[NS], Diffuse[NS];
vec4 Sphere[NS], Specular[NS];
...
void main() {
Ldir[0] = normalize(vec3(1.,1.,.5));
Lcol[0] = vec3(1.,1.,1.);
Ldir[1] = normalize(vec3(-1.,0.,-2.));
Lcol[1] = vec3(.1,.07,.05);
Sphere[0] = vec4(.2,0.,0.,.3);
Ambient[0] = vec3(0.,.1,.1);
Diffuse[0] = vec3(0.,.5,.5);
Specular[0] = vec4(0.,1.,1.,10.); // 4th value is specular power
Sphere[1] = vec4(-.6,.4,-.1,.1);
Ambient[1] = vec3(.1,.1,0.);
Diffuse[1] = vec3(.5,.5,0.);
Specular[1] = vec4(1.,1.,1.,20.); // 4th value is specular power
...
}
| |
|
| |
|