Refraction in OpenGL

by Thomas Kerwin

I implemented a simple refraction shader with GLSL. The final program has three main features: Fresnel calculations, chromatic dispersion, and surface bumpmapping.

Refraction with an index of refraction around that of water.

Fresnel calculations

To determine the amount of light that is reflected or refracted when a ray hits the boundary of the sphere, I used the Fresnel equations. When the angle of incidence is small, most of the light passes through the material and when it is large, most of the light is reflected. The exact amounts depend on the refraction indicies of the two materials and that relationship is given by the Fresnel equations. I used Wikipedia as a reference for the exact equations. My translation to GLSL is as follows: void myRefract(in vec3 incom, in vec3 normal, in float index_external, in float index_internal, out vec3 reflection, out vec3 refraction, out float reflectance, out float transmittance) { float eta = index_external/index_internal; float cos_theta1 = dot(incom, normal); float cos_theta2 = sqrt(1.0 - ((eta * eta) * ( 1.0 - (cos_theta1 * cos_theta1)))); reflection = incom - 2.0 * cos_theta1 * normal; refraction = (eta * incom) + (cos_theta2 - eta * cos_theta1) * normal; float fresnel_rs = (index_external * cos_theta1 - index_internal * cos_theta2 ) / (index_external * cos_theta1 + index_internal * cos_theta2); float fresnel_rp = (index_internal * cos_theta1 - index_external * cos_theta2 ) / (index_internal * cos_theta1 + index_external * cos_theta2); reflectance = (fresnel_rs * fresnel_rs + fresnel_rp * fresnel_rp) / 2.0; transmittance =((1.0-fresnel_rs) * (1.0-fresnel_rs) + (1.0-fresnel_rp) * (1.0-fresnel_rp)) / 2.0; }

Theta 1 is the angle between the normal and the incident ray, while theta 2 is the angle between the refracted ray and the negative of the normal. This code can probably be optimized at the cost of adding more temporary variables. I then look up the color produced by the reflected ray and the refracted ray in an environment cube map. I only calculate the effects of the light intersection on the front side of the sphere, not the back.

Chromatic Dispersion

Chromatic dispersion effect.

When white light is refracted, the different colors of light that compose the white light are bent to different degrees depending on the wavelength. I simulate that effect here by constructing three different refracted rays, one each for red, blue, and green. After looking up each ray in the environment map, I recompose the values into the final color, putting the red channel of the red ray in the final color and so on. This gives a nice rainbow effect, although I do have the effect exaggerated here to make it easier to see.

Surface Bumpmapping

To make my refractive object look more like glass, I implemented a bumpmaping technique. Glass objects often have scratches or other perturbations of the surface that can be simulated well using bumpmapping. I created a noise texture in the image editing program The GIMP and processed it with ATI's normal map generator. That program turns a texture image into a DOT3 bumpmap. I then added the normal from the bumpmap to the given normal and renormalized: newNormal = 0.5 * (oldNormal + 2.0 * ( texture2D(bumpmap, gl_TexCoord[0]) - 0.5 ) )

Notes

Object with a refraction index of .8

GLSL does have a built in refract function, but this doesn't seem to be well supported on ATI drivers at this time. Also, ATI's GLSL compiler seems to be much more strict than NVidia's, at least in terms of automatic type conversions. For example, ATI's drivers give an error when attempting to multiply an int by a float but NVidia's drivers make the conversion automatically.

You can download my (simple) vertex shader and the regular refraction and chromatic refraction fragment shaders. You can also download my windows program if you want to try it out yourself. I used the arch cubemap from Codemonster's texture repository. I created the bumpmap myself, as mentioned above.

References

Here are some of the documents I used to implement the shader. The Orange Book (OpenGL Shading Language) was extremely helpful, and I recommend it to anyone learning GLSL. I also relied on Wikipedia for all the optics equations I used. Other sources that I looked at included: