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

Hey everyone! There are no updates for now, but depending on the how things go for the next week or so, there could be one.

For now, I’ve created a few animations of various parameters of the thin-film interference node group changing. They’re really fun to watch!

This is a bubble within glass with the bubble’s refractive index increasing. At some point, the refractive index matches the glass, and the bubble disappears. Similar to that experiment with glass rods disappearing in oil when their refractive indices are similar.

This is an air bubble in glass with an increasing refractive index. The colors of the film change throughout the duration of the animation.

The following is a bubble with increasing thickness.

This next one is an example of absorption in glass. It shows how using the ‘k_I’ parameter of the object inside (in this case, a ball of gold) in conjunction with the ‘k_S’ input of the surrounding glass work together to create an absorption effec - it’s a DANCE PARTY!!!

My favorite one is an example of how the ‘Absorption’ input affects absorption. This parameter controls the scale of the absorption. Using the physically correct refractive indices of water at specific wavelengths, we can accurately simulate the absorption of water as the depth increases. Water has a very small extinction coefficient, but that’s what gives it its color (I didn’t realize this until I played around with water’s refractive index in the node group).

The ball inside is also gold… sunken treasure?

Here are the depth values and their respective images:

The final image would be an example of a situation were the absorption is so high, a refractive shader would not be needed anymore. The colors would probably be slightly different for salt water (likely slightly greener) because the salt would change the refractive index.


I have never put in the time to really understand complex IOR, but your tip that the imaginary component is just absorption definitely helps. I think my knowledge of the behaviour of waves at boundaries (to phase shift or not to phase shift) is a bit lacking, too.

Your latest examples look spectacular.

I have a question regarding real-life materials and their IOR. You stated that K_I is equal to the absorption. Is there any way to derive the K_I of a material at a particular wavelength from some other property it has, or is this data exclusively measured from physical materials? How have you represented the complex IOR of water to give it a realistic absporbtion colour?

1 Like

There’s a bit more to the imaginary part (such as how it can affect the angle of refraction), but yes, it does, in general, describe absorption. Oh, and the phase shift is described by the δ in the equations I typed up. Normally, δ = (2 pi d / lambda) (n + ik) cos(theta), but δ in the equations I describe is what allowed R+T=1 (energy is conserved) when the films all have a real index.

I’m actually trying to get a reliable verification of the formulation I use, so that part might change.

I do remember @prutser mentioning a relationship that connects the real and imaginary parts of the refractive index. It’s called the Kramers-Kronig relations.

I input the values for the refractive index I found here into n_S and k_S then increased the Absorption. The Absorption takes values in nanometers, so if you want a distance in meters, you’ll need to convert accordingly.

On a side note, I will say that the upcoming update might, yet again, change much of the formulas used to calculate interference. I was told that the transfer matrix I use can become numerically unstable, so I’m working on implementing the S-matrix (still in the “trying to understand it” part, haha).

I will eventually be updating the collection of equations I typed up, but if you want a head start at looking through the implementation, here’s the resource I’m using: Click! (only up to lecture 5). The videos really helped me understand the implementation.


What a hidden gold-nugget of information! I’m having a read through it just now.

1 Like

Hey everyone!

Update v2.2 should be coming soon when I finish writing some detailed documentation for it! The update implements a more stable modification of the calculations, fixes a bug in the calculations for specific situations, and corrects the formulas for lossy incident media (which does change the look of some of the glass materials added in v2.1). Also, depending on how the tests for a new absorption formula go, there might be some different looks in terms of that (currently having problems with float values in the new formula).


Oops, maybe saying I’d have it “done soon” was a bad idea. Anyway, I have not yet been able to finish the detailed documentation, but here’s the update for now:


The changes in this update are described in my previous post. As for the problem with the new absorption calculations, I instead used the formula in v2.1 and removed an unnecessary step. Some of the glass materials have changed as a result, but they should be more correct.

I’ll be updating the top post when I finish the documentation!


I’ve took v2.2 for a spin with my new RTX 2080 and the overall speed is amazing. Render times are around 13-23 seconds with defaults from a file - 2.8 current build.

I’ve also tried materials in EEVEE and guess what. They are working just fine. Of course not glass ones, some of them. Some of them are crashing Blender. Here are some simple tests - Cycles top ones:

