GPU Compatible Thin-Film Interference v2.1: Glass Within Glass + Absorption

Good job. Thanks for sharing

Could you explain me then why there is no similarity between thin-film over conductive material while taking values from table I’ve wrote about in post #34?

@JettG_G also stated:

Before, trying to understand all the analytic spectral integration stuff in that paper was really hard, so I pretty much skipped it when I tested out their interference equation (which gave the exact same result as the equation I used from Pruster). After getting another good look through it, I think it might be doable in Blender…

Well, since everything else pans out pretty nicely, it could be that their n3 value is different than specified (actually couldn’t find a specification in the supplementary), or the spectral part is very important (don’t expect it), or perhaps they made a mistake?

Edit: not excluding mistakes on my/our part

the biggest influence ,beside the right formulas,are the used IOR and k values afaik.then the used trichromatic RGB wavelengths lambdas,makes a huge differents with my testings (because that results in different frequency/color disburtion).
for a even more accurate shader,you can implement the calculation from the temperatur that results in different IOR,or the rising damping factor in the thinfilm with increasing name a few ideas, for more realworld influnce factors.

afaik ,the spectral calculation ,in the paper that doo posted,uses a calculation for matching eyesensitivity,for a better wavelength disburtion especially for the red,because the red has influence in the low wavelength at around 4x0 nm and the yellowish red part iirc 580-600+.

edit,you can double your accuracy simply with doubling the sample you have 3 for can make a spectral render for the poor man.simply calculate the magenta cyan and yellow with the correct wavelength lambdas and IORs ofcourse.and combine the results.than you have 6 sample points.

That’s actually already included in ‘k’: a thicker absorbing film (that is, k>0) will automatically transmit less light :slight_smile:

you can double your accuracy simply with doubling the sample you have 3 for can make a spectral render for the poor man.simply calculate the magenta cyan and yellow with the correct wavelength lambdas and IORs ofcourse.and combine the results.than you have 6 sample points.

Only if you assume a spectral shape of your lamp (each lamp individually) and weigh the relative components accordingly.

Just for reference if anyone is interested, I did a poor man’s spectral calculation with 81 samples across the spectrum for my OSL script (same physics as this node group) some time ago. Here I assume a white light spectrum (D65) and also used white light in Cycles (R,G,B)=(1,1,1).

Conclusion: it only matters if you really want scientifically exact results, represented on a RGB display.

Here’s the full post for reference:

Thank you for making this!

Hey everyone, it was brought to my attention, so I updated the blend file to have an attribution to Robin Marin for the scene.

Also, while I have not been able to implement the equations correctly from the papers mentioned above, I think I may have found a different way to implement some fake spectral calculations. Though, I’m having trouble attempting to implement it.

The idea is that if I input a gradient of wavelengths (linear interpolation) into the Interference Component node group (found inside the Interference node group) as a very small texture (along with a map of n and k values made in the same way), the result will be a map of reflectances. The map is then multiplied by a fake white color to produce color. I basically intend for it to work similarly to pixels on an electronic screen.

Now, I’m not exactly sure if it will end up working but here’s a link to the file I’ve been working on. Try playing around with it and see what you can get:

The link will also be in the first post.

I’m running out of time right now to describe some of the ideas I have to implement the fake spectral stuff correctly, so I’ll make another reply with them as soon as I can.

I have also actually created a new simplified Interference node group (made similar to what’s described above but with constant interpolation) that I’ll be posting soon. This method works much faster than calculating the Interference Component node group three times. Although, because of the many small textures used in this method, the refraction shader does not converge fast enough. To fix that, I kept the original setup alongside the new one, so the new one calculates the reflection colors and the old one calculates transmission colors. It should not matter in the end that the calculations are different because the result should all be the same.

1 Like

Hello! I’m really impressed seeing what you did there, and I want to ask you a question. Is adding Glossy shader physically correct? Isn’t better idea to make colored specular component of dielectric shader or mix it with metal color? I’m attempting to create super physically based uber shader using 3 RGB IOR values, can I use and mention your method in my semi-tutorial video showing the node structure of PBR shader? I’ll give you credit, of course :smiley: I should post some results on May - June, when I finish animation :cool:

Yes it is correct. The total amount of light is the sum of the transmitted part (T), reflected part ® and the absorbed part (A), so setting the total to 100% means T+R+A = 100%. R and T are calculated by the shader. For a solid metal, there is no transmitted part so T = 0; Therefore, just having a glossy shader which takes care of the R part is sufficient in that case.

For a colorless (non-absorbing) dielectric, A = 0, therefore R+T = 100%. Therefore, you hook up the R part to the glossy shader and add a refractive shader to it where the color input takes the T output of the node. Note that for a simple glass the reflection is colorless (if the light source is white), but that with interference effects this changes (soap bubble effect). However, it is still the correct thing to do.

If the base material of your dielectric is colored, it means it absorbs light. The standard glass shader in blender doesn’t handle this correctly. Two issues arise: 1) If you select a color, the color is the same regardless the thickness of the object, and 2) the reflection of the object is colored. This doesn’t happen unless the material is hugely absorbing but then you wouldn’t use a glass shader anymore. You can check yourself with any colored glass object that with a white light the reflection is white as well.

Gottfried Hoffman wrote a nice tutorial on how to fake the absorption inside the glass: best solution to fake it then is to take the T output of the node from this thread and put it into a refractive shader which includes Gottfried’s trick. The glossy shader with the R part as color input is then added again which gives you a pretty accurate glass shader.

Isn’t better idea to make colored specular component of dielectric shader or mix it with metal color?

If it gives the result you like it’s all fine, but physically it’s incorrect.

As for attempting to completely fix the problem with absorbing substrates, could some of you help take a look at these articles:

Generalization of complex Snell-Descartes and Fresnel laws

Ray tracing in absorbing media

I want to try implementing a generalized Snell’s law of refraction from the first article (whichever of the listed ones in the article is fastest) to see how it would affect the results. The second article seems to have some clues (equations: 5, 6 and 7) to helps solve the first, but I just can’t seem to figure out the math and solve for the necessary terms. I would very much appreciate any help!

Of course, transparent, absorbing materials are not exactly common, but I would love for the node group to at least correctly calculate for such situations. Also, solving this might help find an effective refractive index for materials with absorbing substrates (or materials in absorbing incident media) that we can plug into the refraction shader.

I’m really sorry for the very, very late reply (I haven’t been keeping up with this website for a while…), but you can go ahead and use the node group!

Hey, thanks for the update! So you vectorized the node group essentially, right? Cool :slight_smile: Also good idea to take the matrix approach, especially if it’s simply faster. I’m looking forward to check it out on my GPU tonight :smiley:

Can you explain what you mean? If I add a layer of glass on top of gold (= lossy substrate), I still have a yellowish surface color meaning that R+T is less than 1 as T = 0 and R < 1.

For what it’s worth, I once calculated how much the rays would deviate from the non-absorbing substrate case, and if I recall correctly it was negligible for the cases where you’d still speak of a transparent substrate. As soon as the extinction coefficient was large enough to make the wave vector substantially deviate from the simple case, the material was so strongly absorbing that it would be opaque for any macroscopic object. Of course it has been two years since then and your test might indicate otherwise.

Edit: seems like your new blend file misses the background image for illumination?

If you add the R+T outputs using the color mix node, then subtract the result from 1, it seems to give zero which is incorrect. The result should be what T is right now because, like you said, T should be 0.

I was able to figure out the generalized laws, and I actually did find that it’s pretty negligible. As for your question in the other thread, figuring out and implementing the generalized laws is what seemed to change the output for lossy films in v2.1. Try checking out Principles of Optics, 6th Edition, chapter 13.2, for the derivation I used (but with the ‘n+ik’ definition and without the assumption of a dielectric incident medium). Chapter 13.4 has another derivation for (n+ik)cos(theta) that I used instead of s_z(n+ik).

Oops, my ‘pack files into .blend file’ option keeps coming up unchecked (the image should be the same as v1.0). I’ll fix when I get home later! Thanks for telling me and checking out the update!

You need to be careful here. Let’s say we have a metallic substrate (meaning, absorbing). The Fresnel coefficient for transmission will not be 0, as the amplitude of the plane wave into the substrate is not zero (it cannot be because of the boundary conditions at the interface).

However, in the case of an absorbing substrate, the wave will exponentially decay and all the power is absorbed. Since the Fresnel equations are valid for an infinite substrate, by definition then the transmitted power to ‘the other side’ is zero. Adding R and T in this case doesn’t mean anything really, except that it has something to do with the electromagnetic fields on both sides of the interface. The correct equation is A = 1 - R, with A the absorbed fraction of the power.

Playing the devil’s advocate: if you use a more general law for a case the simpler law was valid for, you must get the same outcome. That said, Born and Wolf do not really treat a more general case. In contrast to my code, they use complex angles instead of complex k vectors, but the result is the same. Specifically, the space helmet should remain the same as the substrate in that case is nonabsorbing.

Finally, having an absorbing incoming medium is sort of a weird situation to apply the math to, since this would mean that the incoming wave would be infinitely strong at the source, infinitely far away.


The above is not criticism and let it be clear I very much appreciate your work!

Ah, I see. So let’s say we have a lightly absorbing glass substrate, how exactly should I handle that (this is actually the situation I’ve been trying account for when I talk about lossy materials)? It wouldn’t necessarily be infinite, meaning that light is still transmitted while also being attenuated. I assume I would use the normal implementation of Beer’s law along with the attenuation coefficient?

I have actually implemented a solution involving Beer’s law, but the absorption was not at all noticable, because of how large the wavelength values are in the attenuation coefficient. Not sure if I did it correctly though. It should be:

Exp(-4 pi K d / y),

where y is the wavelength, K is the imaginary part of k_z, and d is the distance, right?

Hah, you’re right! Generalized laws should give the same results; I must have implemented it wrong. Although, I will say that the way I understand it, Born and Wolf use the complex k vectors described in 13.2 to find the angles between the real phases. And I’m realizing that maybe I’ve got it wrong because I use that same angle for the imaginary phase when I should be finding another angle (need to test it when I get home).

Using the same math, that angle would be 0 because the incident medium is real. And because they assumed the incident media to be real, it makes sense they wouldn’t have described it.

Yeah, it wouldn’t really make sense, but, because the layers are flipped for backfaces, I wanted to account for such a situation where the incident medium is absorbing. And that brings us back to the situation about the lightly absorbing glass substrate. It’s not necessarily infinite… so what if I were to calculate the incident medium’s relevant values in reference to it entering from air, the true incident medium. Would that be more correct?

Oh, and this is why I looked at Born and Wolf’s derivation, it seemed to be easily altered to account for absorbing incident media.

Nah, I really appreciate it. I get to talk about my thoughts about implementing the physics and test my knowledge while correcting my mistakes and all!

Sorry for the delay, and the rambling below. I took the time to look a bit more carefully into the second paper you listed in a previous post (ray tracing in absorbing media).

First of all let me emphasize that the method you implemented for the Fresnel equations with your first node setup is already the generalized Fresnel equations, meaning that having arbitrary absorbing media (k>0) are not a problem in order to calculate R and T (same goes for the OSL code I wrote). This holds for both substrate and film(s). Therefore, the generalized Fresnel coefficients in that particular paper are not something new really. It’s just expressed in a slightly different way.

The same paper does give us a nice idea of the error we make with the ray angle given that the ray refracts slightly differently when absorption is in play. According to themselves, the deviation in angle is less than 1% when n/k < 0.15, where n is the real part of the refractive index and k is the imaginary part. So let’s see about the extreme, where the error is the largest.

