Does total internal reflection work correctly in PP2014 ?
Back in 2010 bagginsbill posted an image in the Renderosity Underwater Submarine thread with total internal reflection. So total internal reflection seemed to be working way back then.
While investigating a weird refraction problem (which turned out to be the wrong filtering setting on an image!) I did a test that seemed to indicate that PP2014 does NOT do total internal reflection correctly. If going from a higher to lower IOR at a surface and the incident angle is greater than the critical angle (i.e. the criteria for total internal reflection) my test seemed to indicate that nothing happens - the ray simply continues through the surface on its original path.
Here's the basic test (originally posted in the above mentioned thread here) - the backdrop plane has simple vertical colour bands:
I also did the test with a 'normals outward' version of the half cylinder once I realized that an IOR less than 1.0 could be used. Same result.
So the questions are:
- Does PP2014 (tested with 10.0.5.28925) do total internal reflection correctly?
- If it does what stupid mistake am I making in my test?
- If it doesn't do it correctly is there any sneaky workaround ? (I know you won't fix PP2014! :)
- Since most folks here are using Poser 11, if there is a problem does it still exist in P11 Firefly ?
Even simpler test using just Poser primitives and the dolly camera. Try it. Try it in Poser 11 Firefly too.
Lower plane: Round Groundplane, Scale 2.5%, yTran -0.5PNU, red ambient material
Middle plane: Square Groundplane, yTran 0.0, 100% refractive material IOR 0.67 with bgd color green
Upper plane: Square Groundplane, yTran +0.5PNU, xRot 180, blue ambient material
Dolly camera at (0.0, 0.25, 0.0) PNU pointing directly down, Focal length 10mm
The IOR of 0.67 should create refraction as if the medium above the plane has IOR 1.5 and the medium below has IOR 1.0 (based on my understanding of bagginsbill's water surface prop at the end of the Underwater Submarine thread). The critical angle for going from IOR 1.5 to IOR 1.0 is about 48 degrees.
By my reckoning the central red circle an the adjoining green ring in the render are right - that's the refraction below the critical angle working correctly. I think the outer edge of the green ring is the critical angle. Everything outside this should be total internal reflection and we should see a reflection of the blue groundplane above. But we don't - it looks to me as if the render outside the outer edge of the green ring is as if the refracting plane wasn't there.
Of course, my test and/or analysis may be wrong - I have been known to make stupid mistakes! ;o)
Edit: forgot the image!
This is how the simple test (red circle with green ring) seems to me. Just quick sketches so the actual angles are way off. But you see what I mean?
Underwater submarine does NOT rely on Poser to do the TIR. I wrote a shader with my own, correct implementation of Fresnel, which could correctly calculate the switch to pure reflection.
As well, I placed the water surface upside down and used inverse IoR for the refraction, because most versions of Poser don't correctly handle back-to-front refraction AT ALL. I was, as usual, making my shader more complex, so that it could work in all Posers back to Poser 6.
However, today you could use fewer nodes, maybe. I'm not sure that I ever saw the consistent refractions/reflections that I expect.
It's generally not a thing for me to wait for Poser to do correct math. I do it myself, and then really I don't bother struggling with the sometimes nonsense that Poser nodes produce. It may be that Fresnel_Blend works right, or it may not. I'm not testing it any more in PP2014.
Anyway, here's the matmatic script for my under water surface.
def TrueFresnel(ior): n1 = 1.0 n2 = ior nr = n1 / n2 nrsq = nr * nr cosAi = EdgeBlend(1, 0, 1) cosAt = sqrt(Max(1 - nrsq * (1 - cosAi**2), 0)) s1 = n1 * cosAi s2 = n2 * cosAt p1 = n1 * cosAt p2 = n2 * cosAi Rs = ((s1 - s2) / (s1 + s2)) ** 2 Rp = ((p1 - p2) / (p1 + p2)) ** 2 R = .5 * (Rs + Rp) return R gamma = PM(2.2, "Gamma") f = TrueFresnel(.75) def UnderWater(gc=0): if gc: def GC(x): return x ** (1/gamma) def AGC(x): return x ** gamma else: def GC(x): return x def AGC(x): return x refract = AGC(Refract(0, .75)) reflect = AGC(Reflect()) output = Blend(refract, reflect, f) output = GC(output) bump = FractalSum(2, 2, 2, 3, 0, .5, .93) s = EmptySurface() s.Alternate_Diffuse = output s.Bump = bump return s outputs += [ "", UnderWater(0), "GC", UnderWater(1), ]
NOTE!!! This is not an appropriate shader for glassware or whatever. It's strictly for the special case of a large surface plane, with the camera UNDER the plane.
@bagginsbill: Oh dear, yet again I was making a really stupid mistake - I was assuming that a Refract node alone plugged into Refraction_Colour on the PoserSurface would also do the total internal reflection! :facepalm:
Of course, once I added a Reflect node, combined the Refract and Refract with a Fresnel_Blend, and plugged the result into Alt_Diffuse, I got exactly what I expected.
I knew that - I just temporariliy forgot... ;o)
Edit: of course this is just a very simple test case in PP2014, and your comments about earlier Poser's and back-to-front refraction are noted, as is the observation about the specific nature of your underwater water-surface shader. Many thanks as usual.
Ah, I was (very) vaguely recalling something I read in the Poser 6 manual about the 'Fresnel' node, which plugged into the Refraction_Colour input of the PoserSurface and was supposed to do refraction and reflection according to Fresnel's law... and now that I remember that bit, I also remember reading that the Fresnel node didn't get it right. And as usual I got things totally mixed up. :)
@bagginsbill: I've just been trying Poser 6, 8, and 9 too, and now I see why you did your underwater water-surface shader from scratch !
Poser 6 and 8 only have the Fresnel node which doesn't do it.
The Fresnel_Blend node doesn't appear till Poser 9, and even then total internal reflection only seems to work properly if you have front-facing polys and use an IOR < 1.0 , which is what you said. :)
I've just repeated the test from the second post in the current thread in PP2014 with three different IORs (1.0, 1.5, and 0.67), and with the refracting surface facing upwards (front-facing) and downwards (back-facing) for each IOR. The material for the refracting surface is the same one I specified a couple of posts back with a Fresnel_Blend of Refract and Reflect plugged into Alt_Diffuse
So in each case we're looking through a refracting surface at a red circle. Refracted rays going past the circle will show green, and reflected rays will show blue.
With the camera on the air side I'd expect to see the red circle bigger and everything else green.
With the camera on the glass side I'd expect to see the red circle smaller, surounded by a green ring, and blue beyond that.
Here's my results - back-facing surfaces still don't seem to give the correct results using the simple Fresnel_Blend /Refract/Reflect shader. Just confirming what we already knew.
How about this for a rather Heath Robinson approach for getting around the incorrect refraction on back-facing surfaces? It's just an stupid idea...
Create the mesh of your glass object in your modeller (let's call that cylinder A). Create a copy and flip the normals (cylinder B). Here's a picture to get the idea - note that they should overlay each other exactly (i.e. identical vertices), and they're only separated here so you can see both.
Import both into Poser and apply the same refractive material to both, except that the IOR of Cylinder B is the reciprocal of the IOR of cylinder A.
Now for the trick that may or may not work - set things up so that back-facings surfaces aren't rendered (not sure exactly how to do this - render settings, properties tab, material room ?). By my reckoning only the faces of cylinder A where you're looking from air to glass will be rendered, and only the faces of cylinder B where you're looking from glass to air will be rendered. What I'd expect you'd get is about half the faces of cylinder A rendered, and the missing faces will be rendered from cylinder B - so you should get every face rendered, and Poser should get the refraction correct.
Of course, it probably won't work - what d'you think? ;o)