Reflect & refract
Bounce a direction off a surface, or bend it through one, given the surface normal and the index of refraction.
How it works
Light that hits a surface either bounces off or passes through, bending as it enters. GLSL has a built-in function
for each: Normal A unit vector perpendicular to a surface, pointing the way it faces. Lighting and Fresnel effects compare it with other directions.reflect and refract. Both take an incoming direction and the surface normalA unit vector perpendicular to a surface, pointing the way it faces. Lighting and Fresnel effects compare it with other directions.
reflect
reflect(I, N) mirrors the incident direction I about the normal N. It is defined as:
vec3 r = I - 2.0 * dot(N, I) * N; // what reflect(I, N) returns
The dot productMultiplies two vectors component by component and adds the results. For unit vectors it is the cosine of the angle between them, from 1 (same direction) to -1 (opposite). Dot product Multiplies two vectors component by component and adds the results. For unit vectors it is the cosine of the angle between them, from 1 (same direction) to -1 (opposite).dot(a, b) = |a| |b| cos θI points into the surface; subtracting twice that part
flips it, and the part along the surface stays. N must be normalized for the result to keep I’s length.
The usual mistake is the direction of I. It points toward the surface, along the travel of the light. The view
vector most shaders carry (i.view on this site’s cards) points the other way, from the surface toward the camera, so
negate it:
vec3 n = normalize(i.normal);
vec3 v = normalize(i.view); // surface → camera
vec3 r = reflect(-v, n); // the direction a mirror would show at this pixel
Feed r to an environment map or a sky function and you get a mirror. Compare the reflected light direction with the
view direction and you get a Phong highlight, bright where the light bounces straight into the camera:
vec3 l = normalize(vec3(0.4, 1.0, 0.3)); // toward the light
float spec = pow(max(dot(reflect(-l, n), v), 0.0), 32.0); // higher exponent, smaller and sharper highlight
Blinn-Phong replaces the reflected vector with the half vector normalize(l + v) compared against n, which is
cheaper and behaves better at grazing angles; the Specular highlights technique builds on both.
refract
When light crosses from one material into another, it changes speed and bends. Each material has an
index of refractionHow much a material slows light compared with a vacuum, which sets how sharply light bends entering it. About 1.0 for air, 1.33 for water and 1.5 for glass. Index of refraction (IOR) How much a material slows light compared with a vacuum, which sets how sharply light bends entering it. About 1.0 for air, 1.33 for water and 1.5 for glass.n₁ sin θ₁ = n₂ sin θ₂refract(I, N, eta) applies
it:
Iis the normalized incident direction, toward the surface, as withreflect.Nis the normalized normal on the sideIcomes from.etais the ratio of the indices, the one being left over the one being entered.
float eta = 1.0 / 1.5; // air into glass
vec3 t = refract(-v, n, eta); // the bent ray inside the glass
Getting out again reverses everything: the normal flips to face the ray, and eta becomes 1.5 / 1.0. Going from a
higher index into a lower one, like glass into air, a ray at a shallow enough angle can’t leave at all and reflects
back inside. This is total internal reflection, and refract signals it by returning a zero vector, which you should
check before using the result:
vec3 t = refract(dir, -n, 1.5); // glass into air
if (dot(t, t) == 0.0) {
t = reflect(dir, -n); // total internal reflection
}
Refraction on screen
A real-time shader rarely traces the refracted ray through the scene. The common approximation offsets the lookup into an image of what is behind the surface by the bent direction:
vec3 tv = mat3(uView) * t; // world → view, so x and y follow the screen
vec2 offset = tv.xy * 0.05; // strength in screen units, tuned by eye
vec3 behind = texture(uScene, screenUv + offset).rgb;
It is not physically exact, but the eye accepts it. Refracting each color channel with a slightly different eta
separates them like a prism, a cheap dispersion effect. How much of the light reflects and how much refracts depends on
the angle too; the Fresnel effect describes that split.
Further reading
- reflect (opens in a new tab)Reference
- refract (opens in a new tab)Reference
- Snell's law (opens in a new tab)Reference