Cycles PBR thin film interference, iridescence and metals

Great, because I like some feedback :slight_smile:

I noticed in the blend file you have some materials with exposed wavelength and some without, wondering what the difference in workflow is there.

Very good question. The OSL script has statements that are used for conditional compilation to maximize speed. For example, say you only want to use it to calculate the interference due to a single layer on top of a base material. Then, there is no need for the code to calculate the second layer that I’ve just added. You can simply set this layer thickness to 0, and it will be as if it is not there, but the code for it does execute. This seems to me like a waste of CPU time, and therefore the conditional compilation exists.

At the top of the code you can use #define statements to in- or exclude parts of the code. The provided example blend file has actually three identical copies of the OSL code, except for the amount of layers that it calculates. This is controlled by the flags for conditional compilation. The current flags that you can #define are these:

  • SINGLELAYER
  • DOUBLELAYER
  • NONVACUUM
  • EXPOSE_WAVELENGTH

As you probably expect, SINGLELAYER includes just the code for a single layer on top of the base material. DOUBLELAYER includes code for two layers on top of the base material. If you don’t #define either, it will be just the base material (for example, for accurate metals). The NONVACUUM flag exposes the assumed index of refraction for the ambient medium. Typically that’s air, but maybe you have an underwater scene (n = 1.33) and you need to calculate what happens then. This allows you to change it. It has not been tested extensively by me!

Now, to your question: since Cycles isn’t a spectral renderer, we have to choose what wavelengths represent the red, green and blue components. At first, I hard coded some values in the script, which of course can be easily changed by changing the code. However, I realize that often artists aren’t coders (or vice versa :slight_smile: ), so making them exposed in the shader node is an artist-friendly way to give them access to the internals. However, in many cases it’s just fine with the hard-coded values. Therefore, you can choose yourself whether you’d want access to the wavelengths from outside, or just hide them altogether and maybe gain a little bit of speed. There is exactly one case where I used this, which is in the heated iron example material. There, I found that choosing a different wavelength from the default values improved realism. However, I didn’t want to include yet another copy of the same code just for that shader, so all double layer materials have the wavelength exposed.

Also, when trying to find material nK, often databases will only have the n value. Any advice for guesstimating those?

Well, yes, my best advice when nothing is listed is to guess it, realizing that clear glasses have k values of about 1E-9, very strongly absorbing colored clear materials have k ~ 0.1, and metals have k ~ 1…10. Then, if you know something is blue, make the k value the opposite ‘color’. Blue glass means it absorbs yellow light, etc.

Second advice is likely not helpful in practice, I’ve done this once for a scientific paper, but never for 3d renders. The n and k values are related to each other via the so-called Kramers-Kronig relations. If you know n over all wavelengths, you can calculate k and vice versa. Of course you never do, but you can for example fit a model to the known n, from which you then calculate k. I actually did the reverse, measured k and calculated n from that. If you’d want to pursue this route, see for example “Kramers–Kronig Relations in Optical Materials Research”

On an tangentially-related note, can the math you’ve been using to calculate the thin-film interference be expanded to cover cross-polarized light? That keeps coming up in my searches when I’m looking for thin-film stuff is all.

I’m not sure what you mean with cross-polarized light. You can have the orthogonal polarizations, typically parallel and perpendicular when you talk about flat surfaces. The Fresnel code already treats them separately, but in general light that you see around you is randomly polarized and we see the result of the average of the two polarizations. You can separate the two in the OSL script, but I don’t think that is a useful thing in general as there is no polarization state defined in Cycles.

In the meantime, I picked a set of values that jumped out from the array and wrapped it onto that Mike Pan BMW you used earlier.

Very nice:cool: