How to best approach glass from a PBR standpoint?


I made dielectric and metallic PBR materials based on a tutorial (angle^5 rather than fresnel due to controllability), and I have no problems with those. The tutorial said making glass would be just as easy, but (although I have no tutorial for this one) I find that extremely hard.

Setting up “manual glass” using refraction and glossy with a mix curve, I find no proper means of mixing a custom curve (angle^5) with the hardcoded IOR effect in the refraction node. It appears to look okay’ish, until I put a solidify on the objects, at which point the whole thing falls apart.

It will also fall apart if a normal fresnel mixer has a different IOR than the refraction node. So as far as I can figure out, the only way to approach refractive glass is to use a fresnel node as the mixer and match its IOR with the refraction node.

Question 1: How to approach the “ducking” (reduction of fresnel) in fresnel output based on the roughness value? With angle^5 based PBR we’re just applying a angle^(1/5) reduction, and it just works. Applying angle^(1/5) reduction to IOR based fresnel, well, it kinda works, but it doesn’t really look correct.

Question 2: What is glass, in a PBR sense of the word? I’m currently exposing absorption color and density while forcing white into glossy and refraction. I’m also exposing a “bias” control which lets you control the shape of the fresnel (anti reflection coating on the glass, or a more reflective glass). I’m also exposing IOR, even if for PBR approaches we don’t usually expose the power (steepness of the fake fresnel curve) to the user. Is there anything “special” that goes on with glass as its roughness increases? Should I introduce translucency which adds a heavy cost? Should I simulate russian roulette to avoid ray splitting, and if so, should I do it even for the first hit?

PBR glass - a bit more thought extensive than I expected :slight_smile:

Just thinking if this “ducking” is even correct… Energy conservation for reflections spread by roughness is already
taken care of by Glossy node.

I’m not sure, could be. I thought of it as a hack to counter some problems when using angle^5 as input rather than fresnel. If I set up fresnel output to a greater than input (>0.44’ish), varying the IOR I get a band closing in (faster from the center of a sphere than the perimeter). It closes up as fresnel reaches 5.941. Maybe this is correct behavior, I don’t know. But it’s counterintuitive when you want to deal with facing. But using facing^5 as a rough approximation gives off a really strange look when simply driving glossy color like a red metal sphere fading to white at the edges. It looks much more natural when you bring in (additional) ducking. Or am I missing something here.

But you may be right, in may not have a place in my glass, which uses fresnel anyway (has to, to make it work good with refraction node’s IOR control). The additional ducking made my glass go very dark when roughness was brought up. I’ll give it some tweaks and see what happens.

IIRC, the “ducking” is a hack to workaround the lack of per-microfacet fresnel in Cycles. There’s no way to apply it to refractions though. For glass, I’d recommend just using the glass BSDF and the volume absorption nodes, and call it a day. Refraction/glossy mix is called as two shaders instead of one, I believe, making it somewhat slower/noisier than the glass node. Leave the glass node at full white and send transmission color through the color on the volume absorption node. Scale with density as needed. For clean glass, that’s more or less fine. You can add dirt/smoke to the glass by mixing with a regular diffuse/glossy shader (like one of the many PBR node groups out there).

I think if you want frosted glass, the per-microfact fresnel problem is going to show up again and I’m not sure there’s a good way to work around it in that case. If you have ultra-frosted glass with too bright of a highlight, you could use a glossy/refraction mix with fresnel, and just stick a math node between the fresnel and mix shader nodes. You can cheat your way to anti-glare glass the same way.

Isn’t per-microfacet fresnel done by just replacing surface normal with half vector between incoming and outgoing when calculating fresnel term? If so I would expect that this would make reflection brighter not darker (just a hunch). How luxrender does it?

How about russian roulette to avoid splitting? What would be your approach to this? I’m doing a LERP’ed dense noise, and go glossy if result is greater than 0.5, refraction otherwise. See top left of image. But early tests results shows that rendering using RR takes slightly longer than without, so I’m guessing I’m doing it wrong. It’s supposed to give a massive speed increase without noticeable loss of quality. Bummer. Any suggestions on how to do this correctly? I didn’t have any code to go by, just some vague explanation. Or do Cycles prevent ray splitting on its own if it can (say randomized for consequtive samplings of the same pixel it does one or the other but not both)? Image:

Branched path always ray-splits, regular path does a random pick. If I’m reading the alSurface blog post right, it seems it does a random pick weighted by the mix factor, essentially? That might be a really useful optimization for the mix shader itself in Cycles, but I’m not sure how you’d implement it through SVM. The noise texture will always return the same value for a particular sample point, so it won’t work as a random source. The mix shader won’t disable the shader call unless the fac is actually 0 or 1, so you need a shader that actually gives 0 or 1 randomly, not a pre-mixed average.

@ostry: Not sure, I’m afraid. :frowning:

If outputting the greater than math node to an emission (outputs 0 or 1, right?), it produces noise of black and white density weighted by fresnel. Or do you mean it averages around before doing the calls, ultimately always ending up calling both? I was using regular path though, wasn’t aware of a difference.

Greater-than is 0/1, the problem is the noise texture isn’t a per-sample RNG. A given texture coordinate will always return the same value no matter how many times you sample it. It’s meant to give randomness over space, not across samples. You won’t get any kind of RR from that, you just get textured falloff. I’ll admit I might not be understanding all this properly, but I think RR would require a function that returns a different random number every time the shader calls it. Which I don’t believe Cycles has.

Oh, textured falloff was what I was going for here, but I was hoping that making noise output very dense it would kinda blur into subsamples (or something). Like i.e., if sampling 8x8 to make up a pixel, the net result would be a mixed value of those 64 samples, even if those 64 samples got the same result from each trace. I admit, I don’t really know how this stuff works, I’m used to brute force ray tracing from old times :slight_smile:

In the article, he mentions that RR isn’t performed on the first split. Which brings me to another question: How can I count ray depth from “only this medium”? Can I exclude ray depths that occurred before reaching current material? Say having suzanne in custom glass behind a normal glass window (or two, or three). I only want optimizations on suzanne to occur if ray depth > 1 within suzanne’s custom glass. Is this possible?

This isn’t important, just trying to learn and figure out some stuff.