Rendering Shadows

The final bit to actually render the shadows requires a small change to your shade_hit function from Building a World. You need to check whether the point is in shadow or not, and then pass that state to your lighting function.

Add the following test to those that you wrote for the shade_hit function. To demonstrate the case where some object is shadowing the point of intersection, it creates a world and two spheres, and positions a light so that the second sphere is in the shadow of the first. Then, a ray and an intersection are created such that the point of intersection is in the shadow. The shade_hit function should return only the ambient color of the second sphere in this case.

 Scenario​: shade_hit() is given an intersection in shadow
 Given​ w ← world()
 And​ w.light ← point_light(point(0, 0, -10), color(1, 1, 1))
 And​ s1 ← sphere()
 And​ s1 is added to w
 And​ s2 ← sphere() with:
  | transform | translation(0, 0, 10) |
 And​ s2 is added to w
 And​ r ← ray(point(0, 0, 5), vector(0, 0, 1))
 And​ i ← intersection(4, s2)
 When​ comps ← prepare_computations(i, r)
 And​ c ← shade_hit(w, comps)
 Then​ c = color(0.1, 0.1, 0.1)

Now, making this test pass may seem to be merely a matter of taking the point of intersection and sending it directly to the is_shadowed function. But if you do this, you’re liable to wind up with a rendered picture that looks like it’s been attacked by fleas, as in the following figure.

images/shadows/speckled-shadows.png

This effect is called acne, and it happens because computers cannot represent floating point numbers very precisely. In general they do okay, but because of rounding errors, it will be impossible to say exactly where a ray intersects a surface. The answer you get will be close—generally within a tiny margin of error—but that wiggle is sometimes just enough to cause the calculated point of intersection to lie beneath the actual surface of the sphere.

As a result, the shadow ray intersects the sphere itself, causing the sphere to cast a shadow on its own point of intersection. This is obviously not ideal.

The solution is to adjust the point just slightly in the direction of the normal, before you test for shadows. This will bump it above the surface and prevent self-shadowing.

Add the following test, which sets up a sphere and an intersection such that the intersection occurs at z=0. After calling the prepare_computations function you wrote in Chapter 7, Making a Scene, there should be a new attribute, over_point, which will be almost identical to point, with the z component slightly less than z=0.

 Scenario​: The hit should offset the point
 Given​ r ← ray(point(0, 0, -5), vector(0, 0, 1))
 And​ shape ← sphere() with:
  | transform | translation(0, 0, 1) |
 And​ i ← intersection(5, shape)
 When​ comps ← prepare_computations(i, r)
 Then​ comps.over_point.z < -EPSILON/2
 And​ comps.point.z > comps.over_point.z

Note that the test compares the over_point’s z component to half of -EPSILON to make sure the point has been adjusted in the correct direction.

In pseudocode, your prepare_computations function will need to do something like this:

 # after computing and (if appropriate) negating
 # the normal vector...
 comps.over_point ← comps.point + comps.normalv * EPSILON

EPSILON is the tiny number discussed in Comparing Floating Point Numbers, and is used here to bump the point just a bit in the direction of the normal.

Next, modify your shade_hit function so that it invokes is_shadowed with the hit’s newly offset over_point attribute, and then call the lighting function (again with over_point) with the result. It’ll look like this in pseudocode:

 function​ shade_hit(world, comps)
  shadowed ← is_shadowed(world, comps.over_point)
 
 return​ lighting(comps.object.material,
  world.light,
  comps.over_point, comps.eyev, comps.normalv,
  shadowed)
 end​ ​function

Go ahead and make that change to your shade_hit function and make sure your tests are all passing. Once they are, it’ll be time to wrap this chapter up and render some shadows!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.200.197