There are some differences with reflections and no mesh lights in EEVEE but overall it looks amazing and works fast as hell.


Wow! I never got around to testing it in the updated Blender builds, and I’m glad you did so for me, hah. Do you remember your Cycles render settings for these? I’d love to compare them to mine.

Never did I expect the nodes to work in Eevee, that’s crazy!

Also, I finally got around to making the renders for the documentation, so the true update should be coming ̶s̶o̶o̶n̶! Here is a preview of documentation for the effects of the complex refractive index:

Don’t worry, I’ll be labeling everything!

I’ve not changed a thing from your file. So they are “default” from you.

I don’t know what caused so drastic changes in render times as I’ve lastly tested it on gtx 770 and blender 2.79. Now I’m using gtx 2080 and blender 2.8 daily.

It’s crazy fast, here is example from render preview:

1 Like

Pretty cool to see it works in Eevee :slight_smile:

1 Like

I finally got around to testing a render in the most recent build of 2.80, but my results were actually slower than those in 2.79.

Results using the same red glass material:
2.79: 2.06.01
2.80: 2:55.58
Slowdown from 2.79 to 2.80: 139.34%

Results using car paint_1:
2.79: 1.53.06
2.80: 2:21.11
Slowdown from 2.79 to 2.80: 124.81%

I think the speed up may be from your GPU! Mine is a GTX 1050.

I also tested the materials in Eevee, and I was still surprised it worked despite your account! I also didn’t expect it to work for the 3 layer calculations, but it did.

It seems the problem with the glass materials is not necessarily the nodes but rather the limitation that Eevee cannot retrieve ray length, which is used in the absorption calculations.

Edit: I looked into it more, and it seems the optimal tile size for GPU’s is smaller. After some testing, I ended up with a tile size of 16 and a render time of 2:33.61. Although, as per your comment, you did not change any of the render settings, so this point is irrelevant.

I also compared the noise between the 2.79 and 2.80 renders, and there is less noise in 2.80.

Left: 2.79, Right: 2.80

Would it be possible for you to compare your render times between 2.79 and 2.80 with the same GPU? I’m curious to see if your 2.80 results are also slower.

I’ve tested it a little bit more and Blender 2.80 is faster. Mostly.

Here is a full test: thin-film-speeds-spreadsheet details inside.

1 Like

This thread is super-interesting, and I wanted to chime in with a question and a thank you for those involved.

I’m a Grad student in materials engineering, and I use blender to make a lot of my figures. I’ve been trying to make a material to represent processed silicon wafers for a while now, and this seems to be just the ticket, but I’ve been having difficulty getting it to work for my application. Is there additional documentation that I can look to for how inputs should be formatted?

I’d like to simulate the off-axis viewing of a silicon wafer with ~ 120nm of oxide grown on it.

I understand using the silicon_nk node to input into the substrate part of the shader, but I’m unclear as to the convention for why it has RGB values. In particular, if I’m making an input for silicon dioxide, should the input for n for monolayer films be simply the bulk n, or should it be the n at each individual pure R,G, and B for my given color space? Additionally, for the output, does it matter which shader this is output to? Should it go to a Principled shader, or glossy, and if it’s supposed to go to glossy, should it be summed in an “add shader”? I’m technically inclined, but probably have less than a month of blender experience under my belt right now, so I’m still figuring out how all these node groups work.

Once again I think this is phenomenal work, and am really grateful that it’s been offered up to the community. I can see this being an exceptional tool for simulating and showing thin film device fabrication in an accessible way.

I’m really glad you found this!

Unfortunately, after so long, I still have not come around to finishing the documentation for this. In the case of materials, the RGB valued inputs are to simulate the change of the n and k across different wavelengths of light. This node group samples the interference calculations at 3 different wavelengths, so you would input a respective n value for each (can be done using a combine RGB node). You can check the ‘Wavelengths’ input to find out what wavelengths are being sampled and to derive your respective n values. Alternatively, you can change what wavelengths are sampled, while staying within the values perceived as red, green, and blue, to produce slightly different results.

Of course, for some materials, the refractive index varies little across the selected wavelengths, so it wouldn’t hurt much to simply use a bulk n value.

As for the output, in your case, you would simply plug ‘R’ into a glossy shader. We wouldn’t use the principled shader because it automatically calculates the Fresnel effect with its materials, and the interference effects are, in essence, a Fresnel effect. Cases where you would to use an add shader include effects like thin films on some dielectric material. For those, add the glossy shader to a refractive/diffuse shader that uses the ‘T’ output.

