|Sketch created with limnu iphone app from limnu.com|
First what about reflection? It is usually the classic formula:
S = D - 2*N*dot(D, N)
Does that formula still work if N points along the dotted line? Happily, yes-- try substituting -N in there and you get a "-" on the N and the dot product and they cancel. Also, D need not be a unit vector. I rarely require that in my ray tracers so I can use instancing if I want. N however does need to be a unit vector.
Now what about refraction? It obeys Snell's law:
n sin(theta) = n' sin(theta')
We can use the same trick as in reflection where we use N and D as a 2D basis. First let's make life easy on ourselves and assume D is a unit vector and N points "up" like in the picture, and make an orthogonal basis (let's change the sin() and cos() with c, c', s, s')
D = -cN + sQ
Here Q is to the right and perpendicular to N. We don't know what is is yet, but it we know what it is by rearranging the terms above to be get
Q = (D + cN) / s
We also know that
T = -c'N + s'*Q
We know c = -dot(D,N), and s = sqrt(1 - c^2)
Further we know that s' = (n/n') s
Right there is a place we can get into trouble-- n can be high (over 2 for diamond) so if the more dense medium is where D is (the top medium), we could have s > 1. In that case we get all reflection and no refraction (total internal reflection). Here's a nice image of that:
|The water acts as a perfect mirror due to total internal reflection (from wikipedia)|
T = -c'N + s'Q
T = -c'N + s'(D + cN) / s
We know s'/s = n'/n.
We also know c' = sqrt(1-s'^2) = sqrt(1 - (n/n')^2 (1-c^2) ).
T = (n'/n) D +(c(n'/n) - sqrt(1 - (n/n')^2 (1-c^2) )) N
putting back in c = -dot(D, N) we get
T = (n'/n) D +(-dot(D, N)(n'/n) - sqrt(1 - (n/n')^2 (1-dot(D, N)^2) )) N
Ok! That agrees with the ray tracing news formula. Now what happens when N points down? Only the last term is different:
T = (n'/n) D +(-dot(D, N)(n'/n) + sqrt(1 - (n/n')^2 (1-dot(D, N)^2) )) N
Well isn't that annoying? That sign difference is just sgn(dot(D, N)). I never know how to best code that sort of thing, but when in doubt, be very readable!
if (dot(D,N) > 0) temp_normal = -N // now proceed with original formula