Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

specular_weight reinterpretation #247

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

portsmouth
Copy link
Contributor

@portsmouth portsmouth commented Feb 4, 2025

It was noted on Slack by Masuo that it seems a bit strange that the refraction changes when specular_weight is varied. This happens because in the current spec, we say that the specular_weight controls the IOR of the dielectric.

For a glass object, this may be unintuitive:

specular_weight 1 specular_weight 0.1
image image

The issue is that the name implies "specular presence", so in this case the artist probably expects the control to merely kill the reflection from the glass, not change the refraction as well. We do already have a control that does that, namely specular_color, but it is unintuitive to use that control for dialing the dielectric reflectivity.

So the proposal here is to have the specular_weight simply function as a multiplier to that existing color (so it becomes simply an overall weight factor for the whole lobe). This matches how the specular_weight functioned in Standard Surface.

The change to the spec amounts to a wording change only:

image

Note that this change requires:

  • No change to the existing parameter names/ranges.
  • Some other minor textual changes in the spec, as in the PR
  • A small change to the existing implementations (a simplification in fact).

Adding here the finalized logo. This is almost identical to the one posted on Slack, except:

  - extremely small adjustment to the arrow head, to make it perfectly centered on and orthogonal to the stroke

  - SVG was optimized to remove redundant elements (e.g. gradients). Main logo is now only 10Kb. PNGs exported at 800dpi.
…oftwareFoundation#238)

Rather than eliminating the `specular_weight` > 1 case (as proposed in AcademySoftwareFoundation#228), we discussed keeping this but fixing up the metal logic to ensure the Fresnel remains bounded. This makes the corresponding change needed in the spec. (Note that now the `specular_weight` parameter consistently, for both metal and dielectric, has soft-range $[0,1]$ and full range $[0, \infty]$).

It would be good to double check that the behaviour of the metal looks reasonable for high `specular_weight` values (presumably, similar to the dielectric where the Fresnel saturates).
…dation#218)

Some minor changes are needed to improve the clarity of the wording (around the implementation of the coat details). I discovered these while using the spec myself to implement the Arnold version.
@portsmouth portsmouth changed the title Specular weight reinterpretation specular_weight reinterpretation, to avoid impact on refraction Feb 4, 2025
@portsmouth
Copy link
Contributor Author

portsmouth commented Feb 4, 2025

Note that this simplifies the presentation in the spec (and the implementations) somewhat:

image


We originally went with the approach of specular_weight controlling the IOR to keep everything physically-based, and to align with the Adobe model which has the "specular IOR level" control for texturable IOR modulation.

With this change the ability to modulate IOR directly, with this [0, 1] control is lost. However arguably what users are looking for in the control is more like the uniform suppression of the entire F, like a lobe "weight" (allowing it to be totally killed off), not just suppressing F0 while keeping the grazing highlights intact. The use of textured specular_weight to produce variable specularity should be discouraged anyway, in favor of textured roughness, for better realism.

For the metal lobe, specular_weight already does this uniform suppression of the whole F. (We needed that in order to be able to kill the lobe). Also the specular_color already functions as an unphysical weight for the dielectric, so it does not really impact the physical basis of the model to have specular_weight simply multiply that.

The need to genuinely texture the IOR (e.g. for representing clothing with gems, in a single material) seems more like a niche, unusual use case (that would normally be done simply by modeling the gems separately). And it seems sub-optimal to design the parametrization around constraints of the DCC (i.e. that in the niche case where IOR has to be textured, it can't simply be controlled via [0,1] float textures by plugging in nodes which remap IOR in the range [1, 3]).

Also note that this was the behavior in Standard Surface which has been fairly battle tested, so we can be reasonably confident it is fine, at least for users happy with Standard Surface. (Although Standard Surface did not explicitly allow the weight to exceed 1).

@portsmouth portsmouth changed the title specular_weight reinterpretation, to avoid impact on refraction specular_weight reinterpretation Feb 4, 2025
@portsmouth portsmouth marked this pull request as draft February 4, 2025 18:56
@fpsunflower
Copy link

An alternative take could be to split the IOR used for fresnel from the IOR used for refraction (ray bending). This is the approach we took in Unreal's path tracer and it does have a number of advantages:

  • it's closer to what artists expect. They have independent controls over ray bending vs reflectivity
  • it does not exclude the physically correct behavior (having both IORs match) - it is strictly a superset that is more expressive
  • in the context of rough refraction, reducing the refraction IOR is a way to lower the refraction roughness without touching the reflection roughness (the specular highlight). The way the microfacet math works out, lower IOR means less bending which means roughness does less.

One natural reservation about this idea is -- how does this work with total internal reflection? The refraction equation for computing how the ray bends falls apart at a critical angle -- if this angle doesn't match the fresnel curve, one would think you would get a weird artifact if they don't line up.

There is a property of the dielectric fresnel equations that isn't immediately obvious, but allows this to work:

  Fresnel(cos_theta, 1 / ior) == Fr(cos_theta_t, ior)

Here cos_theta_t is the refraction angle cosine: Sqrt[(1-(1-cos_theta^2)*ior^2] and ior is the refraction index (assume to be >1 here so we are clear about which side is which).

In plain english, what this means is that the Fresnel curve you get when entering the material, is the same as the compressed Fresnel curve you get when exiting the material. As long as you do the "compression" using the refraction IOR, you are free to use any fresnel curve you want, you only need a curve with the right F0 value and have it tend towards 1.0 at grazing angles. Past the grazing angle when leaving the material, you have TIR and just use 1.0 (full reflection).

This also gives a recipe to replace the dielectric fresnel with the Schlick approximation if you want to (as has been documented in other talks like the Disney BSDF writeup or Natty Hoffman's talk on Fresnel).

Some other details that might be relevant -- we use the albedo scaling approach for energy conservation. At first I was worried that decoupling the IORs would require an extra dimension in the albedo table. However in practice, the specular IOR has a minimal effect. So sticking to a 3D table with (cos_theta, roughness, refraction IOR) is sufficient for good results.

I don't recall if the OpenPBR spec spells out the behavior of nested dielectrics -- but this can all be taken into account easily if you want. Again, you have a additional degrees of freedom over the behavior here, but the physically correct behavior can be kept.

@portsmouth
Copy link
Contributor Author

portsmouth commented Feb 4, 2025

This decoupling of the ray bending and the reflection seems like a more aggressive "non-physical" modification than having specular_weight be a simple multiplier. At this point you are basically just altering the Fresnel formulas to taste artistically.

If we did this, then specular_weight is no longer behaving in a "physically correct" way, which I thought was the argument for having it control the IOR. (To get the correct physical behavior with the proposed decoupling, artists have to not change specular_weight from the default).

If it's considered reasonable to have this non-physical behavior of specular_weight (altering the Fresnel formulas for the R and T modes separately) I find having it be a scale factor of the Fresnel easier to deal with, than thinking about it as decoupled IORs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants