CS 381 Fall 2012  >  Lecture Notes for Tuesday, November 13, 2012

CS 381 Fall 2012
Lecture Notes for Tuesday, November 13, 2012

Refraction Mapping

Indices of Refraction & Snell’s Law

When a wave—such as light—moves from one medium to another, its speed can change. This results in a change in the direction the wave is traveling, a phenomenon called refraction.

The index of refraction of a material is the ratio of the speed of light in a vacuum to the speed of light in that material. It is generally higher for more dense materials. For glass, the index is typically in the range \(1.4\) to \(1.9\). For air, it is about \(1.0003\).

Snell’s Law describes how light changes direction when it moves from one material to another with a different index of refraction. This law considers the angle between the light direction and the surface normal vector. The law says that ths angle changes in such a way that its sine is inversely proportional to the index of refraction of the material. Thus, when light moves to a material with a higher index of refraction (from air to glass, say), then the sine decreases, and so the angle decreases. This means that the direction of the wave becomes more nearly parallel to the normal. When light moves to a material with a lower index of refraction (from glass to air, say), its direction moves away from that of the normal.

In the situation where the sine of the angle is increasing (e.g., moving from glass to air), it is possible that Snell’s Law will give a sine greater than \(1\). In this case, the light does not refract; it reflects off the boundary between the two objects. This is called total internal reflection; it is the reason that, for example, the surface of water can look like a mirror when see from below.

To compute the effect of refraction on a ray passing from one medium to another, we need the ray’s direction, the surface normal, and the ratio of the indices of refraction of the two media.

Applying Snell’s Law directly, we could use the dot product to compute the cosine of the angle between the normal and the viewing direction, use the fact that \(\cos^2\,\theta + sin^2\,\theta = 1\) to find the sine of this angle, and then multiply by the ratio of indices to find the sine of the angle between the normal and the new direction vector. Something like the reverse of the above process would give us the new direction vector.

The following formulas allow a more efficient version of this computation. Let \(\mathbf{v}\) be the unit direction vector of light moving from one medium to another, and let \(\mathbf{n}\) be the unit normal vector of the boundary between the media. We assume that \(\mathbf{v}\cdot\mathbf{n} \le 0\); this will generally be true when our light is entering an object. Let \(\eta\) be the index of the medium the light is leaving divided by the index of the medium the light is entering. We first compute a number \(k\):

\[ k = 1-\eta^2\, \Bigl[ 1-(\mathbf{v}\cdot\mathbf{n})^2 \Bigr]. \]

If this \(k\) is negative, then we have total internal reflection; no refraction occurs. If \(k\ge 0\), then \(k = \cos^2\,\theta\), where \(\theta\) is the angle between the new direction and the normal. We continue, computing the new direction vector \(\mathbf{r}\):

\[ \mathbf{r} = \eta\mathbf{v} - \Bigl[\eta \, (\mathbf{v}\cdot\mathbf{n}) + \sqrt{k}\Bigr] \, \mathbf{n}. \]

The speed of light in a medium can vary somewhat according to the frequency of the light. Light of higher frequencies (and thus lower wavelengths) travels slower in a dense medium. That is, the index of refraction is higher for such light, and so its direction changes more when it moves from one medium to another. This means that refraction deflects green light more than red light; blue light is deflected still more. Thus, lenses can focus different colors of light in different places, a phenomenon known as chromatic aberration.

Refraction & Rendering

In reflection mapping, we do a texture look-up based on a reflected viewing direction vector. We can similarly do a texture look-up based on a refracted viewing direction vector. This is refraction mapping; it is another good use of an environment map. The two techniques are handled very similarly; we refract a ray instead of reflecting.

The GLSL built-in function refract performs the computation described above. It takes three parameters: \(\mathbf{v}\), \(\mathbf{n}\), and \(\eta\), above, and returns \(\mathbf{r}\). The first two parameters are of type vec3 and should be normalized, with \(\mathbf{v}\cdot\mathbf{n} \le 0\); again this is generally true when our ray is entering an object. The value \(\eta\) is a float. It is generally less than \(1\) when our ray enters an object, and greater than \(1\) when it exits. The return value is a vec3. It is vec3(0.,0.,0.) if total internal reflection occurs; otherwise, it is the new direction vector, and will have length (approximately) \(1\).

To simulate the direction change due to refraction when light encounters the near boundary of a transparent object, we can use the following GLSL code.

[GLSL]

float index = 1.1;                 // Index of refraction for object
vec3 viewdir = normalize(myvert);  // Viewing direction
vec3 refractview = refract(viewdir, mynorm, 1./index);
    // Refracted viewing direction
vec4 texcolor = textureCube(mycube0, refractview);

Note that, unlike function reflect, the first parameter to refract must be normalized. Thus, we do normalize(myvert) to obtain the viewing direction. Note also that our ray is entering an object of higher density, not leaving it. Thus total internal reflection will not occur; we do not need to check for it.

To simulate chromatic aberration, we can use slightly different indices of refraction to compute the red, green, and blue components of our color.

[GLSL]

float index_r = 1.097;  // Index of refraction for red light
float index_g = 1.1;    // For green light
float index_b = 1.103;  // For blue light

vec3 viewdir = normalize(myvert);   // Viewing direction
vec4 texcolor = vec4(0.,0.,0.,1.);  // Will hold texture color

// Red refraction computation
vec3 refractview_r = refract(viewdir, mynorm, 1./index_r);
texcolor.r = textureCube(mycube0, refractview_r).r;

// Green refraction computation
vec3 refractview_g = refract(viewdir, mynorm, 1./index_g);
texcolor.g = textureCube(mycube0, refractview_g).g;

// Blue refraction computation
vec3 refractview_b = refract(viewdir, mynorm, 1./index_b);
texcolor.b = textureCube(mycube0, refractview_b).b;

None of the above code deals with the fact that our light not only encounters the near boundary of the object, but also the far boundary as well. However, our shader has no information about the shape of the object as a whole, it is difficult to perform the latter computation, and so we leave it out. Despite all our efforts, our computations are actually not physically accurate (alas!).

Simulating Glass

Glass refracts, but it also reflects a small amount of light in a mirror-like manner. Thus we can simulate glass as follows. We compute a color using reflection mapping, as before. Then we compute another color using refraction mapping, using different indices of refraction for the red, green, and blue components. Lastly, we mix (linearly interpolate) these two colors, heavily weighted toward the refraction color.

As discussed above, our refraction-mapping technique only considers what happens to light at the near boundary of the object, resulting in an inaccurate—but hopefully believable—appearance. Because of this missing computation, it helps to reduce the index of refraction from something like \(1.6\) (which is reasonable for real glass) to something more like \(1.1\), as in the above code.

See glass_cube_shaders.zip for examples of shaders that simulate a glass object. These shaders are intended for use with envir.cpp (or usecubemaps.cpp or rendercube.cpp).


CS 381 Fall 2012: Lecture Notes for Tuesday, November 13, 2012 / Updated: 13 Nov 2012 / Glenn G. Chappell / ggchappell@alaska.edu