Jump to content


Glass


  • Please log in to reply
84 replies to this topic

#1 Mario Marengo

Mario Marengo

    Grand Master

  • Members
  • PipPipPipPipPip
  • 1,249 posts
  • Joined: 26-June 02
  • Location:Toronto, Canada
  • Name:Mario Marengo

Posted 10 June 2006 - 12:07 AM

Stu already posted the ultimate glass shader, but I thought it might be fun to try it the old fashion way, just for giggles :P

I'll spread this over several posts so I can add bits and pieces as I find the time to work on them. I have a bunch of code lying around that deals with glass, but since I'm sharing, I thought I'd take the opportunity to rethink the whole thing from scratch, and post as I build it. That way we can also discuss each step separately and critique the choices made along the way.


Requirements:

A complete glass shader should support the following optical properties: reflection, refraction, transmission, absorption, and dispersion (did I leave anything "visible" out?). All these effects are wavelength-dependent, so there's a big choice™ to be made along the way regarding the potential need for a spectral color model. This hypothetical "complete" model would be relatively expensive to compute (in *any* renderer) and clearly "overkill" in many situations (e.g: glass windows), so we'll need the ability to turn features on/off as needed (or end up with several specialized shaders if we can't keep the full version both flexible *and* efficient).

Space:

It is customary to do all reflectance calculations in tangent space, where the x and y axes are two orthonormal vectors lying on the plane tangent to the shade point "P", and z is the unit normal to that plane. This simplifies some of the math and can therefore lead to more efficient code. However, since both VEX and RSL provide globals and runtime functions in some space other than tangent ("camera" space for those two), working in tangent space will inevitably mean some transformations. Whether working in tangent space is still advantageous after that, remains to be seen. As a result, we'll need to look at the costs involved in writing our functions in either space, and base our final choice on what we find.

Naming Conventions:

I'll adopt the following naming conventions for the main variables:
vector n - unit normal to the surface
vector wi - unit incidence vector, points *toward* the source
vector wo - unit exitance vector, points *toward* the observer
vector wt - unit transmission direction
[float|spectrum] eta_i - index of refraction for the incident medium
[float|spectrum] eta_t - index of refraction for the transmissive medium (glass)
[float|spectrum] kr - fraction of incident light/wavelength that gets reflected
[float|spectrum] kt - fraction of incident light/wavelength that gets transmitted

All angles, unless otherwise stated, are in *radians*!
All vector parameters, unless otherwise stated, are expected to be normalized!

Fresnel:

This is the workhorse for glass, it alone is responsible for 90% of the visual cues that say "glass". The Fresnel functions determine the fraction of light that reflects off a surface (and also the fraction that gets transmitted, after refraction, *into* the surface). Glass is a "dielectric" material (does not conduct electricity), so we'll use that form of the function. We'll also ignore light polarization (we're doing glass, not gems... a full model for gem stones would need to take polarization into account).
But wait! both RSL and VEX already *have* this kind of fresnel function, so why re-invent the wheel?!?
Implementations are all slightly different among shading languages. Having our own will hopefully provide a homogeneous (actually, we're shooting for "identical") look and API across renderers -- if we find the renderer's native fresnel is identical to ours we could always choose to switch to the native version (which is usually faster).

The following is, to the best of my knowledge, an accurate Fresnel implementation in VEX for dielectrics (unpolarized). In case we find it useful at some point, I give it for both "current" space (camera space for VEX and RSL), and tangent space. Here's the fragment for world space:
// Full Fresnel for dielectrics (unpolarized)
//-------------------------------------------------------------------------------
// world space
void wsFresnelDiel(vector wo,n; float eta_i,eta_t; 
				   export vector wr,wt; export float kr,kt;
				   export int entering) 
{

   if(eta_i==eta_t) {
	  kr = 0.0;
	  wr = 0.0;
	  kt = 1.0;
	  wt = -wo;
	  entering = -1;

   } else {

	  float ei,et; 

	  // determine which eta is incident and which transmitted
	  float cosi = wsCosTheta(wo,n);
	  if(cosi>0.0) {entering=1; ei=eta_i; et=eta_t; } 
		 else {entering=0; ei=eta_t; et=eta_i; }
   
	  // compute sine of the transmitted angle
	  float sini2 = sin2FromCos(cosi);
	  float eta   = ei / et;
	  float sint2 = eta * eta * sini2;
   
	  // handle total internal reflection
	  if(sint2 > 1.0) {
		 kr = 1.0;
		 wr = 2.0*cosi*n - wo;
		 kt = 0.0;
		 wt = -wo; // TODO: this should be zero, but...
	  } else {
		 float cost  = cosFromSin2(sint2);
		 float acosi = abs(cosi);
   
		 // reflection
		 float etci=et*acosi, etct=et*cost, 
			   eici=ei*acosi, eict=ei*cost;
		 vector para = (etci - eict) / (etci + eict);
		 vector perp = (eici - etct) / (eici + etct);
		 wr = 2.0*cosi*n - wo;
		 kr = (para*para + perp*perp) / 2.0;
   
		 // transmission
		 if(entering!=0) cost = -cost;
		 kt = ((ei*ei)/(et*et)) * (1.0 - kr);
		 wt = (eta*cosi + cost)*n - eta*wo;
	  }
   }
}

The support functions like cosFromSin() and so on, are there just for convenience and to help with readability. These are included in the header.
After some testing, it looks like VEX's current version of fresnel() (when used as it is in the v_glass shader) and the custom one given above, are identical. Here's an initial test (no illumination, no shadows... this is just a test of the function).

frtest_10_20_MP.jpg

Yes, you'd expect the ground to be inverted in a solid glass sphere. The one on the right is a thin shell. I ran a lot of tests beyond that image, and I'm fairly confident it's working correctly.
The first thing that jumps out from that image though, is the crappy antialiasing of the ground's procedural texture function for the secondary rays. In micro-polygon mode, you'd need to raise the shading quality to 4 or more to start getting a decent result... at a huge speed hit. It looks like there's no area estimation for secondary rays in micropolygon mode. In ray-tracing mode however, things are good -- whether it uses ray differentials or some other method, the shader gets called with a valid estimate and can AA itself properly. The micropolygon problem needs to get looked into though.

frtest_10_20_TR.jpg

You would expect the built-in version to run faster than the custom one, and it does (~20% faster)... as long as you keep the reflection bounces low (1 or 2). As the number of bounces increases, our custom version starts out-performing the built-in one. Yes, this is weird and I very much suspect a bug. By the time you get to around 10 bounces, the custom code runs around 7 times faster (!!!) -- something's busted in there.

OK. That's it for now. It's getting late here, so I'll post a test hipfile and the code sometime soon (Monday-ish).

Next up: Absorption.

(glass cubes with a sphere of "air" inside and increasing absorption -- no illumination, no shadows, no caustics, etc)

abstest_20_20_TR.jpg
Mario.

#2 sibarrick

sibarrick

    Grand Master

  • Members
  • PipPipPipPipPip
  • 1,981 posts
  • Joined: 29-September 03

Posted 10 June 2006 - 12:34 AM

Great stuff as always! cheers Mario  :D
"The trick is finding just the right hammer for every screw"

#3 stu

stu

    Illusionist

  • Members
  • PipPipPip
  • 469 posts
  • Joined: 16-October 02
  • Location:Toronto

Posted 10 June 2006 - 05:08 AM

Nice, much better than mine.  :)

#4 TheUsualAlex

TheUsualAlex

    Houdini Monkey

  • Members
  • PipPipPipPipPip
  • 1,097 posts
  • Joined: 25-June 02

Posted 10 June 2006 - 08:19 AM

Dang! Master Mario! That's awesome! Is this thread sticky?

#5 Jason

Jason

    King Tapir

  • Administrators
  • 3,708 posts
  • Joined: 08-November 00
  • Name:Jason Iversen

Posted 10 June 2006 - 10:01 AM

TheUsualAlex, on Jun 10 2006, 08:19 AM, said:

Dang! Master Mario! That's awesome! Is this thread sticky?

View Post

Now it is:)

As usual. only the hightest quality information from you, Mario!  Looking forward to reading more!
jason iversen
++odforce guy, and supervisor @ r+h, jiversen-at-rhythm
odforce g+ page: https://plus.google.com/103473736257525043693

#6 Mario Marengo

Mario Marengo

    Grand Master

  • Members
  • PipPipPipPipPip
  • 1,249 posts
  • Joined: 26-June 02
  • Location:Toronto, Canada
  • Name:Mario Marengo

Posted 10 June 2006 - 09:19 PM

Hey, thanks all :D I'm having fun dissecting this one (or re-dissecting), and I fully expect all of you to slap me upside the head if I start doing something stupid!

Jim (Wolfwood) has already contributed a lot by looking into some of the Mantra-side issues (some false positives, and some possibly real) and bouncing around approaches. So this is already very much *not* a one man show.

A fun trip ahead I think... though it'll take some time to get to the end I'm sure.

P.S: I re-read what I wrote and realized that one shouldn't attempt to copy-paste at 2AM... that code fragment wouldn't have compiled... way to go: screw up the bit that's preceded by "The following is, to the best of my knowledge, an accurate Fresnel implementation in VEX..." :lol: (I went back and fixed it... I think).
Mario.

#7 symek

symek

    Grand Master

  • Members
  • PipPipPipPipPip
  • 1,535 posts
  • Joined: 02-November 04
  • Location:Waw/Pol
  • Name:Szymon Kapeniak

Posted 11 June 2006 - 04:05 AM

Mario!
Write a book on this!
Be reach!
Live forever!
Awesome! (I hate this word, but its so accurate :) )