I’ll take n = 1.5 for typical glass and set k to the maximum to stay within 1% error: k = 0.15*n = 0.225. So for a refractive index of 1.5+0.225i, the angle of refraction starts to deviate 1% from the case where there is no absorption. Let’s then see how far this ray gets into the glass before it’s virtually completely absorbed: absorption scales with exp(-4 pi k d/lambda), where k is the imaginary part of the refractive index (that is, not the imaginary part of k_z) and d is the distance traveled. Let’s take lambda to be 600 nm, nicely in the middle of the visible spectrum (400-800 nm). After one wavelength (d = 600 nm), we have exp(-4 pi k) = 0.06 = 6% of light left. In less that one micrometer nearly all the light has been absorbed!

Conclusion: the absorption is so strong that this glass isn’t transparent at all, yet the deviation from Snell’s law is only 1%. Nothing to be worried about really. For truly low-absorbing glasses, the deviation is much less and the real part of the refractive index can be used worry-free in a refraction shader.

How I would deal with absorbing substrates: for lightly absorbing glasses, use the T you get from the generalized Fresnel equations and plug that into a refractive shader. Then take absorption into account with volume shaders or fake it with the ray length node output (Beer-Lambert).

When k is on the same order of magnitude as n, the light is absorbed so quickly that it makes no sense to add a refraction shader and only a glossy shader is sufficient. When n < 1 and k > 0 you’re likely looking at metals or semiconductors and again only a glossy shader is sufficient.

Perfect then :slight_smile:

It wasn’t the Fresnel equations, per se, it was more like Snell’s law not working for absorbing incident media. With the way it was in v2.0, Snell’s law would produce values R+T=/=1 (without absorption in the substrate being taken into account) even with the corrected transmission given by the mixed Poynting vector for absorbing media.

That’s actually exactly what I did for v2.1! Although, the implementation is slightly different. I use the absorption squared to modify the R because light goes through the incident medium then travels back through the medium again. That is in addition to the T output being modified by simply the absorption.

You know, one of the problems I had initially was getting the Beer-Lambert law to work correctly. It wasn’t so much an incorrect implementation of it but me forgetting to convert the units of the ray length output, hah! By default it assumes that the ray length is given in mm and converts that to nm.

Yeah, the deviation of the angle of refraction is practically nonexistent at values that don’t completely absorb the light, but I did keep the effective refractive index output for when the incident medium is not air (such as water).

In one of the new materials I added (which shows an example of an unphysical glass which models exactly that but with absorption removed), I made sure to make a note of this!

Thanks for the input!

Right now I’m making renders of the new additional materials (nearly all glasses) for the update post, but here’s the blend file for now: Click!

Tell me if you see any inconsistencies, mistakes, or maybe even typos anywhere! Thanks!

Hey everyone! I just updated the top post with v2.1 and images of the new materials! If anyone notices any inconsistencies, mistakes, or typos in the .blend file tell me so I can fix it. Thanks everyone!

I’m pretty happy with the new update, so have some images/videos of real life thin-film interference effects I’ve encountered:

1 Like

Hi, this thread was both beautiful and very informative!

I have attempted to make a thin film interference node group in Cycles, but ran into troubles when it came to my understanding of complex IOR. I have created a system which allows you to render spectral images in Cycles, and define materials dependent on the specific wavelength. (Can be found here)

In terms of implementing thin film interference in my spectral environment, I ran into the issue of being able to deal with interaction of the primary reflection and the reflection off the underneath material. For example, a bubble goes from an IOR of 1 to ~1.33, then back to 1 inside the bubble, but for a thin film over polished metal, the destructive interference depends on the reflection spectrum of the metal underneath - that’s where my system fell apart.

Does this implementation deal with that? It could be that the math of my implementation is just too simplistic, but I feel like this would have to be done in code because it would need to ‘look’ at the result of the underneath shader before determining the colour to display.