Add interference effects to glossy and glass shaders
Open, NormalPublic


As a response to patch D2003 and my own work in OSL (interference script), I suggest to add the option of interference effects to the glossy and glass shaders natively in cycles. Please note that I'm not familiar with the source code of Cycles, so this task description is based on cursory glancing through the code.

The OSL script suffers from the limitation that the effect of roughness on the surface normal does not influence the reflectance and transmittance factors calculated with the generalized Fresnel equations. Only the macroscale normal is used for the reflectance and transmittance. Patch D2003 handles this in a more appropriate way for metals by applying the correct reflectance to each sample from a distribution of angles in case of rough surfaces.

I propose to build on top of patch D2003, which I expect to have pretty much laid out all the ground work, and to extend it to include one or two additional layers of an arbitrary material that can be described with a complex refractive index.

Things to discuss:

  • Implementation: multiple glossy shaders, or a single shader that can handle substrate-only, substrate+1 layer, substrate+2 layers?
  • The phase of the reflectance is required for physically-correct behavior (no approximation). I implemented a complex math library in OSL just for this. Is it acceptable to do the same for cycles?
  • If for substrate-only the approximate Fresnel reflectances are used, then for 1 or two layers with zero thickness a small color change can occur as in that case the exact solution is used. Is that acceptable? (disregarding a check for zero thickness of course)
  • What to show the user? My OSL script has 3 n,k pairs (RGB) for each material (substrate, layer 1, layer 2). Then two thicknesses, one for each layer. I think this is the minimum. I optionally have the wavelengths of the RGB triplet and the refractive index of the outside medium as input via conditional compilation. The latter is perhaps the least useful, but the wavelengths determine the spacing between the interference fringes and therefore have an artistic value.

There are probably more things that I didn't think of, but this is a start. I am familiar with the physics and the equations that calculate such a feature and have experience with coding in C++. I would volunteer to implement interference effects in cycles and am thinking along the lines of co-development with a more senior Cycles developer.



As I understand it, the OSL shader you implemented is not just about thin film interference, but a general substrate + 2 glossy layers shader? At that point, aren't you almost writing an uber shader, like the Disney principled BSDF that's being implemented?

We should try to end up with nodes that complement each other and have consistent and "intuitive" parameters.

I don't have the full picture for this. Perhaps more rare or specific effects like thin film interference or car paint materials do not need to be part of any uber shader. Instead we could have a node that takes some BSDF as input, and outputs a modified BSDF with an extra layer on top? Or maybe for most materials you could have one parameter that controls the amount of interference, and you don't really need so much control?

For the complex math functions, if they are required we could have them of course. If we can use a simpler and faster approximation that could be fine too, I'm not too familiar with the specifics here.

No, the OSL script is definitely not an uber shader. It really is only about thin-film interference. Let me for simplicity stick to the glossy version: the OSL implementation is the direct analog of the fresnel_conductor() function. Only, it's not limited to just metallic responses, but takes into account the coherent bouncing of light in between the films, which gives the colorful surfaces you see in soap bubbles.

There is no actual BSDF output from the OSL script. Instead, the calculated color/reflectance is inserted into the color input of a single glossy shader. This is not an effect that you can simulate by stacking multiple glossy shaders with different materials, not even when it's a glossy shader based on the complex refractive index (aka n, k pair). The crucial part to get the interference pattern is that you calculate with the (complex) amplitudes of the reflectances, and not the reflected intensities. I therefore also suspect that creating a node which modifies an input node will not work, as the output of any BSDF is related to intensity.

The analogy with the fresnel_conductor function is the precise reason why I think patch D2003 is very close to get a working implementation quickly. Instead of calling fresnel_conductor(), you'd call generalized_fresnel (making something up here) and that's it. Of course, there's ample room to discuss how to make the parameters intuitive.

With respect to the complex math, I figure that using the <complex> C++ template for the float3 type should be sufficient. I guess only the actually used functions from the class would be linked into the binary(?)

Ok, I'm not expert at this so bear with me. The layers you have in this model, aren't they like typical BSDF layers? But you more accurately simulate the interaction of the complex IOR, while making simplifying assumptions about e.g. the roughness of the layers, that let you fold everything into a fresnel function?

If that's the case, it might be fine to treat them in a special way if doing the complex IOR interaction in a general framework is not practical. But I wonder if this strikes the right balance. Adding 6-8 parameters seems way too much for things like soap bubbles, while for car paint just a different fresnel function might not be enough and you want to be able to give different layers different roughness, normals for flakes, etc.

It could be done as a separate node that modifies a BSDF if we want to, where the node would basically change the fresnel function of the BSDF closure. Such a node could use a model like this, or a color ramp, etc. I'm not sure if that's the right design, but it is possible.

We can't use <complex> in the kernel, there's no C++ support in OpenCL at the moment, we must use C code still.

Robert M (prutser_) added a comment.EditedAug 21 2016, 11:56 PM

Well of course, and since I'm not an expert on the computer science part, I ask of you to do the same with me :)