cheers,
SY.
(...) It was late, late in the evening, the lovers they were gone;
The clocks had ceased their chiming, and the deep river ran on.

#8 Rafal123

Rafal123

    Houdini Master

  • Members
  • PipPipPipPip
  • 505 posts
  • Joined: 04-October 04

Posted 11 June 2006 - 05:15 AM

hey Mario great work but could You tell what's the diffrence between glass and ice exept refraction index?

sometimes cubes of ice have frozen air bubbles inside, can that also be achived by shader?

#9 Jason

Jason

    King Tapir

  • Administrators
  • 3,708 posts
  • Joined: 08-November 00
  • Name:Jason Iversen

Posted 11 June 2006 - 08:55 AM

Mario Marengo, on Jun 10 2006, 12:07 AM, said:

The first thing that jumps out from that image though, is the crappy antialiasing of the ground's procedural texture function for the secondary rays. In micro-polygon mode, you'd need to raise the shading quality to 4 or more to start getting a decent result... at a huge speed hit. It looks like there's no area estimation for secondary rays in micropolygon mode. In ray-tracing mode however, things are good -- whether it uses ray differentials or some other method, the shader gets called with a valid estimate and can AA itself properly. The micropolygon problem needs to get looked into though.

Hi Mario,

For those at home who are not so familiar with Mantra and want to learn some of the gory details, turning on raytracing (with the -r switch; found in the Command field's [+] button) affects only the primary rays. When enabled, it makes Mantra fire out the primary rays from camera (through each pixel) into the scene and shades any surface(s) they strike. When left alone in micropolygon mode, Mantra will "dice" up all the surfaces in the view to small micropolygons and shades all of these. However, if an object's shader wants to, it can shoot rays into the scene itself.

So, knowing that the secondary rays are always raytraced in either micropolygon mode or raytracing, this must be related to the sampling of the primary rays only.

So Mario et al, do you think this problem is due to the distribution of the micropolygons? i.e in the dicing method? Have you experimented with different surfaces topologies to test that out? What are you using currently? A Primitive sphere? I've long been confused about the algorithm Mantra uses to dice geometry. Trying to battle shader aliasing for rendering ocean surfaces makes me wish we knew the exact method so we could fight with our eyes open.

Or, less likely, do you think that somehow the calculation of the attributes that you're using eg. the surface Normal, differ in micropolygon mode?

Finally; have you tried to render this with 8.1? Are there any improvements you've noticed over 8.0 in this type of scene?

Thanks for all the wonderful info!
Jason
jason iversen
++odforce guy, and supervisor @ r+h, jiversen-at-rhythm
odforce g+ page: https://plus.google.com/103473736257525043693

#10 old school

old school

    Houdini Master

  • Members
  • PipPipPipPip
  • 888 posts
  • Joined: 21-March 03
  • Location:The Great White North

Posted 11 June 2006 - 06:09 PM

I believe there is an error in the default VEX Glass SHOP...

The test to see whether you are entering or leaving the surface in the fresnel() function material reads:
    dot(nn, N) < 0 ? 1/eta : eta
where nn is the frontfaced normals. This is being tested against N. The default render results are definitely not like Mario's above and explains why I never got results comparable to other examples.

Shouldn't the test be from the viewer to the surface normal? Like this:
    fresnel(-V, nn, dot(-V, N) < 0 ? 1/eta : eta, kr, kt, R, T);
The Advanced RenderMan Book uses this form to test for the enter/leaving test (Listing 17.3, glass.sl, p491).

After doing this change to the default VEX Glass shader, the render looks pretty much like Mario's above.
glass_test.jpg
Now to get the top reflection in the thin wall glass sphere object.

If there is an error in the default VEX Glass Shader, then this tutorial needs ammending:
http://odforce.net/t...derwriting4.php
and all my shaders derived from the default VEX Glass shader.


You really have to watch the surface normals on your geometry as well. In the thin wall sphere the inner sphere requires a Primitive SOP to reverse the inner primitive only so that the surface normals point inward.


Anxiously awaiting more Mario. Bravo! Fantastic!
There's at least one school like the old school!

#11 Mario Marengo

Mario Marengo

    Grand Master

  • Members
  • PipPipPipPipPip
  • 1,249 posts
  • Joined: 26-June 02
  • Location:Toronto, Canada
  • Name:Mario Marengo

Posted 11 June 2006 - 07:24 PM

Disclaimer: what follows is complete, 100% speculation based on the results I'm seeing. I haven't spoken with anyone from SESI (yet) in order to confirm or deny any of it. I will try to get more reliable information as things progress, but right now, it's all just that: speculation... so please take it with a few large cubes of refractive salt :P

Jason, on Jun 11 2006, 12:55 PM, said:

So Mario et al, do you think this problem is due to the distribution of the micropolygons? i.e in the dicing method? Have you experimented with different surfaces topologies to test that out? What are you using currently? A Primitive sphere? I've long been confused about the algorithm Mantra uses to dice geometry. Trying to battle shader aliasing for rendering ocean surfaces makes me wish we knew the exact method so we could fight with our eyes open.

I've tried different surface types and they all show slightly different versions of secondary-ray aliasing (at quality=1). I don't think it has to do so much with the distribution of the mp's as much as what it does with the derivative information (gathered at the mp) when shooting the secondary rays. A texture can only antialias itself when given a somewhat accurate estimate of the area being shaded. A pure ray tracer doesn't have the luxury of knowing the area of the surface each hit represents, so it usually approximates it using ray differentials, or by shooting lots of rays and filtering, or both. A micropolygon renderer knows the area of the micropolygon of the primary surface, but then would have to use some other method for the secondary rays. My gut tells me that when Mantra is in micropolygon mode, all secondary rays have an area estimate of zero (point sampling), whereas in raytracing mode, something like what I mentioned above is going on and the area estimates exist and are valid. In MP-mode you need to raise the quality, which shoots more point-sampled rays and so gets closer to the real result, at a price.

Now, I'm using the reflectlight() and refractlight() calls (1-sample with zero angular spread) in the current shader. It is possible that a different call, like trace(), might carry differentials with it (I haven't tried it). But even if it did, I don't think we would be much better off because then we'd have to handle the trace recursion ourselves, which would be a horrible mess inside a shader (assuming it's possible at all). Ditto with shooting 4 rays, one from each corner of the mp, since I believe derivatives might be non-existent by the time we get to the third surface and beyond.
One possibility that occurs to me would be to determine the angular differential at P (dNdu,dNdv) and shoot multiple uniform (not cosine-weighed) rays over the solid angle. But then we'd be back at square one if the derivatives at higher bounce levels are invalid...
We really need more information before we can make an intelligent choice for MP-mode. :(

Jason, on Jun 11 2006, 12:55 PM, said:

Or, less likely, do you think that somehow the calculation of the attributes that you're using eg. the surface Normal, differ in micropolygon mode?

I sincerely hope not! :)
Nah, I really don't think N or any other global is different between the two modes. Derivatives on the other hand... and specifically how they get carried around during recursion... *that's* where I suspect a difference.

Jason, on Jun 11 2006, 12:55 PM, said:

Finally; have you tried to render this with 8.1? Are there any improvements you've noticed over 8.0 in this type of scene?

I haven't tried any of this in 8.1 yet. I'll post the test bundle soon (I just need to clean up a couple things), then we can all do some testing of this stuff.

Cheers!
Mario.

#12 Mario Marengo

Mario Marengo

    Grand Master

  • Members
  • PipPipPipPipPip
  • 1,249 posts
  • Joined: 26-June 02
  • Location:Toronto, Canada
  • Name:Mario Marengo

Posted 11 June 2006 - 08:07 PM

Hey Jeff,

old school, on Jun 11 2006, 10:09 PM, said:

Shouldn't the test be from the viewer to the surface normal? Like this:
    fresnel(-V, nn, dot(-V, N) < 0 ? 1/eta : eta, kr, kt, R, T);

Hmmm... not so sure about that. At one point, I did a comparison between my custom Fresnel and the v_glass shader just to see the difference, and they were pretty much identical, provided that:
1. Your objects all have correctly oriented normals. (really, no glass shader will have a hope in hell of "getting it right" unless this is the case).
2. The shader doesn't force shading normals to face front (or manipulate them in any other way before computing Fresnel)... ever.
3. And one other somewhat mysterious thing... something that seemed puzzling at the time (and which I sort of "shelved" for further testing), was the result I got after removing the sidedness logic from the fresnel() call itself (moved it outside the function call)... it started giving me all sorts of unexpected results. For example, when, instead of calling it in the way that the v_glass code does:

fresnel(-V, nn, dot(nn, N) < 0 ? 1/eta : eta, kr, kt, R, T);

you called it something like this:

vector n  = normalize(N);
vector wo = normalize(-I);
float myeta = dot(n,wo)>0.0 ? 1.0/eta : eta;
fresnel(-wo, n, myeta, kr, kt, R, T);

then all kinds of craziness ensued... maybe the optimizer was chopping off something important... or maybe I was doing something wrong. I didn't persue it at the time because I was concentrating on the custom version, but it's worth checking out... even if it's just to confirm that I'm just full of poo :)

old school, on Jun 11 2006, 10:09 PM, said:

You really have to watch the surface normals on your geometry as well. In the thin wall sphere the inner sphere requires a Primitive SOP to reverse the inner primitive only so that the surface normals point inward.

Yup. This is *very* important. And it will be equally as important with the shader I'm developping here. There's no way for the shader to handle arbitrary normal directions predictably over multiple bounces.

P.S: the top reflection in the spherical shell is in the region of total internal reflection of the inner surface, and I *believe* the built in fresnel() handles TIR propperly now, so it should show up if you raise the number of reflection bounces high enough...  :unsure:

Cheers!
Mario.




1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users