Now reflection is fairly simple. The angle between the reflected ray and the normal is the same as the angle between the incoming ray and the normal.
Calculating the direction of the ray after refraction is a bit more involved. We start with Snell's Law, stating that ratio of the sines of the angles of incidence and refraction is equivalent to the opposite ratio of the indices of refraction, to get the direction of the refracted ray. The outgoing angle is obviously not always defined with this formula, which bring us to the next phenomena. When the light is traveling from a medium with higher optical density to a medium with a lower one and the incident angle is larger than the critical angle total internal reflection occurs and no light leaves the medium. In fact some light is almost always reflected and the amount is governed by the Fresnel equations. In my implementation I just approximate the amount with the dot product (with complete disregard for all the complexities, not to mention frequency and polarization), but the visual result is good.
public static Vec3 reflect(Vec3 normal, Vec3 incident) {
float cosTheta = normal.dot(incident);
return incident.minus( normal.times(2 * cosTheta) );
}
With some lengthy transformations we can get a formula for the direction of the refracted ray that contains the same condition, defining total internal reflection, as Snell's law. Thus, when the flowing method can't calculate the direction total internal reflection has occurred.
I hope that was more insightful. Feel free to drop a comment.
public static Vec3 refract(Vec3 normal, Vec3 incident, float n1, float n2) {
float dn = incident.dot(normal);
float c = 1 - ((n1*n1)*(1-dn*dn) ) / (n2*n2);
if(c < 0)
return null;
Vec3 t = incident.minus( normal.times( dn ) ).times( n1/n2 )
.minus(
normal.times( (float)Math.sqrt(c)) );
return t;
}