I hope this helps, and tell me if you have any more questions!

1 Like

Thanks for the help! I’m still trying to work through how I need to set things up. I’ve used the following node setup to try to generate the desired output:

However, this doesn’t quite give me the expected result. I should mention that the silicon nk plugged into the first thin film input has the n,k values for SiO2, and I 've varied D1 from 0-1000 with basically no change

For reference, I’m trying to recreate a wafer that I made:

The variation in color at the edges is from actual thickness variation, and the blue color is from the patterning step. The field oxide (green) should be ~550nm, and corresponds well with this chart. The blue portions should be ~120nm or so. If I wanted to model something like this physically according to my understanding of your node group, I would plug in a single SiO2 n,k group into the thin film input, and use the silicon n,k for my substrate, and by varying D1, I should be able to vary the observed color, in rough correspondence with the film thickness chart. I realize that it probably won’t correlate exactly, but as long as it’s close that’s good enough.

I’m clearly doing something wrong, but I can’t figure out what it is, I think largely due to my noobishness with the blender node setup.

Once again, thanks for working all of this out and being willing to help!

Ah, I think this is where you went wrong. By using the same node group, you are using the same values input for both the film and the substrate. This happens because Blender updates all node groups of the same type if any changes happens in any of its copies. You can confirm this yourself by checking the values inside both nodes. Try adding a new ‘Combine RGB’ node. Alternatively, you can make one of the copies independent from the other by clicking on the ‘7’ next to the ‘F’ for either of the nodes. Note that you will need to restore the original values for the ‘silicon_nk’ node.

It looks like you have everything correct, but you might want the roughness on the glossy shader to match the roughness on the thin film node.

Hope this solves your problem!

Yeah, the thicker the films are, the less the colors might match because more samples across the spectrum are required.

I am currently working on something that could do the spectral calculations for the films, but generalizing (or finding a way to generalize) the formulas to lossy films and multiple layers has been quite difficult. If any are curious, here’s what I am referencing: Click!

1 Like

Man, this is one helluva piece of code you’ve implemented. I fixed the nodes as you described and the examples track almost perfectly with experiment. I rendered the first few in the color series. The very thinnest oxide doesn’t quite work (Edit: After looking at the thinnest one again, I think it actually works quite well. It just needs to be mapped onto a more realistic flat substrate), and things get a little squirrelly when you go to very thick oxides, but in the main processing regimes, this thing works basically flawlessly. Note, the colors being referenced are in the center of the wafer, given a viewing angle which is normal to the wafer plane.

Really, fantastic work on this!

Edit: Experimenting a little further with the thicker oxides, it looks like there’s about a 20nm shift once you get above 500nm, but things still track pretty well once you account for that offset.

1 Like

After a spending week attempting to generalize the integration method in the paper, I simply stuck with the single, non-absorbing layer (for now). Here’s a linear thickness gradient–50 to 1000 nm–of a SiO2 layer on Si:

Top: Interference calculated with 3 samples (current nodes)
Middle: Colors from the chart @BesselFunct linked (thanks for providing the nice chart!)
Bottom: Spectral calculations

You can see that the spectral calculations match very nicely at most points, but it’s actually a little better than that. The colors in the chart are listed with uneven intervals, and I simply didn’t proportion it evenly.

There are two mostly unavoidable problems with the spectral thin-films, not as a result of any calculation mistakes, but as a result of a calculation limitation. Although, it shouldn’t really matter in most cases.

For now, I’ll be testing if using better fit functions will significantly change the results… and then I’ll probably try my hand at generalizing, again.

Hey JettG_G,

I ´ve downloaded your last version(2.2), great work by the way, and I´ve noticed that your scale isn´t right on most objects. It´s like 0.106 mostly and should be 1.
This could lead to interferences when rendering.

Just wanna let you know…

Here is a render with all your materials, love it.

1 Like

Woah! I think the odd scales were from the testing file itself because I don’t remember changing anything except the materials. I just tested it, and the speedup is amazing! For the car paints I quickly tested, I had a speed up of about 320%. Thanks for telling me! I’ll be sure to include this change in the upcoming update.

I love the render of all the materials! It’s so satisfying to look at.