With that in mind, I would say 'maybe' as an answer to your question. Indeed, you disregard the possibility of different roughnesses between the layers. This isn't just an approximation, it's a requirement for interference to be visible. On any substrate, the additional layers need to be conformal, and the roughness needs to be of 'macroscopic' size. In other words, on the scale of the wavelength, the surface needs to be smooth for a sufficiently large area for the light to bounce around in a coherent way at least a couple of times. See the picture here:

I've tried to illustrate what I mean: in (a) you'd have the typical stack of BSDF functions, what is not what I have in mind. Instead, the situation in (b) is what you'd typically see with interference effects. There, a conformal layer is on top of the substrate which gives rise to interference. Because of this conformality, the stack can indeed be described with a generalized fresnel function, which takes constructive and destructive interference of light into account.

While I do agree that 6-8 additional parameters seem excessive for a soap bubble, I'd like to stress that interference effects are much more present in daily live than you might think and aren't limited to just rainbow effects in soap. For example, fingerprints on bare metal give rise to interference, heating of iron changes its color because of the formation of a layer of iron oxide, aging copper turns first darker red and then to somewhat grey because of a layer of cupric oxide (among other materials), and silver tarnishes with silver sulfide over the years. These can all be easily simulated with interference terms and yield photo-realistic and physically-correct results (disregarding the microfacet half-vector issue of course).

With respect to your remark about car paint shaders, I come back to the point that there can't be interference if the layers aren't conformal. I therefore don't believe that the amount of degrees of freedom is insufficient for a car paint shader. Moreover, there is no reason why you couldn't mix multiple interference shaders with different roughness settings if that's the look you'd want. The input for normals for flakes is, if I get what you mean, already in place as it is with the current glossy shaders and doesn't need any additional input.

Regarding your thinking-out-loud suggestion of a node that modifies a BSDF: it sounds like the equivalent of a callback function to me? That would be precisely what I missed in the OSL language. I'm also not sure it is the right design, but it's good to consider the options.

It's a pity <complex> cannot be used, but other options then might be the C99 complex functions, or worst-case I could re-implement my complex math library from OSL in C.

Some images of the non-trivial examples:

Edit: and one more obvious example for completeness' sake:

Thanks for the explanation. I think it could fit in the Metallic BSDF node as another fresnel mode, only showing extra parameters when a "Thin Film" mode is enabled. The Glass BSDF could get a similar mode.

Still, a lot of parameters like this with indirect effect on the look is not really in line with how I think Cycles shader nodes should be designed. Personally I think we should try to find parametrizations like the Artistic fresnel or the Disney BSDF, where there is a relatively intuitive and direct relationship to the look of the material. With 8-10 total parameters for just fresnel, it seems more an interface for an engineer than an artist.

If we can somehow expose this as a color ramp for example, driven by the angles with the incoming and outgoing light, that might be more intuitive. But I'm not sure if it is possible.

I understand your hesitation to 'dump' many parameters onto users but I think that the way Cycles seems to be progressing is the correct way to proceed: artistic settings for those that want to go with that workflow, and physically-correct settings for those in favor of that type of workflow.

The artistic method has always been an option, however with ever-increasing computational power I think it's logical to pursue better photorealism based on physical principles. Cycles itself is of course a prime example, but also the upcoming n,k metallic shader is a good example. I also think that refractive index and layer thickness are easy enough to understand, which leaves the extinction coefficient (k) that can be more elusive. Even if you don't really know, finding measured values of n and k is not hard to do and as a user you're immediately rewarded with, for example, metals that look and behave just like they would in the real world. There simply is no way to get that result without the physical approach. The issue can be alleviated further by providing demo files that already contain the correct settings for many metals/materials, as a sort of material library, and documentation that goes beyond describing just the parameters but also the pricinples. I would also volunteer for that.

It's interesting that you bring up the option of color ramps vs. angle, as that approach actually would be best suited for the simple n,k materials. Unless you parameterize the values of n and k, you can fit the physically-correct behaviour of metals with such an approach. However, making these curves by hand and getting them correct is much harder than filling in the listed values for the metal you'd like. But, as soon as you would like to parameterize anything (in this particular case layer thicknesses, for example), this method doesn't work at all. In order to expose the additional functionality, would be something to make 'modifier frames' of the simple shaders, that add additional functionality to the simple shader? For example, you draw an 'interference frame', drop in a glossy shader and it that makes it expose the additional parameters? Indeed in the backend I would implement them as separate shaders, but the presentation might be more intuitive as you wouldn't have an enormous list of different types of shaders. This also means that the user is only exposed to them when they purposely take the action to get the functionality.

After reading through this I thought I would add that there should be a more artistic way to get reasonably accurate results with inputs of color, thickness, and roughness. I've played with this idea, but not having the technical understanding @Robert M (prutser_) has, I didn't create a shader with the physical attributes. I guess the implementation will depend on striking the right balance of ease of use to accuracy and flexibility.

I definitely think having all possible needed values for a 3 layer material is flexible, and if you know the numbers it can give you perfect results. That being said, for things like soap bubbles a transparent/glossy mix with its colour determined by a thin film interference node with just two inputs is sufficient.

I'm looking forward to seeing this added.